Date Picker
Headless date primitives for React — segmented input, popover calendar, range and multiple selection. Built on native Intl, timescape, and Floating UI. Zero CSS shipped.
Features
- Single, range, and multiple selection via one Root API (mode).
- Segmented date input powered by timescape — locale-aware order and separators.
- Popover calendar with Floating UI positioning, or inline calendar via composition.
- Full keyboard navigation across segments, day/month/year grids — RTL-aware arrows.
- WAI-ARIA grid and spinbutton semantics built in.
- Unavailable dates (focusable, not selectable) vs disabled dates.
- HiddenInput for native HTML form submission.
- Form props: name, required, invalid, errorMessage.
- Unstyled — style with className, style, and data-* attributes.
- Localization via Intl (locale, weekStartsOn, dir) — no bundled locale data.
- Customizable screen-reader messages via messages prop.
- Time segments via granularity (hour, minute, second) — powered by timescape.
- DatePicker.Presets + useDatePickerActions for shortcut buttons.
- pageBehavior for multi-month grids; allowsNonContiguousRanges for booking ranges.
- Fully controlled mode="multiple" with value sync.
Installation
Install the Date Picker primitive on its own, or use the aggregator @kenos-ui/react package if you already depend on other Kenos primitives.
Install a package
pnpm add @kenos-ui/react-datepickerpnpm add @kenos-ui/reactImport
import { DatePicker } from "@kenos-ui/react-datepicker";import { DatePicker } from "@kenos-ui/react";Examples
Every demo is interactive and keyboard-navigable.
Default
The README pattern — controlled state with useState, segmented Input, Trigger, and the Calendar shorthand. Toggle the code tabs to see unstyled, CSS, Tailwind, and Panda CSS approaches.
Styling works with plain className on each part, then data-* selectors from your Content scope for calendar cells. Keep display: table-cell on td[role=gridcell] — do not use flex/grid on table cells.
Composition
For full control, compose ViewControl, Grid, and Day with a render prop instead of the Calendar shorthand.
Composables
Use only the parts you need — input-only, calendar-only, or the full popover path in Default.
A segmented input without a calendar — omit Content and Trigger. Ideal for birth dates and compact forms.
Controlled
Pass value and onValueChange on DatePicker.Root for fully controlled state.
Selected: —
Date Range
Set mode="range" with dual inputs (index={0} and index={1}). Live hover preview styles days with data-in-range. Press Escape after picking a start date to cancel the pending anchor without closing the popover.
Select a start date, then press Escape to cancel the pending range without closing.
Multiple Dates
mode="multiple" toggles dates on click. Pass value and onValueChange for fully controlled sync — programmatic updates and form resets stay in step with the calendar. Keep the popover open with closeOnSelect={false}.
No dates selected yet
Localization
Segment order, separators, weekday names, and week start derive from Intl. Pass any BCP 47 locale tag.
| Sun | Mon | Tue | Wed | Thu | Fri | Sat |
|---|---|---|---|---|---|---|
| 28 | 29 | 30 | 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 |
Min and Max
Clamp the selectable range with minDate and maxDate on Root. Disabled dates show data-disabled.
| Sun | Mon | Tue | Wed | Thu | Fri | Sat |
|---|---|---|---|---|---|---|
| 28 | 29 | 30 | 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 |
Disabled Dates
Pass disabled={(date) => boolean} to disable specific dates — weekends in this example. Disabled cells receive no focus and use aria-disabled.
| Sun | Mon | Tue | Wed | Thu | Fri | Sat |
|---|---|---|---|---|---|---|
| 28 | 29 | 30 | 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 |
Unavailable Dates
Use unavailable when dates should stay focusable for keyboard users but must not be selected — e.g. booked slots. Cells expose data-unavailable and rich aria-label text.
| Sun | Mon | Tue | Wed | Thu | Fri | Sat |
|---|---|---|---|---|---|---|
| 28 | 29 | 30 | 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 |
Form Native
Add DatePicker.HiddenInput with name on Root for native <form> submission. Pair with required for browser validation.
Invalid State
Set invalid and errorMessage on Root to wire aria-invalid and aria-errormessage on the input group. Style with data-invalid on Root.
RTL
Pass dir="rtl" (or rely on document.dir) to mirror arrow-key navigation in day, month, and year grids.
Presets
Wrap shortcut buttons in DatePicker.Presets inside Content. Use useDatePickerActions() for selectDate, selectRange, selectToday, and related helpers — no manual dispatch wiring.
Time Granularity
Set granularity to "hour", "minute", or "second" to add time segments via timescape. Use hourCycle={12 | 24} to force 12h or 24h display. Calendar selection updates the date portion and preserves the current time.
Jun 15, 2026, 14:30
Non-contiguous Ranges
By default, completing a range fails when unavailable dates sit between start and end. Set allowsNonContiguousRanges on mode="range" to allow booking-style ranges that skip unavailable days (weekends, blocked slots).
Weekends are unavailable but you can still book Mon → Fri with allowsNonContiguousRanges.
Select a range
Page Behavior
pageBehavior="visible" (default) changes the visible month on PageUp/PageDown. pageBehavior="single" moves focus by one month without changing the month header while the target day is still rendered in the grid — useful for future multi-month layouts.
<DatePicker.Root pageBehavior="single">
<DatePicker.Content><DatePicker.Calendar /></DatePicker.Content>
</DatePicker.Root>Year Navigation
In year view, PrevTrigger / NextTrigger page through 12-year windows. minDate / maxDate disable out-of-range years.
| Sun | Mon | Tue | Wed | Thu | Fri | Sat |
|---|---|---|---|---|---|---|
| 28 | 29 | 30 | 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 |
Form Integration
Bind value and onValueChange to your form library. All examples validate with Zod.
Styling
Target data-* on Root and day cells. Root exposes data-open, data-invalid, data-focus-within, and more for field-level states.
Accessibility
Kenos implements the WAI-ARIA date picker and grid patterns. Roles, labels, and focus management are handled for you. Disabled dates are removed from the tab order; unavailable dates remain focusable with descriptive labels.
Roles and state
| Part | Role / attributes |
|---|---|
| DatePicker.Input | role="combobox" (popover path) |
| DatePicker.Trigger | aria-haspopup, aria-expanded, aria-controls |
| DatePicker.Content | role="dialog" |
| DatePicker.Grid | role="grid" |
| DatePicker.Day | role="gridcell" · aria-selected · aria-current="date" |
| Input segments | role="spinbutton" · aria-valuenow · aria-valuetext |
Keyboard support
Behavior depends on which part has focus — trigger, segments, or calendar grid. Try the inline calendar below with arrow keys.
Trigger
| Key | Description |
|---|---|
| Enter / Space | Open popover and move focus into the calendar. |
| Arrow ↓ | Open popover when closed. |
| Escape | Close popover when open; restore focus to trigger. |
Segments (Input)
| Key | Description |
|---|---|
| 0–9 | Type digits; auto-advance when segment is full. |
| Arrow ↑ / ↓ | Increment or decrement focused segment. |
| Arrow ← / → / Tab | Move between month, day, year segments. |
| Alt + Arrow ↓ | Open popover when closed. |
| Alt + Arrow ↑ | Close popover when open. |
| Backspace | Clear focused segment. |
| Escape | Close popover when open. |
Day grid
| Key | Description |
|---|---|
| Arrow keys | Move focus by day (roving tabindex). |
| PageUp / PageDown | Previous / next month (pageBehavior visible). single: move focus ±1 month within grid when the target day is visible. Shift + Page = year. |
| Home / End | First / last day of focused week. |
| Enter / Space | Select focused date. |
| Tab / Shift+Tab | Move between the day grid and header controls (Prev, month/year label, Next). |
| Escape | Close popover. In range mode with pending start, cancels anchor instead. |
Header controls
| Key | Description |
|---|---|
| Tab / Shift+Tab | Move between Prev, ViewTrigger, Next, and the active grid. |
| Enter / Space | Activate Prev/Next (change month or year page) or ViewTrigger (switch day → month → year view). |
Month / Year grids
| Key | Description |
|---|---|
| Arrow keys | Move between months or years. |
| PageUp / PageDown | Year view: previous / next 12-year page. |
| Enter / Space | Select month or year; drill into next view. |
| Escape | Return to previous view or close popover. |
API Reference
Props, data attributes, and keyboard interactions. Import from @kenos-ui/react-datepicker.
Root props
"single"Selection mode. Controls value shape and Input index usage.
Controlled or uncontrolled value. Shape depends on mode.
Popover open state (popover path only).
navigator.languageBCP 47 tag — segment order, separators, labels, week start.
Override first day of week (0 = Sunday).
Clamp selectable range.
Disable dates — no focus, no selection (aria-disabled on cells).
Mark dates unavailable — focusable but not selectable (data-unavailable).
Prevent edits via segments or grid.
Text direction for keyboard navigation. Defaults to document.dir.
Override prev/next/view labels and range screen-reader prompts.
Form field name for HiddenInput.
Native required on HiddenInput.
Sets aria-invalid on the input group.
Linked via aria-errormessage when invalid.
Calendar opens focused on this date.
"day"Lowest time unit in Input segments. day = date only; minute adds hour + minute.
12h vs 24h time segments. Defaults from locale when omitted.
"visible"Day grid PageUp/Down: visible changes month view; single moves focus within the grid when possible.
falseRange mode only. Allow completing a range that spans unavailable dates between start and end.
Focus-within callbacks on Root (popover excluded).
true (single)Close popover after selection. Granular per mode when object.
falseOpt-in focus trap + aria-modal on Content.
Presets component
Shortcut buttons or links. Pair with useDatePickerActions for selectDate, selectRange, etc.
Layout wrapper for preset controls.
useDatePickerActions
Dispatch SELECT_DATE — single or toggle in multiple.
Dispatch SET_RANGE.
Dispatch SET_SELECTED_DATES — multiple mode.
Select today's date.
Select today + n days.
Current Root mode from context.
HiddenInput
Override Root name. Renders nothing if no name is set.
Input props
Range mode: 0 = start segment, 1 = end segment.
Accessible labels for spinbutton segments.
Content props
Render into document.body.
Floating UI positioning.
Keep in DOM when closed (exit animations).
trueFlip when no space.
Data attributes (Root)
| Attribute | On part | Description |
|---|---|---|
| [data-open] | Popover is open. | |
| [data-invalid] | invalid prop is true. | |
| [data-focus-within] | Focus is inside the field (excludes popover). | |
| [data-readonly] | readOnly prop is true. | |
| [data-disabled] | disabled prop is true. | |
| [data-required] | required prop is true. |
Data attributes (Day)
| Attribute | On part | Description |
|---|---|---|
| [data-selected] | Selected day. | |
| [data-today] | Current date. | |
| [data-outside-month] | Adjacent month day. | |
| [data-disabled] | Disabled (min/max or disabled fn) — no focus. | |
| [data-unavailable] | Unavailable callback — focusable, not selectable. | |
| [data-in-range] | Inside range selection. | |
| [data-range-start] / [data-range-end] | Range endpoints. |