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.
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-monoreponpx shadcn@latest add ourostack/ouro-ui/ouro-base#v1.2.13Then 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.13Or, with the @ouro namespace set up:
npx shadcn@latest add @ouro/ouro-base @ouro/calendarIts 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.13import { 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
DatePickerinstead 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 withrangea{ 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
rangethe{ 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
rangethe 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). Withrangeit is the picked{ start, end }instead (nullfor none), and passing{ start, end }withoutrangeis a type error. Pass it withonValueChangeto 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 usesisDateDisabled:<Calendar isDateDisabled={(date) => booked.has(date)}>.maxDate: Not a prop. Ouro UI usesmax:<Calendar max="2026-12-31">.minDate: Not a prop. Ouro UI usesmin:<Calendar min="2026-10-11">.mode: Not a prop. Ouro UI usesrangefor a range; one day is the default:<Calendar range>.onChange: Not a prop. Ouro UI usesonValueChange, which gets the ISO date:<Calendar onValueChange={setDate}>.onSelect: Not a prop. Ouro UI usesonValueChange:<Calendar value={date} onValueChange={setDate}>.selected: Not a prop. Ouro UI usesvalue:<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
- 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)