Components
Date Picker
The select's field with a calendar glyph, opening a calendar in the floating panel, with optional presets.
npx -y github:anwarlemu/opencommerce-brand-cli#release add date-picker --surface siteRun from the target React 19.2+ app root with Tailwind CSS 4 and the @/* source alias. Source and examples as JSON.
Usage
Use for a date in a form or filter (a key's expiry, an order's date range). Use DateRangePicker for ranges; give presets for common choices (Today, Last 7 days). Give it a name for FormData; the value posts as YYYY-MM-DD.
- A free-text date field without a picker
- A picker that stays open after a single date is chosen
- Presets that do not match the calendar's selection
Exports: DatePicker, DateRangePicker, formatDate, formatRange, DatePickerProps, DateRangePickerProps. This component includes a client boundary. Keep event handlers and state in a client component.
Example
"use client";
import * as React from "react";
import { type DateRange } from "@/components/ui/calendar";
import { DatePicker, DateRangePicker } from "@/components/ui/date-picker";
import { Label } from "@/components/ui/label";
const TODAY = new Date(2026, 9, 26);
const days = (n: number) => new Date(TODAY.getFullYear(), TODAY.getMonth(), TODAY.getDate() + n);
export function Site() {
const [date, setDate] = React.useState<Date | undefined>(days(30));
return (
<div className="grid w-full max-w-72 gap-1.5">
<Label htmlFor="key-expiry">Key expires</Label>
<DatePicker id="key-expiry" name="expires" value={date} onValueChange={setDate} calendar={{ today: TODAY, disabled: { before: TODAY } }} />
</div>
);
}
export function Range() {
const [range, setRange] = React.useState<DateRange | undefined>({ from: days(-6), to: TODAY });
return (
<div className="grid w-full max-w-80 gap-1.5">
<Label htmlFor="orders-range">Orders placed</Label>
<DateRangePicker id="orders-range" name="placed" value={range} onValueChange={setRange} calendar={{ today: TODAY }} />
</div>
);
}
export function Presets() {
const [range, setRange] = React.useState<DateRange | undefined>();
return (
<div className="grid w-full max-w-80 gap-1.5">
<Label htmlFor="report-range">Report</Label>
<DateRangePicker
id="report-range"
value={range}
onValueChange={setRange}
calendar={{ today: TODAY }}
presets={[
{ label: "Today", value: { from: TODAY, to: TODAY } },
{ label: "Last 7 days", value: { from: days(-6), to: TODAY } },
{ label: "Last 30 days", value: { from: days(-29), to: TODAY } },
{ label: "This month", value: { from: new Date(2026, 9, 1), to: TODAY } },
]}
/>
</div>
);
}
export function States() {
return (
<div className="grid w-full max-w-72 gap-3">
<DatePicker aria-label="Delivery date" placeholder="Pick a delivery date" aria-invalid />
<p className="-mt-2 font-sans text-body-sm text-danger">Choose a delivery date.</p>
<DatePicker aria-label="Locked date" defaultValue={TODAY} disabled />
</div>
);
}Source
"use client";
import * as React from "react";
import { CalendarDays } from "lucide-react";
import { Calendar, type CalendarProps, type DateRange } from "@/components/ui/calendar";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { cn } from "@/lib/utils";
// A date field (extrapolated from the brand's select and floating panel). The trigger is the select's field: 36px, 8px
// corners, white on the 16 percent ink edge, a 14px calendar glyph at 64 percent ink, the date in 14px Inter (the
// placeholder faint) and the select's drawn chevron at the right; an open picker turns the edge blue. The calendar opens
// under it in the popover's white panel, growing from the trigger. Presets sit in a column of 34px rows at the panel's
// left, the chosen one on the pale blue. A single date closes the panel when picked; a range closes once both
// ends are set. Opening puts focus on the chosen day (or today), so the arrow keys work at once.
const TRIGGER =
"flex h-9 w-full min-w-0 cursor-pointer items-center gap-2.5 rounded-control border border-hairline bg-surface px-3 font-sans text-body-sm leading-body whitespace-nowrap text-fg outline-none transition-[border-color] duration-160 ease-out hover:border-line-field-hover focus-visible:border-line-brand data-[state=open]:border-line-brand disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-danger data-[empty=true]:text-fg-faint motion-reduce:transition-none";
type Preset<T> = { label: string; value: T };
function formatDate(date: Date, locale?: string, options: Intl.DateTimeFormatOptions = { month: "short", day: "numeric", year: "numeric" }) {
return new Intl.DateTimeFormat(locale, options).format(date);
}
function formatRange(range: DateRange | undefined, locale?: string) {
if (!range?.from) return undefined;
if (!range.to) return `${formatDate(range.from, locale)} -`;
const sameYear = range.from.getFullYear() === range.to.getFullYear();
return `${formatDate(range.from, locale, sameYear ? { month: "short", day: "numeric" } : undefined)} - ${formatDate(range.to, locale)}`;
}
function DatePickerTrigger({ empty, label, className, children, ...props }: React.ComponentProps<"button"> & { empty: boolean; label?: string }) {
return (
<PopoverTrigger asChild>
<button type="button" data-slot="date-picker-trigger" data-empty={empty} aria-label={label} className={cn(TRIGGER, className)} {...props}>
<CalendarDays aria-hidden strokeWidth={1.5} className="size-3.75 shrink-0 text-fg-muted" />
<span className="min-w-0 flex-1 truncate text-left">{children}</span>
<svg aria-hidden viewBox="0 0 10 6" className="h-1.5 w-2.5 shrink-0 fill-none stroke-current text-fg-muted [stroke-linecap:round] [stroke-linejoin:round] [stroke-width:1.5]">
<path d="M1 1l4 4 4-4" />
</svg>
</button>
</PopoverTrigger>
);
}
function Presets<T>({ presets, isActive, onPick }: { presets: Preset<T>[]; isActive: (p: Preset<T>) => boolean; onPick: (value: T) => void }) {
return (
<div data-slot="date-picker-presets" role="group" aria-label="Presets" className="flex gap-0.5 overflow-x-auto border-line p-1 max-sm:border-b sm:w-40 sm:flex-col sm:border-r">
{presets.map((p) => (
<button
key={p.label}
type="button"
aria-pressed={isActive(p)}
onClick={() => onPick(p.value)}
className="flex h-8.5 shrink-0 items-center rounded-menu px-2.5 text-left font-sans text-body-sm leading-tight whitespace-nowrap text-fg-muted outline-none transition-[background-color,color] duration-160 ease-out hover:bg-wash-hover hover:text-fg focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-solid focus-visible:outline-focus-ring aria-pressed:bg-blue-5 aria-pressed:text-fg-brand motion-reduce:transition-none"
>
{p.label}
</button>
))}
</div>
);
}
const sameDay = (a?: Date, b?: Date) => !!a && !!b && a.toDateString() === b.toDateString();
type CalendarPassThrough = Omit<CalendarProps, "mode" | "selected" | "onSelect" | "required" | "min" | "max">;
type DatePickerProps = {
value?: Date;
defaultValue?: Date;
onValueChange?: (date: Date | undefined) => void;
placeholder?: string;
disabled?: boolean;
/** Shortcuts beside the calendar (Today, In a week). */
presets?: Preset<Date>[];
/** Props for the calendar inside (disabled days, the first day of the week, today). */
calendar?: CalendarPassThrough;
locale?: string;
id?: string;
name?: string;
className?: string;
"aria-invalid"?: boolean;
"aria-label"?: string;
};
function DatePicker({ value: valueProp, defaultValue, onValueChange, placeholder = "Pick a date", disabled, presets, calendar, locale, id, name, className, ...aria }: DatePickerProps) {
const [open, setOpen] = React.useState(false);
const [inner, setInner] = React.useState(defaultValue);
const value = valueProp !== undefined ? valueProp : inner;
const set = (d: Date | undefined) => {
if (valueProp === undefined) setInner(d);
onValueChange?.(d);
};
return (
<Popover open={open} onOpenChange={setOpen}>
<DatePickerTrigger id={id} empty={!value} disabled={disabled} className={className} aria-invalid={aria["aria-invalid"]} label={aria["aria-label"]}>
{value ? formatDate(value, locale) : placeholder}
</DatePickerTrigger>
{name && <input type="hidden" name={name} value={value ? value.toISOString().slice(0, 10) : ""} />}
<PopoverContent data-slot="date-picker-content" align="start" onOpenAutoFocus={(e) => e.preventDefault()} className="w-auto gap-0 overflow-hidden p-0">
<div className="flex flex-col sm:flex-row">
{presets && (
<Presets
presets={presets}
isActive={(p) => sameDay(p.value, value)}
onPick={(d) => {
set(d);
setOpen(false);
}}
/>
)}
<Calendar
{...calendar}
mode="single"
selected={value}
defaultMonth={value ?? calendar?.defaultMonth}
locale={locale}
autoFocus
onSelect={(d) => {
set(d);
if (d) setOpen(false);
}}
/>
</div>
</PopoverContent>
</Popover>
);
}
type DateRangePickerProps = Omit<DatePickerProps, "value" | "defaultValue" | "onValueChange" | "presets"> & {
value?: DateRange;
defaultValue?: DateRange;
onValueChange?: (range: DateRange | undefined) => void;
presets?: Preset<DateRange>[];
/** How many months show side by side (two by default; one on a phone). */
numberOfMonths?: number;
};
function DateRangePicker({ value: valueProp, defaultValue, onValueChange, placeholder = "Pick a date range", disabled, presets, calendar, locale, numberOfMonths = 2, id, name, className, ...aria }: DateRangePickerProps) {
const [open, setOpen] = React.useState(false);
const [inner, setInner] = React.useState(defaultValue);
const value = valueProp !== undefined ? valueProp : inner;
const set = (r: DateRange | undefined) => {
if (valueProp === undefined) setInner(r);
onValueChange?.(r);
};
const [narrow, setNarrow] = React.useState(false);
React.useEffect(() => {
const query = window.matchMedia("(max-width: 39.99rem)");
const update = () => setNarrow(query.matches);
update();
query.addEventListener("change", update);
return () => query.removeEventListener("change", update);
}, []);
const label = formatRange(value, locale);
return (
<Popover open={open} onOpenChange={setOpen}>
<DatePickerTrigger id={id} empty={!label} disabled={disabled} className={className} aria-invalid={aria["aria-invalid"]} label={aria["aria-label"]}>
{label ?? placeholder}
</DatePickerTrigger>
{name && (
<>
<input type="hidden" name={`${name}From`} value={value?.from ? value.from.toISOString().slice(0, 10) : ""} />
<input type="hidden" name={`${name}To`} value={value?.to ? value.to.toISOString().slice(0, 10) : ""} />
</>
)}
<PopoverContent data-slot="date-range-picker-content" align="start" onOpenAutoFocus={(e) => e.preventDefault()} className="w-auto max-w-[calc(100vw-2rem)] gap-0 overflow-hidden p-0">
<div className="flex flex-col sm:flex-row">
{presets && (
<Presets
presets={presets}
isActive={(p) => sameDay(p.value.from, value?.from) && sameDay(p.value.to, value?.to)}
onPick={(r) => {
set(r);
setOpen(false);
}}
/>
)}
<Calendar
{...calendar}
mode="range"
selected={value}
defaultMonth={value?.from ?? calendar?.defaultMonth}
numberOfMonths={narrow ? 1 : numberOfMonths}
locale={locale}
autoFocus
onSelect={(r) => {
set(r);
if (r?.from && r.to) setOpen(false);
}}
/>
</div>
</PopoverContent>
</Popover>
);
}
export { DatePicker, DateRangePicker, formatDate, formatRange };
export type { DatePickerProps, DateRangePickerProps };Reference: Extrapolated from opencommerce (the select field and the floating panel).