{
  "title": "DatePicker",
  "description": "A trigger + popover composition that lets users pick a single date or a date range through a locale-aware calendar — with Field integration, min/max constraints, and react-hook-form support.",
  "url": "/docs/components/date-picker",
  "since": "1.0.0",
  "tags": [
    "date",
    "picker",
    "range",
    "i18n",
    "a11y",
    "form"
  ],
  "platform": "both",
  "source": "---\ntitle: DatePicker\ndescription: A trigger + popover composition that lets users pick a single date or a date range through a locale-aware calendar — with Field integration, min/max constraints, and react-hook-form support.\nsince: 1.0.0\ntags: [date, picker, range, i18n, a11y, form]\nplatform: both\ncategory: inputs\n---\n\nimport { BundleSize } from '@/components/bundle-size';\nimport { Preview } from '@/components/preview';\nimport { PropsTable } from '@/components/props-table';\n\n<BundleSize component=\"DatePicker\" />\n\n## Overview\n\n`<DatePicker>` and `<DatePicker.Range>` are **trigger + popover** composites. The\ntrigger renders as a TextInput-styled button (border, padding, calendar icon) that\nopens a `<Popover>` containing the existing `<Calendar>` component in single or\nrange mode. There is no typed text input in v1 — calendar selection is the only\ninput method; keyboard-typed date entry is deferred to a future release.\n\nDisplay formatting uses `Intl.DateTimeFormat` with `dateStyle: 'medium'`, so dates\nrender in the user's locale automatically (e.g. \"May 21, 2026\" in `en-US`, \"21\nmai 2026\" in `fr-FR`). The locale comes from the nearest `NoriProvider`'s i18n\ncontext and can be overridden per instance via the `locale` prop.\n\nBoth components connect to `<Field>` via the standard `Field.Control` hook: `id`,\n`aria-labelledby`, `aria-describedby`, `aria-invalid`, and `aria-required` are all\nforwarded from the field envelope to the trigger automatically.\n\n## Basic usage\n\n<Preview name=\"date-picker-basic\" />\n\n```tsx\nimport { useState } from 'react';\nimport type { CalendarDate } from '@internationalized/date';\nimport { DatePicker } from '@nori-ui/core';\n\nexport default function BasicExample() {\n    const [d, setD] = useState<CalendarDate | null>(null);\n    return <DatePicker value={d} onChange={setD} placeholder=\"Pick a date\" />;\n}\n```\n\n## Range\n\n`DatePicker.Range` takes a `{ start: CalendarDate | null; end: CalendarDate | null }`\nvalue. The popover closes automatically once both `start` and `end` are set.\n\n<Preview name=\"date-picker-range\" />\n\n```tsx\nimport { useState } from 'react';\nimport type { CalendarDate } from '@internationalized/date';\nimport { DatePicker } from '@nori-ui/core';\n\ntype DateRangeValue = { start: CalendarDate | null; end: CalendarDate | null };\n\nexport default function RangeExample() {\n    const [r, setR] = useState<DateRangeValue>({ start: null, end: null });\n    return <DatePicker.Range value={r} onChange={setR} placeholder=\"Pick a range\" />;\n}\n```\n\n## Min / Max\n\nUse `minValue` and `maxValue` to constrain the selectable range. Dates outside\nthe bounds are rendered as disabled in the calendar and cannot be selected.\n\n<Preview name=\"date-picker-min-max\" />\n\n```tsx\nimport { useState } from 'react';\nimport { CalendarDate } from '@internationalized/date';\nimport { DatePicker } from '@nori-ui/core';\n\nexport default function MinMaxExample() {\n    const [d, setD] = useState<CalendarDate | null>(null);\n    return (\n        <DatePicker\n            value={d}\n            onChange={setD}\n            minValue={new CalendarDate(2026, 5, 1)}\n            maxValue={new CalendarDate(2026, 5, 31)}\n            placeholder=\"May 2026 only\"\n        />\n    );\n}\n```\n\n## Disabled\n\nSet `disabled` to prevent any interaction. The trigger renders at 60% opacity\nand `aria-disabled` is applied.\n\n```tsx\nimport { DatePicker } from '@nori-ui/core';\n\n<DatePicker disabled placeholder=\"Not available\" />\n```\n\n## With Field\n\nWrap in `<Field>` for a label, description, and error message. The `id`,\n`aria-labelledby`, `aria-describedby`, `aria-required`, and `aria-invalid`\nattributes are injected automatically by `Field.Control`.\n\n```tsx\nimport { useState } from 'react';\nimport type { CalendarDate } from '@internationalized/date';\nimport { DatePicker, Field } from '@nori-ui/core';\n\nexport default function FieldExample() {\n    const [d, setD] = useState<CalendarDate | null>(null);\n    return (\n        <Field label=\"Date of birth\" required>\n            <DatePicker value={d} onChange={setD} placeholder=\"Pick a date\" />\n        </Field>\n    );\n}\n```\n\nWith an error message:\n\n```tsx\nimport { useState } from 'react';\nimport type { CalendarDate } from '@internationalized/date';\nimport { DatePicker, Field } from '@nori-ui/core';\n\nexport default function FieldErrorExample() {\n    const [d, setD] = useState<CalendarDate | null>(null);\n    return (\n        <Field label=\"Date of birth\" required error={d === null ? 'Required' : null}>\n            <DatePicker value={d} onChange={setD} placeholder=\"Pick a date\" />\n        </Field>\n    );\n}\n```\n\n## react-hook-form\n\nUse `<Controller>` to wire DatePicker (or DatePicker.Range) into React Hook Form.\n\n### Single\n\n```tsx\nimport { useState } from 'react';\nimport type { CalendarDate } from '@internationalized/date';\nimport { DatePicker, Field } from '@nori-ui/core';\nimport { useForm, Controller } from 'react-hook-form';\n\ntype FormValues = {\n    startDate: CalendarDate | null;\n};\n\nexport default function RHFSingleExample() {\n    const { control, handleSubmit } = useForm<FormValues>({\n        defaultValues: { startDate: null },\n    });\n\n    const onSubmit = (data: FormValues) => {\n        console.log(data);\n    };\n\n    return (\n        <form onSubmit={handleSubmit(onSubmit)}>\n            <Controller\n                control={control}\n                name=\"startDate\"\n                rules={{ required: 'Start date is required.' }}\n                render={({ field, fieldState }) => (\n                    <Field\n                        label=\"Start date\"\n                        required\n                        error={fieldState.error?.message ?? null}\n                    >\n                        <DatePicker\n                            value={field.value}\n                            onChange={field.onChange}\n                            placeholder=\"Pick a date\"\n                        />\n                    </Field>\n                )}\n            />\n            <button type=\"submit\">Submit</button>\n        </form>\n    );\n}\n```\n\n### Range\n\n```tsx\nimport type { CalendarDate } from '@internationalized/date';\nimport { DatePicker, Field } from '@nori-ui/core';\nimport { useForm, Controller } from 'react-hook-form';\n\ntype DateRangeValue = { start: CalendarDate | null; end: CalendarDate | null };\n\ntype FormValues = {\n    period: DateRangeValue;\n};\n\nexport default function RHFRangeExample() {\n    const { control, handleSubmit } = useForm<FormValues>({\n        defaultValues: { period: { start: null, end: null } },\n    });\n\n    const onSubmit = (data: FormValues) => {\n        console.log(data);\n    };\n\n    return (\n        <form onSubmit={handleSubmit(onSubmit)}>\n            <Controller\n                control={control}\n                name=\"period\"\n                rules={{\n                    validate: (v) =>\n                        (v.start !== null && v.end !== null) || 'Both dates are required.',\n                }}\n                render={({ field, fieldState }) => (\n                    <Field\n                        label=\"Period\"\n                        required\n                        error={fieldState.error?.message ?? null}\n                    >\n                        <DatePicker.Range\n                            value={field.value}\n                            onChange={field.onChange}\n                            placeholder=\"Pick a range\"\n                        />\n                    </Field>\n                )}\n            />\n            <button type=\"submit\">Submit</button>\n        </form>\n    );\n}\n```\n\n`react-hook-form` is not a peer dependency of `@nori-ui/core`. Install it\nseparately:\n\n```bash\nyarn add react-hook-form\n```\n\n## Accessibility\n\nThe trigger renders with `role=\"combobox\"`, `aria-haspopup=\"dialog\"`, and\n`aria-expanded` (toggled as the popover opens/closes). When `disabled`, the\ntrigger also gets `aria-disabled=\"true\"`.\n\nAll `aria-*` props (`aria-labelledby`, `aria-describedby`, `aria-invalid`,\n`aria-required`) are forwarded from the trigger props to the underlying\n`Pressable`, so they are wired automatically when you use `<Field>`. You can\nalso pass them explicitly for custom label associations.\n\nThe calendar inside the popover follows the standard keyboard navigation\ndocumented on the [Calendar](/docs/components/calendar) page. The `Tab` key\nmoves focus into and out of the popover; `Escape` closes it.\n\n## Locale\n\nBy default both components read the active locale from the nearest\n`NoriProvider`'s i18n context. This affects date formatting in the trigger and\nthe calendar's first day of week and weekday names.\n\nTo override per-instance, pass a BCP 47 locale tag:\n\n```tsx\n<DatePicker value={d} onChange={setD} locale=\"de-DE\" />\n```\n\nThe `firstDayOfWeek` prop lets you override the first day of week independently\nof locale (e.g. force Monday in a locale that defaults to Sunday):\n\n```tsx\n<DatePicker value={d} onChange={setD} firstDayOfWeek={1} />\n```\n\n## API reference\n\n### `<DatePicker>`\n\n<PropsTable component=\"DatePicker\" />\n\n### `<DatePicker.Range>`\n\nAccepts the same props as `<DatePicker>` with the following differences:\n\n- `value` — `{ start: CalendarDate | null; end: CalendarDate | null }`.\n- `defaultValue` — uncontrolled initial range; same shape as `value`.\n- `onChange` — fires with the updated range object on each selection.\n\nAll other props (`disabled`, `placeholder`, `locale`, `minValue`, `maxValue`,\n`firstDayOfWeek`, `isDateUnavailable`, `id`, `aria-labelledby`,\n`aria-describedby`, `aria-invalid`, `aria-required`) behave identically to\n`<DatePicker>`.\n"
}
