Calendar
Calendar shows a month grid so users can pick a date without opening anything
first — the right shape for booking flows, availability views, and filters where
the month should stay on screen.
Usage
import { Calendar } from '@takeoff-ui/react-spar';
<Calendar />
Calendar is a single component: the month, the grid, and every day cell are
rendered by the underlying engine, so there are no compound parts to place.
Reach any node through the classNames / slotProps slot keys instead —
root, months, month, nav, previousMonthButton, nextMonthButton,
chevron, monthCaption, captionLabel, dropdowns, dropdownRoot,
dropdown, monthGrid, weekdays, weekday, weeks, week, weekNumber,
weekNumberHeader, day, dayButton, footer.
Values are Date objects. value + onValueChange is the controlled pair;
defaultValue is the uncontrolled one.
Playground
Range
mode="range" selects a continuous span and reports { from, to }. min and
max bound how many days it may cover, and excludeDisabled rejects a range
that would swallow a disabled day.
The range edges are styled by class rather than by attribute, because the engine
expresses the position that way: .tk-calendar-day-range-start,
.tk-calendar-day-range-middle, .tk-calendar-day-range-end.
Multiple dates
mode="multiple" collects separate days into an array. min / max bound how
many can be selected.
Restricting what can be picked
minDate and maxDate are inclusive and bound both navigation and selection.
disabledDates blocks individual days, disabledWeekDays blocks whole columns
(0 = Sunday), and firstDayOfWeekIndex decides which weekday leads the row.
For a whitelist, pass allowedDates — every unlisted day is then disabled. An
empty array means "no whitelist", so a list that has not loaded yet does not
disable the whole month:
<Calendar allowedDates={[new Date(2026, 7, 3), new Date(2026, 7, 4)]} />
Sizes
Two scales, base (40px day cells) and small (32px) — the two the design
system defines for a picking grid.
Header types
headerType is Takeoff Core's tk-datepicker header vocabulary. basic
divides the month row from the grid; divided, light, primary and dark
drop that divider and put the month and its arrows inside a boxed surface
instead.
Navigation & layout
captionLayout swaps the month label for <select> navigation, navLayout
moves the arrows, showWeekNumber adds the week column, fixedWeeks keeps the
grid six rows tall so the box does not resize between months, and
showOutsideDays fills the leading and trailing week.
Month and year panels
The month and the year in the caption are buttons: they swap the day grid for a twelve-month or twelve-year board — Takeoff Core's view switch. Picking a year drills down to that year's months; picking a month returns to the days.
There is one board however many months are displayed, and it belongs to the first of them: that month's caption carries the buttons, the later captions stay plain labels of their own month. While a board is open the calendar shows that one month, so the card keeps its navigation and returns to the full row when the board closes.
Which board is showing is a third controlled pair: view + onViewChange, with
defaultView to choose the opening board without taking control of it.
The header carries two pairs of arrows, each stepping one rung of the board it
is on: the single ones move a month, or a year once the year board is showing;
the double ones move a year, or a whole twelve-year page. Each arrow is named
after what it lands on and stays live while any part of that month, year or page
is inside minDate / maxDate.
This is the one part of the anatomy react-day-picker does not render — it has
no month or year view — so the wrapper supplies it, and minDate / maxDate
disable the cells that fall outside.
A dropdown* caption changes two things and nothing else. The caption keeps the
engine's <select> pair rather than gaining switch buttons, since that layout
owns the same node; and the header drops the year arrows, because the year
<select> already covers them and the row has no width for both. The boards
themselves still work, through view / defaultView.
navLayout="around" drops the year arrows for the same reason — the engine
renders that row itself, as one arrow on each side of the caption. The boards
work there too: the single arrows step the board's own rung, so the year board
still walks from one twelve-year page to the next.
disableNavigation reaches the boards as well as the arrows: the cells go
disabled, since picking one would move the displayed month.
Presets
footer takes any node, which makes it the place for shortcut controls under
the grid. Setting the value is all a preset has to do — a date that lands
outside the visible month brings the grid with it.
One caveat worth knowing: the engine renders the footer as a polite live region
(role="status"), which is right for the status text it was designed for and
less so for a row of buttons. Static labels like these do not re-announce, but
if you would rather keep controls out of a live region, render them as a sibling
of <Calendar> instead of through footer — the wiring is identical.
Localization
locale takes a react-day-picker locale object, so only the locales you
import are bundled:
import { tr } from 'react-day-picker/locale';
import { Calendar } from '@takeoff-ui/react-spar';
<Calendar locale={tr} firstDayOfWeekIndex={1} />;
numerals switches the digit system, dir="rtl" mirrors the layout, and
timeZone decides which day counts as today.
Customization
Calendar exposes several customization surfaces without taking ownership of its
selection or keyboard behavior. Use classNames for CSS hooks, slotProps for
HTML attributes, renderDay for the content inside each day button, and
footer for status or supporting content.
The following example combines all of them. The calendar gets a custom visual skin, event markers inside individual days, an accessible grid description, a custom navigation label, a live selection status, and a toggle for outside days:
Every class in the rendered tree is Takeoff-owned (tk-calendar-*) — no engine
stylesheet is imported. Day state is read from the attributes the engine already
emits, so recipes can target state without a wrapper mirror:
.tk-calendar-day[data-selected] .tk-calendar-day-button {
/* ... */
}
.tk-calendar-day[data-today] .tk-calendar-day-button {
/* ... */
}
When styling a slot, prefix selectors with the root class. The recipe styles a
selected day through .tk-calendar-day[data-selected] .tk-calendar-day-button,
so an override needs at least that much weight — which is also why a bare
utility class on a slot can lose: utility frameworks usually emit into a cascade
layer, and unlayered recipe rules win over layered ones regardless of order.
Accessibility & Keyboard
- The grid is a real
role="grid"table labelled by the month caption. - Arrow keys move by day,
PageUp/PageDownby month,Home/Endto the week edges, andEnter/Spaceselect the focused day. - Each day button carries a localized accessible name; disabled and outside days are announced through the engine's own ARIA.
- The previous/next buttons stay in the tab order at the boundary and expose
aria-disabled, so reaching the edge does not shift the layout. - Chevrons are
aria-hidden— the buttons carry the names. footeris rendered in a polite live region.- The month and year boards are
role="grid"too, with one tab stop: arrow keys move between cells (vertically by four, the board's column count),Home/Endreach the row edges, and the displayed month or year isaria-selected. Cells outsideminDate/maxDatecannot take focus, so every key steps over them and leaves the keypress alone when there is nothing left to reach. - Opening a board moves focus into it; picking a month hands focus back to the
caption trigger for that board — the one it was opened from, or the caption's
own when
defaultVieworviewopened it.Escapecloses a board the same way, and stops there rather than reaching a surface the calendar sits in. - The caption keeps a polite live region of its own, so the month still announces after the arrows move it.
API Reference
Calendar
See react-day-picker docs for primitive behavior.
Props
| Name | Type | Default | Description |
|---|---|---|---|
| size | CalendarSize | 'base' | Size scale → root data-size. |
| headerType | CalendarHeaderType | 'basic' | Caption-row treatment → root data-header-type. |
| view | CalendarView | - | Board the body shows (controlled). Pair with onViewChange; the caption's month and year switch it, so a view passed without a handler locks the body to that board. Under a dropdown* caption the boards still work, but the caption keeps the engine's <select> pair instead of gaining switch buttons. |
| defaultView | CalendarView | 'day' | Board the body opens on, without taking control of it. |
| minDate | Date | - | Earliest selectable date, inclusive. Bounds both navigation (the engine's startMonth) and selection (a { before } disabled matcher). |
| maxDate | Date | - | Latest selectable date, inclusive. Bounds both navigation (the engine's endMonth) and selection (an { after } disabled matcher). |
| disabledDates | Date[] | - | Individual dates that cannot be selected. |
| allowedDates | Date[] | - | Whitelist: when set, every date not listed is disabled. Combines with disabledDates / disabledWeekDays / minDate / maxDate — a date must pass all of them to be selectable. |
| disabledWeekDays | number[] | - | Weekday indices that cannot be selected, 0 = Sunday. |
| firstDayOfWeekIndex | CalendarWeekStart | - | First day of the week, 0 = Sunday. Defaults to the locale's own first day. |
| classNames | Partial<Record<CalendarSlot, string>> | - | Per-slot class name overrides. |
| slotProps | Partial<Record<CalendarSlot, React.HTMLAttributes<HTMLElement>>> | - | Per-slot HTML attribute overrides. |
| renderDay | CalendarDayRenderer | - | Custom content rendered inside each day button. |
| footer | React.ReactNode | - | Add a footer to the calendar, acting as a live region. Use this prop to communicate the calendar's status to screen readers. Prefer strings over complex UI elements. |
| autoFocus | boolean | - | When a selection mode is set, DayPicker will focus the first selected day (if set) or today's date (if not disabled). Use this prop when you need to focus DayPicker after a user action, for improved accessibility. |
| dir | string | - | The text direction of the calendar. Use ltr for left-to-right (default) or rtl for right-to-left. |
| id | string | - | A unique id to add to the root element. |
| role | "dialog" | "application" | - | The role attribute to add to the container element. |
| aria-label | string | - | The aria-label attribute to add to the container element. |
| aria-labelledby | string | - | The aria-labelledby attribute to add to the container element. |
| month | Date | - | The month displayed in the calendar. As opposed to defaultMonth, use this prop with onMonthChange to change the month programmatically. |
| defaultMonth | Date | The current month | The initial month to show in the calendar. Use this prop to let DayPicker control the current month. If you need to set the month programmatically, use month and onMonthChange. |
| numberOfMonths | number | 1 | The number of displayed months. |
| pagedNavigation | boolean | - | Paginate the month navigation displaying the numberOfMonths at a time. |
| reverseMonths | boolean | - | Render the months in reversed order (when numberOfMonths is set) to display the most recent month first. |
| reverseYears | boolean | - | Reverse the order of years in the dropdown when using captionLayout="dropdown" or captionLayout="dropdown-years". |
| hideNavigation | boolean | - | Hide the navigation buttons. This prop won't disable the navigation: to disable the navigation, use disableNavigation. |
| disableNavigation | boolean | - | Disable the navigation between months. This prop won't hide the navigation: to hide the navigation, use hideNavigation. |
| captionLayout | "label" | "dropdown" | "dropdown-months" | "dropdown-years" | - | Show dropdowns to navigate between months or years. - label: Displays the month and year as a label. Default value. - dropdown: Displays dropdowns for both month and year navigation. - dropdown-months: Displays a dropdown only for the month navigation. - dropdown-years: Displays a dropdown only for the year navigation. Note: By default, showing the dropdown will set the startMonth to 100 years ago and endMonth to the end of the current year. You can override this behavior by explicitly setting startMonth and endMonth. |
| navLayout | "around" | "after" | - | Adjust the positioning of the navigation buttons. - around: Displays the buttons on either side of the caption. - after: Displays the buttons after the caption. This ensures the tab order matches the visual order. If not set, DayPicker preserves its legacy layout, but the tab order may not align with the visual order when using captionLayout="dropdown". |
| fixedWeeks | boolean | - | Display always 6 weeks per each month, regardless of the month’s number of weeks. Weeks will be filled with the days from the next month. |
| hideWeekdays | boolean | - | Hide the row displaying the weekday row header. |
| showOutsideDays | boolean | - | Show the outside days (days falling in the next or the previous month). Note: when a broadcastCalendar is set, this prop defaults to true. |
| showWeekNumber | boolean | - | Show the week numbers column. Weeks are numbered according to the local week index. |
| broadcastCalendar | boolean | - | Display the weeks in the month following the broadcast calendar. Setting this prop will ignore weekStartsOn (always Monday) and showOutsideDays will default to true. |
| ISOWeek | boolean | - | Use ISO week dates instead of the locale setting. Setting this prop will ignore weekStartsOn and firstWeekContainsDate. |
| timeZone | string | - | The time zone (IANA or UTC offset) to use in the calendar (experimental). See Wikipedia for the possible values. |
| today | Date | - | The today’s date. Default is the current date. This date will get the today modifier to style the day. |
| locale | Partial<DayPickerLocale> | enUS - The English locale default of date-fns. | The locale object used to localize dates. Pass a locale from react-day-picker/locale to localize the calendar. |
| numerals | Numerals | latn Latin (Western Arabic) | The numeral system to use when formatting dates. - latn: Latin (Western Arabic) - arab: Arabic-Indic - arabext: Eastern Arabic-Indic (Persian) - deva: Devanagari - beng: Bengali - guru: Gurmukhi - gujr: Gujarati - orya: Oriya - tamldec: Tamil - telu: Telugu - knda: Kannada - mlym: Malayalam |
| formatters | Partial<Formatters> | - | Formatters used to format dates to strings. Use this prop to override the default functions. |
| labels | Partial<Labels> | - | Labels creators to override the defaults. Use this prop to customize the aria-label attributes in DayPicker. |
| className | string | - | Extra class on the root element. |
Events
| Name | Type | Default | Description |
|---|---|---|---|
| onViewChange | (view: CalendarView) => void | - | Fires when the caption or a board selection moves the body to another board. |
| onMonthChange | MonthChangeEventHandler | - | Event fired when the user navigates between months. |
Data attributes
| Attribute | Applied when | Purpose |
|---|---|---|
| data-slot="root" | Always | Stable selector for wrapper styling on the root slot. Every anatomy node carries its own data-slot (day, month-grid, weekday, …). |
| data-size | Always | Reflects the resolved size for theme recipe scoping; also drives the day-cell geometry. |
| data-header-type | Always | Reflects the resolved headerType; drives the caption-row treatment. |
| data-view | Always | The board the body is showing — day, month or year. Also what pins the body box, so switching views does not resize the card. The caption triggers and the board carry their own data-view. |
| data-mode | Always | Emitted by the engine. Reflects the selection mode. |
| data-multiple-months | numberOfMonths is greater than 1 | Emitted by the engine. |
| data-week-numbers | showWeekNumber is set | Emitted by the engine. |
| data-nav-layout | navLayout is set | Emitted by the engine. |
| data-day | Always, on each day cell | Emitted by the engine. The cell’s ISO date (YYYY-MM-DD) — the stable hook for targeting a specific day. |
| data-selected | The day is part of the selection | Emitted by the engine on the day cell. |
| data-today | The day is today | Emitted by the engine on the day cell. |
| data-outside | The day belongs to a neighbouring month | Emitted by the engine on the day cell. |
| data-disabled | The day cannot be selected | Emitted by the engine on the day cell. |
| data-focused | The day holds keyboard focus | Emitted by the engine on the day cell. |
Calendar — mode="single"
Props
| Name | Type | Default | Description |
|---|---|---|---|
| mode | "single" | 'single' | Selection mode. |
| value | Date | - | Selected date (controlled). |
| defaultValue | Date | - | Initially selected date (uncontrolled). |
Events
| Name | Type | Default | Description |
|---|---|---|---|
| onValueChange | (value: Date) => void | - | Fires with the new selection, or undefined when it is cleared. |
Calendar — mode="multiple"
Props
| Name | Type | Default | Description |
|---|---|---|---|
| mode | "multiple" | - | |
| value | Date[] | - | Selected dates (controlled). |
| defaultValue | Date[] | - | Initially selected dates (uncontrolled). |
| min | number | - | Fewest dates that may be selected. |
| max | number | - | Most dates that may be selected. |
Events
| Name | Type | Default | Description |
|---|---|---|---|
| onValueChange | (value: Date[]) => void | - | Fires with the new selection, or undefined when it is cleared. |
Calendar — mode="range"
Props
| Name | Type | Default | Description |
|---|---|---|---|
| mode | "range" | - | |
| value | DateRange | - | Selected range (controlled). |
| defaultValue | DateRange | - | Initially selected range (uncontrolled). |
| min | number | - | Fewest days the range may span. |
| max | number | - | Most days the range may span. |
| excludeDisabled | boolean | - | Forbid a range that would contain a disabled date. |
Events
| Name | Type | Default | Description |
|---|---|---|---|
| onValueChange | (value: CalendarRange) => void | - | Fires with the new range, or undefined when it is cleared. |
Type Definitions
| Name | Definition |
|---|---|
| CalendarSize | 'small' | 'base' |
| CalendarHeaderType | 'basic' | 'divided' | 'light' | 'primary' | 'dark' |
| CalendarView | 'day' | 'month' | 'year' |
| CalendarWeekStart | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
| CalendarSlot | | 'root' | 'months' | 'month' | 'nav' | 'previousMonthButton' | 'nextMonthButton' | 'previousYearButton' | 'nextYearButton' | 'chevron' | 'monthCaption' | 'captionLabel' | 'captionTrigger' | 'dropdowns' | 'dropdownRoot' | 'dropdown' | 'monthGrid' | 'monthYearGrid' | 'monthYearCell' | 'weekdays' | 'weekday' | 'weeks' | 'week' | 'weekNumber' | 'weekNumberHeader' | 'day' | 'dayButton' | 'footer' |
| CalendarDayRenderer | (date: Date, modifiers: Modifiers) => ReactNode |
| DayPickerLocale | { labels?: DayPickerLocaleLabels } |
| Numerals | "latn" | "arab" | "arabext" | "deva" | "geez" | "beng" | "guru" | "gujr" | "orya" | "tamldec" | "telu" | "knda" | "mlym" | "thai" | "mymr" | "khmr" | "laoo" | "tibt" |
| Formatters | { /** Format the caption of a month grid. */ formatCaption: typeof formatCaption; /** Format the day in the day cell. _/ formatDay: typeof formatDay; /** Format the label in the month dropdown. _/ formatMonthDropdown: typeof formatMonthDropdown; /** Format the week number. */ formatWeekNumber: typeof formatWeekNumber; /** Format the header of the week number column. _/ formatWeekNumberHeader: typeof formatWeekNumberHeader; /** Format the week day name in the header. / formatWeekdayName: typeof formatWeekdayName; /* Format the label in the year dropdown. _/ formatYearDropdown: typeof formatYearDropdown; } |
| Labels | { /** The label for the navigation toolbar. */ labelNav: typeof labelNav; /** The label for the month grid. _/ labelGrid: typeof labelGrid; /** The label for the gridcell, when the calendar is not interactive. _/ labelGridcell: typeof labelGridcell; /** The label for the month dropdown. */ labelMonthDropdown: typeof labelMonthDropdown; /** The label for the year dropdown. _/ labelYearDropdown: typeof labelYearDropdown; /** The label for the "next month" button. _/ labelNext: typeof labelNext; /** The label for the "previous month" button. */ labelPrevious: typeof labelPrevious; /** The label for the day button. _/ labelDayButton: typeof labelDayButton; /** The label for the weekday. _/ labelWeekday: typeof labelWeekday; /** The label for the week number. */ labelWeekNumber: typeof labelWeekNumber; /** The label for the column of week numbers. */ labelWeekNumberHeader: typeof labelWeekNumberHeader; } |
| MonthChangeEventHandler | (month: Date) => void |
| DateRange | { from: Date | undefined; to?: Date | undefined; } |
| CalendarRange | DateRange |