# Date Picker
The select's field with a calendar glyph, opening a calendar in the floating panel, with optional presets.

Install: npx opencommerce-ds add date-picker
Requires: theme, calendar, popover, utils
Packages: lucide-react
Exports: DatePicker, DateRangePicker, formatDate, formatRange, DatePickerProps, DateRangePickerProps
Client boundary: yes
Reference: Extrapolated from opencommerce (the select field and the floating panel)


## 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.
- Avoid: A free-text date field without a picker
- Avoid: A picker that stays open after a single date is chosen
- Avoid: Presets that do not match the calendar's selection


## Examples
Example components: npx opencommerce-ds add date-picker calendar label
[Site](https://opencommerce-ds.vercel.app/preview/date-picker/Site) | [Range](https://opencommerce-ds.vercel.app/preview/date-picker/Range) | [Presets](https://opencommerce-ds.vercel.app/preview/date-picker/Presets) | [States](https://opencommerce-ds.vercel.app/preview/date-picker/States)

examples/date-picker.tsx

```tsx
"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

components/ui/date-picker.tsx

```tsx
"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 };

```
