<!-- Generated by scripts/gen-docs.mts from the JSDoc in registry/. Do not edit; run `pnpm run docs`. -->

# Calendar

A month grid to pick a day or a range of days, with the arrow keys or a press. It follows the ARIA grid pattern: one Tab stop, arrows move by day and week, Home and End to the week's ends, Page Up and Page Down by month (with Shift, by year), and Enter or Space picks. Month and weekday names, digits and the first day of the week come from `locale` through `Intl.DateTimeFormat`. Values are ISO dates (`"2026-10-11"`), the format a date input submits.

```tsx
import { Calendar } from "@/components/ouro/calendar/calendar";
```

## Example

```tsx
<Calendar label="Delivery date" value={date} onValueChange={setDate} min="2026-10-11" />
<Calendar label="Trip" range value={trip} onValueChange={setTrip} locale="en-GB" />
<Calendar label="Booking" isDateDisabled={(date) => booked.has(date)} />
```

## Use instead when

- Use `DatePicker` instead for a date in a form: it is a labeled field that opens this calendar on desktops and the phone's own picker on touch screens.

## Props

Type: `CalendarProps<Range>`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` |  | Layout only: margins and grid placement (`mt-4`). Appearance classes warn in development; the calendar takes its colors from your theme. |
| `defaultMonth` | `string` |  | The month shown first, as any ISO date in it. Without it, the selected date's month, or today's. |
| `defaultValue` | `(Range extends true ? DateRange \| null : string)` |  | What is picked at first, when you do not control `value`: an ISO date, or with `range` a `{ start, end }`. |
| `isDateDisabled` | `((date: string) => boolean)` |  | Marks single days unavailable: `isDateDisabled={(date) => booked.has(date)}`. It gets each day as an ISO date. Unavailable days stay in the arrow-key path, so screen readers still hear them, but cannot be picked. |
| `label` (required) | `string` |  | What the dates are for, read by screen readers as the calendar's name: `"Delivery date"`. Required, because a grid of numbers says nothing on its own. It is not shown; put a visible heading beside the calendar if the page needs one. |
| `locale` | `string` | `"en-US"` | The BCP 47 locale for the month and weekday names, the digits and the first day of the week: `"en-GB"` starts weeks on Monday, `"en-US"` on Sunday. Pass your app's locale; the default is the same on the server and in the browser, so hydration matches. |
| `max` | `string` |  | The latest day the user can pick, as an ISO date: `max="2026-12-31"`. Later days are unavailable. |
| `min` | `string` |  | The earliest day the user can pick, as an ISO date: `min="2026-10-11"`. Earlier days are unavailable. |
| `nextMonthLabel` | `string` | `"Next month"` | The next month button's label. Translate it with your app. |
| `onValueChange` | `((value: CalendarValue<Range>) => void)` |  | Called when the user picks: with the ISO date, or with `range` the `{ start, end }` in order once both ends are picked. |
| `previousMonthLabel` | `string` | `"Previous month"` | The previous month button's label. Translate it with your app. |
| `range` | `Range` |  | Picks a range: the first press sets the start, the second the end, and Escape drops a start. A range never spans an unavailable day: the preview and the end stop at the last available day before it, so a stay cannot include a booked night. Without `range` the calendar picks one day. |
| `ref` | `Ref<HTMLDivElement>` |  | The calendar's root element. |
| `value` | `(Range extends true ? DateRange \| null : string)` |  | The picked day as an ISO date (`"2026-10-11"`, `""` for none). With `range` it is the picked `{ start, end }` instead (`null` for none), and passing `{ start, end }` without `range` is a type error. Pass it with `onValueChange` to control the calendar. |

## Don't use

These are not props. Passing one fails the type check with the fix in the error, and in development a console message names the fix.

- `disabled`: Not a prop. Ouro UI uses `isDateDisabled`: `<Calendar isDateDisabled={(date) => booked.has(date)}>`.
- `maxDate`: Not a prop. Ouro UI uses `max`: `<Calendar max="2026-12-31">`.
- `minDate`: Not a prop. Ouro UI uses `min`: `<Calendar min="2026-10-11">`.
- `mode`: Not a prop. Ouro UI uses `range` for a range; one day is the default: `<Calendar range>`.
- `onChange`: Not a prop. Ouro UI uses `onValueChange`, which gets the ISO date: `<Calendar onValueChange={setDate}>`.
- `onSelect`: Not a prop. Ouro UI uses `onValueChange`: `<Calendar value={date} onValueChange={setDate}>`.
- `selected`: Not a prop. Ouro UI uses `value`: `<Calendar value={date} onValueChange={setDate}>`.
- `weekStartsOn`: Not a prop. The locale sets the first day of the week: `<Calendar locale="en-GB">`, or `"en-US-u-fw-mon"`.

## `className` is for layout only

Pass margins, width and grid placement (`mt-4`, `w-full`, `col-span-2`). Your classes always win over Ouro UI's: Calendar merges `className` last with `cn` (tailwind-merge), so a class that conflicts with its own replaces it.[^layout-classes]

Appearance comes from props, not classes. In development, a class that sets appearance logs one warning per component instance, naming the prop to use instead. A class sets appearance when, after its variants (`hover:`, `sm:`) and any `!` are removed, it starts with `bg-`, `text-`, `border-`, `rounded-`, `shadow-`, `font-`, `ring-`, `from-`, `via-`, `to-`, `fill-` or `stroke-`, or is `border`, `rounded`, `shadow` or `ring`, or has a `dark:` variant. Text wrapping, overflow and table layout classes (`text-wrap`, `text-ellipsis`, `border-collapse`) are layout and do not warn.

Alignment classes (`text-center`) warn too: set alignment on a parent element.

## Types

### `CalendarValue`

What a Calendar picks: an ISO date, or with `range` a `DateRange`.

```ts
type CalendarValue<Range extends boolean> = Range extends true ? DateRange : string;
```

[^layout-classes]: test:registry/ouro/components/ouro/calendar/calendar.test.tsx::Calendar > lets app layout classes win on the root, and gives days 36px with a fine pointer verify:testExists(registry/ouro/components/ouro/calendar/calendar.test.tsx::Calendar > lets app layout classes win on the root, and gives days 36px with a fine pointer)
