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

DatePicker

A Calendar in a BottomSheet, bound to a value and a draft. You write every part.

Departure
Thu 17 Sep 2026
CancelDepartureDone

September 2026

MonTueWedThuFriSatSun1415161718192021222324252627

Installation

npx axiom add date-picker
pnpm dlx axiom add date-picker
yarn dlx axiom add date-picker
bun x axiom add date-picker

Also copies: calendar, bottom-sheet, button, chip, input and their dependencies.

Requires the calendar icon in your icon registry. The field borrows the colors of Input: DatePicker brings no tokens of its own.

Usage

import { BottomSheet } from "@/components/ui/bottom-sheet";
import { Calendar } from "@/components/ui/calendar";
import { DatePicker } from "@/components/ui/date-picker";
import { Field } from "@/components/ui/input";

<Field>
	<Field.Label>Departure</Field.Label>
	<DatePicker value={date} onChange={setDate}>
		<DatePicker.Trigger />
		<BottomSheet.Content>
			<BottomSheet.Handle />
			<BottomSheet.Header
				title="Departure"
				leading={<DatePicker.Cancel />}
				trailing={<DatePicker.Done />}
			/>
			<View style={{ paddingHorizontal: 16, paddingBottom: 16 }}>
				<DatePicker.Calendar minDate={today}>
					<Calendar.Header>
						<Calendar.Title />
						<Calendar.Nav>
							<Calendar.PrevButton />
							<Calendar.NextButton />
						</Calendar.Nav>
					</Calendar.Header>
					<Calendar.Grid>
						<Calendar.Weekdays />
						<Calendar.Days />
					</Calendar.Grid>
				</DatePicker.Calendar>
			</View>
		</BottomSheet.Content>
	</DatePicker>
</Field>;

The whole picker is in front of you: a Field, a trigger, a BottomSheet and a Calendar. To change it, change what you write: another header, presets above the calendar, a Clear button under it, any element as the trigger. DatePicker only adds what those pieces don’t know: the value the screen has, and the draft the user is editing.

How it’s built

          date-picker
               │
       ┌───────┴───────┐
       ↓               ↓
   calendar      bottom-sheet

DatePicker is a BottomSheet.Root: BottomSheet.Content, BottomSheet.Header and BottomSheet.Trigger work inside it as usual. The calendar edits a draft. Done applies it, Cancel or a swipe down drops it, so the screen’s value only changes on confirmation.

Props

DatePicker

PropTypeDefaultDescription
mode'single' | 'range''single'One date, or a start and end date. DatePicker.Calendar follows it.
valueDate | { from: Date; to?: Date } | null—The selected date or range. null shows the placeholder.
onChange(value) => void—Called with the new value on Done, on Clear, or on the first press with confirm="instant".
confirm'done' | 'instant''done'done: the value changes on Done, Cancel reverts it. instant: the sheet closes on the first press (single mode only).
hapticHapticKind | false'selection'Haptic played when the user picks a day or a preset. false turns it off.

Parts

PartWhat it is
DatePicker.TriggerA field that shows the value and opens the sheet. Takes placeholder, format and invalid. Inside a Field, it announces its label and follows its state.
DatePicker.CalendarA Calendar.Root bound to the draft. Takes every other Calendar.Root prop (minDate, maxDate, isDateDisabled, minRange, weekStartsOn…) and the Calendar parts as children.
DatePicker.CancelA ghost Button that drops the draft and closes.
DatePicker.DoneA ghost Button that applies the draft and closes. Leave it out with confirm="instant".
DatePicker.ClearA ghost Button that sets the value to null. Disabled while the value is empty.
DatePicker.PresetA Chip that picks its value into the draft: “Today”, “Next weekend”.

The action buttons take every Button prop except onPress, and their text as children. formatDateValue(value, locale) is exported for your own trigger.

Haptics

The picker plays a selection haptic when the user picks a day in the calendar or a preset. Pass haptic={false} to turn it off, or another kind to change it. Nothing plays until your app connects a haptics library: see Haptics.

Use cases

Date of a task

confirm="instant": one press sets the date and closes the sheet. Presets cover the common choices.

Due dateClear

Today

Tomorrow

Next week

MTWTFSS1415161718192021222324252627
<DatePicker confirm="instant" value={task.due} onChange={(due) => update({ due })}>
	<BottomSheet.Trigger asChild>
		<Chip leading="calendar">
			{task.due ? formatShort(task.due) : "Due date"}
		</Chip>
	</BottomSheet.Trigger>
	<BottomSheet.Content>
		<BottomSheet.Handle />
		<BottomSheet.Header title="Due date" leading={<DatePicker.Cancel />} />
		<View style={{ paddingHorizontal: 16, paddingBottom: 16, gap: 12 }}>
			<View style={{ flexDirection: "row", gap: 8 }}>
				<DatePicker.Preset value={today}>Today</DatePicker.Preset>
				<DatePicker.Preset value={addDays(today, 1)}>Tomorrow</DatePicker.Preset>
				<DatePicker.Preset value={nextMonday(today)}>Next week</DatePicker.Preset>
			</View>
			<DatePicker.Calendar>{monthParts}</DatePicker.Calendar>
			<DatePicker.Clear fullWidth />
		</View>
	</BottomSheet.Content>
</DatePicker>

Travel dates

mode="range" behind two fields that read as one. Done stays disabled until both dates are set.

Porto · 2 guests
Check-inThu 17 Sep
Check-outMon 21 Sep

Search stays

4 nights

<DatePicker mode="range" value={stay} onChange={setStay}>
	<BottomSheet.Trigger asChild>
		<StayField from={stay?.from} to={stay?.to} />
	</BottomSheet.Trigger>
	<BottomSheet.Content>
		<BottomSheet.Handle />
		<BottomSheet.Header
			title="Your stay"
			leading={<DatePicker.Cancel />}
			trailing={<DatePicker.Done />}
		/>
		<View style={{ paddingHorizontal: 16, paddingBottom: 16 }}>
			<DatePicker.Calendar minDate={today} minRange={1}>
				{monthParts}
			</DatePicker.Calendar>
		</View>
	</BottomSheet.Content>
</DatePicker>

Form field with validation

A regular field in a form, with a label, maxDate and an error when the date is missing.

Create account
Full name
Katherine Johnson
Date of birth
Select a date
We need your date of birth to continue.
<Field>
	<Field.Label>Date of birth</Field.Label>
	<DatePicker value={birthdate} onChange={setBirthdate}>
		<DatePicker.Trigger />
		<BottomSheet.Content>
			<BottomSheet.Handle />
			<BottomSheet.Header
				title="Date of birth"
				leading={<DatePicker.Cancel />}
				trailing={<DatePicker.Done />}
			/>
			<View style={{ paddingHorizontal: 16, paddingBottom: 16 }}>
				<DatePicker.Calendar maxDate={subYears(today, 13)}>
					{monthParts}
				</DatePicker.Calendar>
			</View>
		</BottomSheet.Content>
	</DatePicker>
	{submitted && !birthdate ? (
		<Field.Error>We need your date of birth to continue.</Field.Error>
	) : null}
</Field>

monthParts stands for the Calendar parts of the Usage example. Write them once in a small component of your app if every picker shows the same month.