A modular React calendar toolkit that starts tiny and grows with your product.
Monolithic pickers ship the grid, the toolbar, the time picker, the presets, the layout opinions — and all the weight. DateForge ships only what you use: every module is its own bundle on its own subpath, sharing one calendar core.
- Zero runtime dependencies. React 18/19 peer, nothing else.
- SSR-safe. No hydration mismatches — server render tested in CI.
- Accessible. Keyboard-first, roving focus, axe-audited, WCAG-checked contrast in every built-in theme.
- Themeable. 28 light/dark theme families, 8 appearances, gradient mode, or bring your own tokens.
- Pay per module. A minimal calendar stays around 20 KB min+gzip; size budgets are enforced in CI.
npm i @dateforge/react-calendarStyles ship with the components: with ESM (any bundler) the CSS is picked up automatically via the modules' own stylesheet imports. CJS consumers import the stylesheet once: import '@dateforge/react-calendar/style.css'.
One import, zero composition:
import { useState } from "react";
import { SimpleCalendar } from "@dateforge/react-calendar/prebuilt";
export function Example() {
const [date, setDate] = useState<Date | null>(null);
return <SimpleCalendar value={date} onChange={setDate} />;
}Every prebuilt is a ready recipe over the same primitives, with plain-Date props (value, defaultValue, onChange, locale, min/max, disabled, theme, appearance, gradient, scheme):
import {
DatePicker,
MonthPicker,
MultiMonthCalendar,
SimpleCalendar,
} from "@dateforge/react-calendar/prebuilt";
<SimpleCalendar onChange={setDate} /> // header + day grid
<DatePicker allowClear onChange={setDate} /> // typed input + grid + Today
<MonthPicker onChange={setMonth} /> // year stepper + 12-month grid
<MultiMonthCalendar months={6} cols={3} mode="range" /> // 6-month range boardWhen a prebuilt stops fitting, drop one level down: Calendar takes a compiled config (from createCalendarConfig) and any mix of modules as children. The root is a CSS grid — cols on the root declares columns, col on a module places it.
import {
Calendar,
commonPresets,
createCalendarConfig,
} from "@dateforge/react-calendar";
import { CalendarDays } from "@dateforge/react-calendar/modules/days";
import { CalendarPresets } from "@dateforge/react-calendar/modules/presets";
import {
CalendarToolbar,
CalendarToolbarMonthTrigger,
CalendarToolbarNext,
CalendarToolbarPrev,
CalendarToolbarYearTrigger,
} from "@dateforge/react-calendar/modules/toolbar";
const config = createCalendarConfig({
mode: "range",
locale: "de-DE",
disabled: { weekends: true },
});
export function BookingCalendar() {
return (
<Calendar
config={config}
cols={3}
onChange={(range) => console.log(range)} // { start: Date, end: Date } | null
>
<CalendarToolbar>
<CalendarToolbarPrev />
<CalendarToolbarMonthTrigger />
<CalendarToolbarYearTrigger />
<CalendarToolbarNext />
</CalendarToolbar>
<CalendarPresets col={1} presets={commonPresets} />
<CalendarDays col={2} />
</Calendar>
);
}Remove a line, remove a feature. Add a module, add a workflow. Order in JSX is visual flow — no order props, no slots.
The onChange value shape is derived from unit × mode alone: day+single → Date | null, day+multiple → Date[], any single span (range, or week/month single) → { start, end } | null, any multi span → { start, end }[].
Import from @dateforge/react-calendar/modules, or per subpath (…/modules/days, …/modules/toolbar, …) for the smallest possible bundle.
| Module | Use it for |
|---|---|
CalendarToolbar + primitives |
Composable header: prev/next (unit-aware), month/year triggers and labels, Home, Apply, clock |
CalendarDays |
Classic month grid — single, multiple, range, multi-range; offset for multi-month boards |
CalendarMonthsGrid / CalendarYearsGrid |
Month/year picking or fast jumps |
CalendarDaysTrack / CalendarMonthsTrack / CalendarYearsTrack |
Physics-based scrollable strips for compact and mobile layouts |
CalendarTimeWheel / CalendarMonthsWheel / CalendarYearsWheel |
iOS-style drum pickers, range-bound aware (bound="from" | "to") |
CalendarManualInput |
Typed, segment-based date entry with keyboard stepping |
CalendarPresets |
Shortcuts — commonPresets, relativePresets, or definePreset your own |
CalendarSelectedDates |
Selected-date chips with overflow and per-chip clear |
CalendarInfo |
Selection metrics, relative hints, empty text |
CalendarLunar |
Information-only lunar phase strip |
- Selection axes —
mode:single/multiple/range/multi-range×unit:day/week/month. PlusminSpan/maxSpan,maxDates,maxRanges. - Disabled vs excluded —
disableddays can't be picked;excludedays are cut out of emitted spans (business-day flows), with segments reported in change details. - 28 theme families — each with light + dark:
abyss,aurora,bauhaus,chalk,crimson,cyber,dracula,eclipse,espresso,fjord,graphite,industrial,meadow,mint,monsoon,nebula,neon,noir,pearl,prism,riso,sandstone,slate,snow,solar,split,temporal,velvet. - 8 appearances (shape/spacing, independent of color):
zenith,airy,bubble,compact,loft,press,soft,square. - Gradient mode —
gradienton the root adds corner glows and gradient selected fills, pure CSS, theme-driven. - Scheme control —
scheme="auto" | "light" | "dark", controllable viaonSchemeChange; no dark-mode flash on SSR. - Locales via
Intl— month/weekday names, digits, and week start derive from any BCP-47locale; no locale files to import. - RTL — logical properties throughout; the calendar follows the inherited
dir. - Time —
withTime, 12/24-hour,ampmLabels,defaultTime, and aminTime/maxTimewindow enforced by the core. - Time zones & DST — IANA
timeZonefor "today" resolution, with explicit ambiguous/nonexistent-time policies. - Presets — built-in packs plus
definePresetwith validation context (disabled/min/max aware). - Accessibility — full keyboard navigation, focus management, live announcements, axe test suite in CI.
- Validation hooks —
onValidationRejecttells you what was refused and why;onViewChangetracks navigation.
import { Calendar, createTheme } from "@dateforge/react-calendar";
import { nebula } from "@dateforge/react-calendar/themes";
import { compact } from "@dateforge/react-calendar/appearances";
// Built-in: by name (generated stylesheet) or by object (tree-shaken)
<Calendar config={config} theme="dracula" />
<Calendar config={config} theme={nebula} appearance={compact} scheme="dark" />
// Custom: common tokens + per-mode overrides → a light/dark family
const brand = createTheme({
accent: "#2563eb",
range: "#22c55e",
light: { backdrop: "#f8fafc" },
dark: { backdrop: "#0f172a", text: "#f8fafc" },
});
<Calendar config={config} theme={brand} />Budgets from .size-limit.json, min+gzip, enforced on every commit (React excluded):
| Import | Budget |
|---|---|
Calendar only |
< 18 KB |
Calendar + CalendarDays |
< 21.5 KB |
Calendar + CalendarDays + theme + appearance |
< 25.5 KB |
Calendar + Toolbar + CalendarDays |
< 26 KB |
SimpleCalendar (prebuilt, one import) |
< 27 KB |
Dual ESM/CJS, sideEffects limited to CSS, subpath exports per module — bundlers drop everything you don't touch.
v3 is a clean rebuild. The three headline breaking changes:
- Config object instead of flat props.
<Calendar mode locale min …>becamecreateCalendarConfig({ mode, locale, min, … })passed as oneconfigprop — compiled once, shared by every module. - Value shapes by
unit×mode.onChangeshapes are fully determined by the config's unit and mode (see above); segments and reasons arrive in a seconddetailsargument. - Themes are families only. Every theme is a
{ light, dark }pair; the v1 single-variant palettes are gone,createThemealways returns a family.
Prefer not to migrate composition code by hand? The prebuilt components cover the common v2 setups with one import. Full details in DOCUMENTATION.md.
- 📚 Repo Documentation
- 🏛 Architecture
- 📝 Changelog
- 📚 Storybook
- 🐛 Issues