Calendar
Navigable month grid with single or range selection.
Installation
npx axiom add calendarpnpm dlx axiom add calendaryarn dlx axiom add calendarbun x axiom add calendarAlso copies: icon-button.
Installs: react-native-gesture-handler, react-native-worklets.
Requires the chevron-left and chevron-right icons in your icon registry. Date math, selection and the day grid live in use-calendar.ts, with no date library. Colors come from calendar.day.{default,pressed,today,selected,inRange,outside,disabled} in the component tokens.
Usage
import { Calendar } from "@/components/ui/calendar";
<Calendar.Root mode="range" selected={range} onSelect={setRange}>
<Calendar.Header>
<Calendar.Title />
<Calendar.Nav>
<Calendar.PrevButton />
<Calendar.NextButton />
</Calendar.Nav>
</Calendar.Header>
<Calendar.Grid>
<Calendar.Weekdays />
<Calendar.Days />
</Calendar.Grid>
</Calendar.Root>;Calendar.Root holds the selection and the displayed month; the other parts read them from context. Leave a part out, move it, or put your own components between them.
Anatomy
Calendar.Root
├── Calendar.Header
│ ├── Calendar.Title
│ └── Calendar.Nav
│ ├── Calendar.PrevButton
│ └── Calendar.NextButton
└── Calendar.Grid
├── Calendar.Weekdays
└── Calendar.Days
└── Calendar.Day
└── Calendar.DotProps
Calendar.Root
| Prop | Type | Default | Description |
|---|---|---|---|
mode | 'single' | 'range' | 'multiple' | 'single' | One date, a start–end range, or any set of dates. |
selected | Date | { from: Date; to?: Date } | Date[] | — | The selection, shaped by mode. A range with only from is a range in progress. |
defaultSelected | same as selected | — | Initial selection when the calendar manages its own state. |
onSelect | (selected) => void | — | Called when the user presses a day. In range mode, the first press sets from, the second sets to. |
month | Date | month of selected | Displayed month. Use with onMonthChange to control navigation. |
defaultMonth | Date | month of selected | Initial month when navigation is uncontrolled. |
onMonthChange | (month: Date) => void | — | Called when the month changes, from the arrows, a swipe or your own controls. |
minDate | Date | — | Days before are disabled, and navigation stops at that month. |
maxDate | Date | — | Days after are disabled, and navigation stops at that month. |
isDateDisabled | (date: Date) => boolean | — | Disables specific days: weekends, fully booked dates. |
minRange / maxRange | number | — | In range mode, limits on the number of nights. |
weekStartsOn | 0 | 1 | … | 6 | locale | First day of the week. Defaults to the device locale. |
locale | string | device | Month and weekday names. |
style | StyleProp<ViewStyle> | — | Extra styles for the container. |
children | ReactNode | — | Header, Grid and any custom content. |
Calendar.Header
A row with the title on the leading side and the navigation on the trailing side.
| Prop | Type | Default | Description |
|---|---|---|---|
style | StyleProp<ViewStyle> | — | Extra styles for the row. |
children | ReactNode | — | Title, Nav, or your own controls. |
Calendar.Title
| Prop | Type | Default | Description |
|---|---|---|---|
format | 'month' | 'month-year' | 'month-year' | “September” or “September 2026”, in the Root locale. |
variant | TextVariant | 'headline' | Text style. |
children | (month: Date) => ReactNode | — | Replaces the default text. |
The title is a live region: screen readers announce the new month after navigation.
Calendar.Nav, Calendar.PrevButton, Calendar.NextButton
Nav is a row for the two buttons. PrevButton and NextButton take IconButton props; they default to size="sm", chevron icons and the labels “Previous month” and “Next month”. Each one is disabled at minDate or maxDate.
Calendar.Grid
| Prop | Type | Default | Description |
|---|---|---|---|
swipeable | boolean | true | Horizontal swipe to change month. |
style | StyleProp<ViewStyle> | — | Extra styles for the grid. |
children | ReactNode | — | Weekdays and Days. |
Calendar.Weekdays
| Prop | Type | Default | Description |
|---|---|---|---|
format | 'short' | 'narrow' | 'short' | “Mon” or “M”. |
Calendar.Days
| Prop | Type | Default | Description |
|---|---|---|---|
weeks | 'fit' | 'fixed' | number | 'fit' | fit: as many rows as the month needs. fixed: always 6 rows, so the height doesn’t change between months. A number shows only that many weeks, starting from the week of month. |
showOutsideDays | boolean | true | Shows days from the previous and next months in muted text. |
children | (day: DayState) => ReactNode | (day) => <Calendar.Day day={day} /> | Renders each cell. |
type DayState = {
date: Date;
key: string; // YYYY-MM-DD
isToday: boolean;
isSelected: boolean;
isRangeStart: boolean;
isRangeEnd: boolean;
isInRange: boolean;
isOutside: boolean;
isDisabled: boolean;
};Calendar.Day
| Prop | Type | Default | Description |
|---|---|---|---|
day | DayState | — | The cell to render, from Calendar.Days. |
disabled | boolean | day.isDisabled | Overrides the disabled state for this cell. |
children | ReactNode | — | Content under the number, such as a Calendar.Dot. |
Days are 40pt cells in a 7-column grid; each one is a button announced with its full date (“Thursday 17 September, selected”).
Calendar.Dot
| Prop | Type | Default | Description |
|---|---|---|---|
color | string | calendar.day.default.dot | Dot color. Turns to calendar.day.selected.dot when the day is selected. |
useCalendar
Reads the Root state from any component inside Calendar.Root, for custom headers and footers.
const { month, setMonth, selected, goToToday } = useCalendar();Use cases
Booking a stay
range mode with past days disabled and a minimum stay. The summary under the grid updates as the user picks.
<Calendar.Root
mode="range"
selected={stay}
onSelect={setStay}
minDate={today}
minRange={2}
isDateDisabled={(d) => bookedDates.has(toKey(d))}
>
<Calendar.Grid>
<Calendar.Days />
</Calendar.Grid>
<StaySummary stay={stay} />
</Calendar.Root>Agenda with marked days
Calendar.Days renders each cell, so a Calendar.Dot marks days with events. A custom button in the header returns to today, and selecting a day filters the list under the calendar.
function TodayButton() {
const { goToToday } = useCalendar();
return (
<Button variant="ghost" size="sm" onPress={goToToday}>
Today
</Button>
);
}
<Calendar.Root selected={day} onSelect={setDay}>
<Calendar.Header>
<Calendar.Title variant="title3" format="month" />
<TodayButton />
</Calendar.Header>
<Calendar.Grid>
<Calendar.Weekdays format="narrow" />
<Calendar.Days weeks={1}>
{(d) => (
<Calendar.Day day={d}>
{eventsByDay.has(d.key) && (
<Calendar.Dot color="feedback.warning" />
)}
</Calendar.Day>
)}
</Calendar.Days>
</Calendar.Grid>
</Calendar.Root>;Birthdate
Far-away dates are slow to reach month by month. Replace Title and Nav with month and year pickers that set the controlled month.
<Calendar.Root
selected={birthdate}
onSelect={setBirthdate}
maxDate={today}
month={month}
onMonthChange={setMonth}
>
<Calendar.Header>
<MonthChip value={month} onChange={setMonth} />
<YearChip value={month} onChange={setMonth} max={today.getFullYear()} />
</Calendar.Header>
<Calendar.Grid swipeable={false}>
<Calendar.Weekdays format="narrow" />
<Calendar.Days />
</Calendar.Grid>
</Calendar.Root>For a date field that opens the calendar in a sheet, use DatePicker.
Related
- DatePicker, which puts a Calendar in a BottomSheet