Forms

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.

SunMonTueWedThuFriSat

Install

Once per app, with Tailwind CSS v4 and an @/* path alias (Ouro HX is built on Base UI, so --base base; skip -t in an existing app):

npx shadcn@latest init --base base --preset nova --yes --no-monorepo
npx shadcn@latest add ourostack/ouro-ui/ouro-base#v1.2.13

Then the component. From v0.8.0 it installs only the helpers it imports and never touches your stylesheet (Installing):

npx shadcn@latest add ourostack/ouro-ui/calendar#v1.2.13

Or, with the @ouro namespace set up:

npx shadcn@latest add @ouro/ouro-base @ouro/calendar

Its tests, stories and Playwright spec are optional. They need Vitest 4.1.11 or later, and from v0.8.0 never change a version your app has (Testing):

npx shadcn@latest add ourostack/ouro-ui/calendar#v1.2.13 ourostack/ouro-ui/calendar-tests#v1.2.13
import { Calendar } from "@/components/ouro/calendar/calendar";

Paths use shadcn's default @/ alias; the CLI rewrites them to yours.

Example

<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>.

className
Type: string
Layout only: margins and grid placement (mt-4). Appearance classes warn in development; the calendar takes its colors from your theme.
defaultMonth
Type: string
The month shown first, as any ISO date in it. Without it, the selected date's month, or today's.
defaultValue
Type: (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
Type: ((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)
Type: 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
Type: string
Default: "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
Type: string
The latest day the user can pick, as an ISO date: max="2026-12-31". Later days are unavailable.
min
Type: string
The earliest day the user can pick, as an ISO date: min="2026-10-11". Earlier days are unavailable.
nextMonthLabel
Type: string
Default: "Next month"
The next month button's label. Translate it with your app.
onValueChange
Type: ((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
Type: string
Default: "Previous month"
The previous month button's label. Translate it with your app.
range
Type: 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
Type: Ref<HTMLDivElement>
The calendar's root element.
value
Type: (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.

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

Claim sources