Inputs
Date picker
A calendar for picking a single date or a range, inline or in a popover under a field-style trigger. The month grid is fully keyboard navigable, ranges preview as you hover, and min, max and custom rules disable dates that can't be booked.
October 2026
| Mo | Tu | We | Th | Fr | Sa | Su |
|---|---|---|---|---|---|---|
November 2026
| Mo | Tu | We | Th | Fr | Sa | Su |
|---|---|---|---|---|---|---|
Oct 21 – Oct 24 · 3 nights
"use client"; import { useState } from "react";import { Button } from "@/components/ui/button";import { Calendar, type DateRange } from "@/components/ui/date-picker"; const today = new Date(2026, 9, 19);const fmt = new Intl.DateTimeFormat("en-US", { month: "short", day: "numeric" }); export function BookingRangeDemo() { const [range, setRange] = useState<DateRange>({ from: new Date(2026, 9, 21), to: new Date(2026, 9, 24) }); const nights = range.from && range.to ? Math.round((range.to.getTime() - range.from.getTime()) / 86_400_000) : 0; return ( <div className="mx-auto w-fit max-w-full rounded-ui-lg border border-line bg-surface shadow-ui"> <div className="p-4"> <Calendar mode="range" numberOfMonths={2} today={today} min={today} value={range} onValueChange={setRange} /> </div> <div className="flex flex-wrap items-center justify-between gap-3 border-t border-line px-4 py-3"> <p className="text-[13px] leading-5 text-muted"> {range.from && range.to ? ( <> <span className="font-medium text-fg tabular"> {fmt.format(range.from)} – {fmt.format(range.to)} </span>{" "} · {nights} {nights === 1 ? "night" : "nights"} </> ) : range.from ? ( "Now pick a check-out date" ) : ( "Pick a check-in date" )} </p> <div className="flex gap-2"> <Button size="sm" variant="ghost" onClick={() => setRange({ from: null, to: null })}> Clear </Button> <Button size="sm" variant="primary" disabled={!nights}> Book studio </Button> </div> </div> </div> );}Usage
import { DatePicker } from "@/components/ui/date-picker";import { Field } from "@/components/ui/field"; export function Example() { return ( <Field label="Due date"> <DatePicker defaultValue={new Date(2026, 9, 30)} /> </Field> );}Examples
Date range
Two months side by side (stacked on phones). The first click sets the start, the second the end; hover previews the range.
October 2026
| Mo | Tu | We | Th | Fr | Sa | Su |
|---|---|---|---|---|---|---|
November 2026
| Mo | Tu | We | Th | Fr | Sa | Su |
|---|---|---|---|---|---|---|
Oct 21 – Oct 24 · 3 nights
"use client"; import { useState } from "react";import { Button } from "@/components/ui/button";import { Calendar, type DateRange } from "@/components/ui/date-picker"; const today = new Date(2026, 9, 19);const fmt = new Intl.DateTimeFormat("en-US", { month: "short", day: "numeric" }); export function BookingRangeDemo() { const [range, setRange] = useState<DateRange>({ from: new Date(2026, 9, 21), to: new Date(2026, 9, 24) }); const nights = range.from && range.to ? Math.round((range.to.getTime() - range.from.getTime()) / 86_400_000) : 0; return ( <div className="mx-auto w-fit max-w-full rounded-ui-lg border border-line bg-surface shadow-ui"> <div className="p-4"> <Calendar mode="range" numberOfMonths={2} today={today} min={today} value={range} onValueChange={setRange} /> </div> <div className="flex flex-wrap items-center justify-between gap-3 border-t border-line px-4 py-3"> <p className="text-[13px] leading-5 text-muted"> {range.from && range.to ? ( <> <span className="font-medium text-fg tabular"> {fmt.format(range.from)} – {fmt.format(range.to)} </span>{" "} · {nights} {nights === 1 ? "night" : "nights"} </> ) : range.from ? ( "Now pick a check-out date" ) : ( "Pick a check-in date" )} </p> <div className="flex gap-2"> <Button size="sm" variant="ghost" onClick={() => setRange({ from: null, to: null })}> Clear </Button> <Button size="sm" variant="primary" disabled={!nights}> Book studio </Button> </div> </div> </div> );}Popover trigger
DatePicker looks like the other fields and opens the calendar under it. It closes on select, Escape or a click outside.
Clients see this on the invoice.
"use client"; import { DatePicker } from "@/components/ui/date-picker";import { Field } from "@/components/ui/field"; const today = new Date(2026, 9, 19); export function PopoverPickerDemo() { return ( <div className="mx-auto grid h-[600px] w-full max-w-xl content-start gap-5 sm:h-[420px] sm:grid-cols-2"> <Field label="Due date" hint="Clients see this on the invoice."> <DatePicker today={today} defaultValue={new Date(2026, 9, 30)} /> </Field> <Field label="Reporting period"> <DatePicker mode="range" today={today} align="end" placeholder="Pick a period" /> </Field> </div> );}Single date
Today is marked with a dot. Use the arrow keys to move, Page Up and Page Down to change month.
October 2026
| Mo | Tu | We | Th | Fr | Sa | Su |
|---|---|---|---|---|---|---|
"use client"; import { useState } from "react";import { Calendar } from "@/components/ui/date-picker"; const today = new Date(2026, 9, 19); export function SingleCalendarDemo() { const [date, setDate] = useState<Date | null>(new Date(2026, 9, 23)); return ( <div className="mx-auto w-fit rounded-ui-lg border border-line bg-surface p-4 shadow-ui"> <Calendar today={today} value={date} onValueChange={setDate} /> </div> );}Disabled dates
min and max bound the selection; isDateDisabled rules out anything else, here weekends.
October 2026
| Mo | Tu | We | Th | Fr | Sa | Su |
|---|---|---|---|---|---|---|
"use client"; import { Calendar } from "@/components/ui/date-picker"; const today = new Date(2026, 9, 19);const isWeekend = (d: Date) => d.getDay() === 0 || d.getDay() === 6; export function DisabledDatesDemo() { return ( <div className="mx-auto w-fit rounded-ui-lg border border-line bg-surface p-4 shadow-ui"> <Calendar today={today} min={today} max={new Date(2026, 9, 31)} isDateDisabled={isWeekend} defaultValue={new Date(2026, 9, 22)} /> </div> );}Props
DatePicker
mode"single" | "range"default: "single"Pick one date or a from–to range.value / defaultValueDate | null · DateRangedefault: —Controlled or initial value. A range is { from, to }.onValueChange(value) => voiddefault: —Called with the new date or range.placeholderstringdefault: "Pick a date"Trigger text while empty.size"sm" | "md" | "lg"default: "md"Trigger height 32 / 36 / 40px.align"start" | "end"default: "start"Popover edge aligned with the trigger.formatOptionsIntl.DateTimeFormatOptionsdefault: { month: "short", day: "numeric", year: "numeric" }How the trigger shows the value.invalid / disabledbooleandefault: —Set automatically inside a Field with error or disabled....calendar propsmin, max, isDateDisabled, weekStartsOn, numberOfMonths, today, localedefault: —Passed to the Calendar inside.
Calendar
mode"single" | "range"default: "single"Selection mode.value / defaultValueDate | null · DateRangedefault: —Controlled or initial selection.onValueChange(value) => voiddefault: —Called on every pick.numberOfMonths1 | 2default: 1Months shown side by side.defaultMonthDatedefault: —First visible month. Defaults to the selection, then today.weekStartsOn0 | 1default: 1Sunday (0) or Monday (1).min / maxDatedefault: —Earliest and latest selectable date.isDateDisabled(date: Date) => booleandefault: —Disable any other dates.todayDatedefault: —Override today (read on the client by default).localestringdefault: "en-US"Month and weekday names via Intl.
Accessibility
- Each month is a role="grid" labelled by its caption; the caption is a polite live region, so month changes are announced.
- One day is in the tab order. Arrow keys move by day and week, Home and End to the start and end of the week, Page Up and Page Down by month, with Shift by year.
- Day buttons have full labels (“Friday, October 23, 2026”), today has aria-current="date", and selected cells have aria-selected.
- Disabled dates stay focusable with aria-disabled, so keyboard users can move past them, but they can't be picked.
- The trigger has aria-haspopup="dialog" and aria-expanded; the popover is a labelled dialog, Escape closes it and focus returns to the trigger.