Skip to content
WANCRAFT

F.85Forms & Inputs

Date Picker

A calendar date picker with an ARIA grid, sliding months, single or range selection and a typed ISO field with a native popover.

NewReactMotionTailwindadvanced

Overview

Date Picker is a keyboard-first calendar. The month is a real ARIA grid with one roving tab stop: arrows move by day and week, Page Up / Down by month (Shift for a year), Home / End to the edges of the week, and Enter picks. Months slide in from the direction you travel. Range mode previews the span as you hover or arrow towards the end date. Min, max and a disabled-date callback mark days as unavailable, month and weekday names come from Intl.DateTimeFormat in any locale, and the week can start on Sunday, Monday or Saturday. As a popover it pairs a typed yyyy-mm-dd field with a native popover that returns focus on close. Today is never rendered on the server: it is read after hydration, or passed in.

01Installation

Method 01 · Registry

CLI verified

Uses the shadcn CLI in a project initialised with shadcn init. Installs the file to components/ui/date-picker.tsx and adds its dependencies.

npx shadcn@latest add https://wancraft.my/r/date-picker.json

Method 02 · Copy and paste

  1. 1. Install the dependencies below.
  2. 2. Copy the source from the Code tab into components/ui/date-picker.tsx.
  3. 3. Import it and adjust props.
npm install motion
Dependencies
motion
Peer
react ≥ 18, tailwindcss ≥ 4
Added
13 Oct 2026
Updated
—

02Usage

The default configuration. Change properties above to generate a tailored example in the Usage tab.

example.tsx
import { DatePicker } from "@/components/ui/date-picker";

export function DatePickerExample() {
  return (
    <DatePicker
      label="Check-in"
      min="2026-01-01"
      onValueChange={(date) => console.log(date)}
    />
  );
}

03Props

PropTypeDefaultDescription
mode"single" | "range""single"One date, or a start and end date.
variant"inline" | "popover""inline"Calendar on its own, or a typed field with a native popover calendar.
value / defaultValuestring | null— / nullSingle mode: selected date as "yyyy-mm-dd" (controlled / initial).
onValueChange(value: string | null) => void—Single mode change handler.
range / defaultRange{ start: string | null; end: string | null }—Range mode: selected range (controlled / initial).
onRangeChange(range: DateRange) => void—Range mode change handler; end is null while the second date is pending.
min / maxstring—Earliest and latest selectable dates ("yyyy-mm-dd"). Navigation stops there.
isDateDisabled(iso: string) => boolean—Marks extra dates unavailable. They stay focusable but cannot be picked.
weekStart"monday" | "sunday" | "saturday""monday"First column of the week.
localestring"en-GB"BCP 47 locale for month and weekday names and day labels (Intl.DateTimeFormat).
todaystring—Overrides today. Without it, today is read on the client after hydration and never rendered on the server.
labelstring"Date"Visible label of the field or calendar.
closeOnSelectbooleantruePopover closes after a date (or the range end) is picked.
accentstring"#FF7448"Selection, range band, today dot and focus colour.
disabledbooleanfalseDisable the field and calendar.
namestring—Hidden input(s) with the ISO value; range mode submits start and end under the same name.

04Features

  • 01ARIA grid with roving focus: arrows, Page Up / Down (Shift = year), Home / End, Enter
  • 02Direction-aware month slide with a fixed six-week height
  • 03Single or range selection with a live range preview
  • 04Min / max and a disabled-date callback
  • 05Locale-aware month and weekday names via Intl.DateTimeFormat; Sunday, Monday or Saturday weeks
  • 06Typed yyyy-mm-dd field with validation, and a native popover that follows the field
  • 07Deterministic SSR: dates are UTC day numbers and today is client-only
motion · springinput · clickinput · keyboardinput · focusinput · hover

05Accessibility

  • The month is role="grid" labelled by the month heading, with column headers carrying full weekday names and one tabbable day.
  • Each day button has a full label (e.g. “Tuesday 13 October 2026, range start”), today has aria-current="date", unavailable days are aria-disabled.
  • Selected days set aria-selected on their gridcell; the grid is aria-multiselectable in range mode.
  • Month changes are announced through a polite live heading; the outgoing month is inert and aria-hidden while it slides out.
  • The popover is a native popover with role="dialog": Escape and outside clicks close it, and focus returns to the calendar button. Alt + Arrow Down opens it from the field.
  • With prefers-reduced-motion months switch instantly.

06Limitations

  • The popover uses the native Popover API (Chrome 114+, Safari 17+, Firefox 125+).
  • The text field accepts ISO dates only (yyyy-mm-dd); locale formats are display-only.
  • Gregorian calendar only.