Calendar
Pre-alphaThe registry and the CLI are not published yet.Roadmap

Calendar

Navigable month grid with single or range selection.

Check-in
September 2026
MonTueWedThuFriSatSun31123456789101112131415161718

19

20212223242526272829301234

Installation

npx axiom add calendar
pnpm dlx axiom add calendar
yarn dlx axiom add calendar
bun x axiom add calendar

Also 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.Dot

Props

Calendar.Root

PropTypeDefaultDescription
mode'single' | 'range' | 'multiple''single'One date, a start–end range, or any set of dates.
selectedDate | { from: Date; to?: Date } | Date[]The selection, shaped by mode. A range with only from is a range in progress.
defaultSelectedsame as selectedInitial selection when the calendar manages its own state.
onSelect(selected) => voidCalled when the user presses a day. In range mode, the first press sets from, the second sets to.
monthDatemonth of selectedDisplayed month. Use with onMonthChange to control navigation.
defaultMonthDatemonth of selectedInitial month when navigation is uncontrolled.
onMonthChange(month: Date) => voidCalled when the month changes, from the arrows, a swipe or your own controls.
minDateDateDays before are disabled, and navigation stops at that month.
maxDateDateDays after are disabled, and navigation stops at that month.
isDateDisabled(date: Date) => booleanDisables specific days: weekends, fully booked dates.
minRange / maxRangenumberIn range mode, limits on the number of nights.
weekStartsOn0 | 1 | … | 6localeFirst day of the week. Defaults to the device locale.
localestringdeviceMonth and weekday names.
styleStyleProp<ViewStyle>Extra styles for the container.
childrenReactNodeHeader, Grid and any custom content.

Calendar.Header

A row with the title on the leading side and the navigation on the trailing side.

PropTypeDefaultDescription
styleStyleProp<ViewStyle>Extra styles for the row.
childrenReactNodeTitle, Nav, or your own controls.

Calendar.Title

PropTypeDefaultDescription
format'month' | 'month-year''month-year'“September” or “September 2026”, in the Root locale.
variantTextVariant'headline'Text style.
children(month: Date) => ReactNodeReplaces 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

PropTypeDefaultDescription
swipeablebooleantrueHorizontal swipe to change month.
styleStyleProp<ViewStyle>Extra styles for the grid.
childrenReactNodeWeekdays and Days.

Calendar.Weekdays

PropTypeDefaultDescription
format'short' | 'narrow''short'“Mon” or “M”.

Calendar.Days

PropTypeDefaultDescription
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.
showOutsideDaysbooleantrueShows 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

PropTypeDefaultDescription
dayDayStateThe cell to render, from Calendar.Days.
disabledbooleanday.isDisabledOverrides the disabled state for this cell.
childrenReactNodeContent 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

PropTypeDefaultDescription
colorstringcalendar.day.default.dotDot 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.

14151617181920212223242526272829301234
4 nightsThu 17 – Mon 21 Sep
Reserve
<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.

SeptemberToday
MTWTFSS14151617181920
Design review14:00 – 15:00
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.

Date of birth

March

1992

MTWTFSS23456789101112131415
<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.

  • DatePicker, which puts a Calendar in a BottomSheet