Date pickers
A day, or two that belong together: DatePicker and DateRangePicker triggers, inline calendars, and a bare DateInput. React Aria owns the grid and locale.
Date pickers
A day, or two that belong together: DatePicker and DateRangePicker triggers, inline calendars, and a bare DateInput. React Aria owns the grid and locale.
Demo
All three of the sheet’s Type variants: both exported pickers, and Available times as the recipe it has to be.
Open it — the popover is the real one
DatePicker, a one-month Calendar, a date input beside a Today button, and a Cancel / Apply footer; for DateRangePicker, the nine-preset sidebar from 1024px, two months from 768px, and a start / end input pair in the footer. Selecting a day does not close either one — shouldCloseOnSelect is false on both pickers, so Apply is what commits. The copied snippet carries the CalendarDate import, because a starting date is an object rather than a string.Contextual demo
A date picker only makes sense beside the thing it dates. Both pickers and both inline calendars, in a real screen.
The family
Nine exported names across seven files — one exported twice, from two modules: a fact, not a typo.
| Component | Description | Import from |
|---|---|---|
| DatePicker | The single-date trigger and its popover: a secondary button with a calendar icon, a one-month Calendar inside, and a Cancel / Apply footer. | @/components/application/date-picker/date-picker |
| DateRangePicker | The range trigger and its popover: nine presets in a sidebar from 1024px, a two-month RangeCalendar, and a footer of two date inputs beside Cancel / Apply. | @/components/application/date-picker/date-range-picker |
| Calendar | The single-select month grid, usable inline. Header, an optional date-input-and-Today row you can replace through children, and the grid. | @/components/application/date-picker/calendar |
| CalendarContextProvider | The state Calendar falls back to when no React Aria calendar context is above it. It seeds a null value, which is why a standalone Calendar ignores defaultValue. | @/components/application/date-picker/calendar |
| RangeCalendar | The range grid, usable inline: two months from 768px and one below, an optional preset row, and a mobile start / end input pair. | @/components/application/date-picker/range-calendar |
| RangeCalendarContextProvider | The same fallback state for RangeCalendar, seeding a null range — and the same defaultValue caveat. | @/components/application/date-picker/range-calendar |
| CalendarCell | One 40px day cell: the twelve states the sheet draws, the range band, the marker dot, and the focus ring. Also consumed by the application calendar family. | @/components/application/date-picker/cell |
| RangePresetButton | The sidebar preset row. Exported twice, from range-calendar.tsx and from range-preset.tsx — same name, same behavior since 2026-08-23, two modules. | @/components/application/date-picker/range-calendar |
| DateInput | A bare segmented date field with the family’s own chrome. Not the base InputDateBase, and not drawn on the sheet. | @/components/application/date-picker/date-input |
Two presentations the sheet draws and the kit does not export
Type axis of three, not two: beside Single date and Dual dates it draws an Available times type, and a whole second modal set — the same menu over a scrim. Neither has an export here, and both are recorded as Figma-only on the Code tab. Neither is a missing primitive, though: Available times is a Calendar beside a column of base Buttons, built under “Available times” below, and the repo’s own date-picker-modal already composes RangeCalendar into a modal by hand.Built from
Six layers make up the family; three have their own docs page.
Component | Where it’s used here |
|---|---|
| Button | Five nav buttons (sm tertiary at size-8 rounded-md), both triggers (secondary, leading calendar icon), the Cancel and Apply pairs (sm secondary and sm primary), the Calendar’s Today button (sm secondary, slot null), and the mobile preset row (sm link-color). |
| @untitledui/icons | ChevronLeft and ChevronRight on the five nav buttons, and Calendar on both triggers. Nothing else in the family draws an icon. |
| InputDateBase | The base segmented field in three places: the single Calendar’s default row (labelled “Date”), the range footer’s start and end pair, and the mobile inputs row. Its ring, shadow, and segment focus are inherited untouched — including the forced-colors gaps recorded as base-owned. |
The two pickers
A trigger plus a popover each: one date, or a range with two months from 768px and presets from 1024px. Both are live.
What the popovers hold
bg-primary with a ring-secondary_alt hairline and shadow-xl, opening 8px below the trigger at placement="bottom end" — the trigger’s end edge, which is its right in a left-to-right page and its left under dir="rtl": a 24/20 panel holding the calendar, then a 16px footer of two sm buttons in equal columns. The range picker’s adds a 152px preset sidebar from 1024px and turns the footer into a start input, an en dash, an end input, and the same two buttons — stacked two-up below 768px, where the inputs move up into the calendar panel instead.Nine presets, none of them props
today() in date-range-picker.tsx with no prop to add, remove, or rename one. Three of them (Last week, Last month, Last year) are handed down to the calendar’s own preset row, which is what a phone gets instead of the sidebar. Your own list is a composition: RangePresetButton beside a RangeCalendar, as under “Preset rows” below.Trigger sizes
One kit-only prop on each picker, passed straight to the base Button. Nothing inside the popover changes with it.
The sheet draws one trigger size
sm geometry — 12/8 padding, 14px text, a 20px leading calendar icon — which is the kit’s default on both pickers. The other four steps are the base Button’s, recorded as a kit extra rather than a divergence to close.Inline calendars
The same grids without a trigger — what the popovers render inside. An inline calendar is the picker minus the button.
January 2027
Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
27 | 28 | 29 | 30 | 31 | 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 | 1 | 2 | 3 | 4 | 5 | 6 |
January 2027
Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
27 | 28 | 29 | 30 | 31 | 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 | 1 | 2 | 3 | 4 | 5 | 6 |
January 2027
Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
27 | 28 | 29 | 30 | 31 | 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 | 1 | 2 | 3 | 4 | 5 | 6 |
February 2027
Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
31 | 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 1 | 2 | 3 | 4 | 5 | 6 |
Standalone calendars need their state seeded through the context
Calendar wraps itself in CalendarContextProvider whenever no calendar context is above it, and that provider seeds value: null. React Aria merges context under props, and its useControlledState treats anything that is not undefined as controlled — so <Calendar defaultValue={date} /> renders controlled at null and the default never lands. Clicking still selects, because the provider owns the state. Every seeded calendar on this page supplies its own context instead, which is the same shape the kit’s provider uses with a starting value. Inside a picker the question does not arise: the picker is the context.Below 768px, and again at 1024px
RangeCalendar asks the repo’s useBreakpoint("md") hook whether the viewport is 768px or wider: two months if it is, one if not, unless visibleDuration pins the count. Below md it also renders a start / end input pair above the grid and, when it has presets, a three-button preset row. The picker’s sidebar is a CSS breakpoint instead — hidden lg:flex, 1024px — so between 768 and 1024 the popover draws two months and no sidebar. Because the month count is a JS breakpoint, a narrow viewport renders two months for one frame before the hook corrects it.Available times
The sheet’s third menu type has no export. It does not need one — it is a Calendar beside nine base Buttons.
January 2027
Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
27 | 28 | 29 | 30 | 31 | 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 | 1 | 2 | 3 | 4 | 5 | 6 |
Available times
Why this is a recipe and not a component
xs step exactly — rounded-lg px-2.5 py-1.5 text-sm font-semibold — so there is nothing here the kit does not already draw. The sheet’s slim scrollbar and the gradient fading its last row are both here too: the scrollbar through the same [&::-webkit-scrollbar] rules the kit’s own message-chat menu uses, and the fade as a pointer-transparent overlay. One thing is added rather than copied: the sheet draws no chosen slot — all nine rows are identical, and only the taken one is greyed — so the selected row here takes the base Button’s own primary colour. That is a kit convention, not something the sheet states. If your product needs this menu in more than one place, wrap the recipe yourself — the family will not grow an export for it without a sheet symbol the kit owns.What the three symbols are
sm secondary Button both pickers use: “Select date” in text-placeholder while empty (11627:254709), and the date in text-secondary followed by the time in text-tertiary once chosen (11627:254719). Desktop (11626:233546) is the ordinary Calendar beside a 200px panel. Mobile (11627:20734) drops that panel for a base Select with a clock icon, moves the date row up out of the footer, and stacks Cancel / Apply two-up — an order that falls out of the kit rather than being arranged by hand, because those rows go through Calendar’s own children slot.Cell states
All twelve the sheet draws, on one 40px cell, composed from live calendars. Four of them are hover, so hover them.
January 2027
Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
27 | 28 | 29 | 30 | 31 | 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 | 1 | 2 | 3 | 4 | 5 | 6 |
January 2027
Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
27 | 28 | 29 | 30 | 31 | 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 | 1 | 2 | 3 | 4 | 5 | 6 |
September 2026
Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
30 | 31 | 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 1 | 2 | 3 |
January 2027
Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
27 | 28 | 29 | 30 | 31 | 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 | 1 | 2 | 3 | 4 | 5 | 6 |
January 2027
Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
27 | 28 | 29 | 30 | 31 | 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 | 1 | 2 | 3 | 4 | 5 | 6 |
January 2027
Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
27 | 28 | 29 | 30 | 31 | 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 | 1 | 2 | 3 | 4 | 5 | 6 |
January 2027
Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
27 | 28 | 29 | 30 | 31 | 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 | 1 | 2 | 3 | 4 | 5 | 6 |
January 2027
Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
January 2027
Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
27 | 28 | 29 | 30 | 31 | 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 | 1 | 2 | 3 | 4 | 5 | 6 |
September 2026
Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
30 | 31 | 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 1 | 2 | 3 |
Three of these states are new on 2026-08-23
hover:bg-secondary_hover on the numeral) had no branch at all; a selected-and-disabled day in a single calendar dropped to a plain disabled cell instead of keeping its brand fill with the numeral at text-white/50; and adjacent-month days in a range calendar were hidden, where both dual sheet variants draw them at 50% — so showOutOfRangeDates now defaults to true. That last one is a behavior change, not a token change: a range calendar that relied on the old default should pass showOutOfRangeDates={false} explicitly, as the second cell above does.How the two disabled states survive being disabled
isSelected on a disabled cell, and the enabled range band hangs off exactly that — so the band has a second branch of its own, isInRange && isDisabled, or a disabled range would lose its fill entirely. Today’s cell needs no such thing: its bg-secondary branch never asks whether the cell is disabled, so the fill and the marker dot both stay, the numeral at text-secondary/50 and the dot at opacity-50. That cell reads the clock rather than a fixed date, which makes it the second live demo on this page.Focus is keyboard-only and lives on the numeral
outline-focus-ring outline-2 outline-offset-2 on the numeral, and the cell goes z-10 while focused so a neighbor cannot clip it. The sheet draws no focused cell at all — the ring is a kit extra, recorded on the Code tab.Preset rows
RangePresetButton is exported twice under one name. Both lists are live: each button compares its range in context.
They were not always the same
text-white in its base classes, and the range-preset copy painted its selected state bg-active — the neutral ramp — where the sheet draws bg-secondary, the interface ramp its sibling already used. Both are now the same string, and both gained aria-pressed, type="button", a forced-colors branch, text-start, and motion-reduce:transition-none. Import either one; import the same one everywhere.Six symbols, two of them still
bg-secondary fill on “This week” in both lists above. The other four are live rather than drawn, because neither hover: nor focus-visible: can be forced from a prop — hover a row for the bg-primary_hover fill, and tab into a list for the 2px focus ring at a 2px offset. The pressed fill is deliberately quiet: it is the interface ramp on a white panel, which is what the sheet draws too, so it reads as “this is the current window” rather than as a second selection.A preset row sets nothing by itself
value decides only whether the row reads as pressed. The picker’s own rows pass an onClick that calls setValue and moves the focused date so the calendar jumps to the new range; a composed list has to do the same, as the lists above do.The standalone date input
A segmented field in the family’s chrome, with no sheet symbol, no value prop, and no way to name itself.
January 2027
Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
27 | 28 | 29 | 30 | 31 | 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 | 1 | 2 | 3 | 4 | 5 | 6 |
Two different date inputs, one family
DateInput here is the family’s own 44-line wrapper around React Aria’s DateInput; InputDateBase is the base kit’s, and it is what the calendar row and both footers actually use. The family one has no labeling props of its own — React Aria’s DateInputProps carries none, and with no DateField above it the component builds its own state from the locale and renders placeholder segments. The demo above borrows a name from the group around it. For a labelled, hinted, validatable field, use the base InputDate instead.