Calendar
A month grid for choosing a day or a span of days.
| Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday |
|---|---|---|---|---|---|---|
Fri Aug 14 2026
Installation
npx shadcn@latest add https://qure-ui.qure.ai/r/calendar.jsonUsage
import { Calendar, type DateRange } from "@/components/ui/calendar"const [value, setValue] = React.useState<Date | null>(new Date())
<Calendar value={value} onValueChange={(v) => setValue(v as Date | null)} />Uncontrolled works too — pass defaultValue and read the date from onValueChange.
No date library. This uses Date for arithmetic and Intl for names. A component you
copy into your repository should not decide that your project now depends on date-fns, and
month grids need very little that the platform does not already have.
A range
Pass mode="range" and the value becomes { from, to }. The first click sets from, the
second closes the range, and a third starts again. Clicking before the start swaps the ends
rather than refusing.
| Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday |
|---|---|---|---|---|---|---|
Fri Aug 14 2026 → Thu Aug 20 2026
While a range is open, hovering previews where it would end. The preview is deliberately
invisible to assistive technology — only the two dates actually chosen carry aria-selected,
because a hover is not a choice.
Limiting what can be picked
| Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday |
|---|---|---|---|---|---|---|
Weekdays only, within 60 days.
min and max bound the range; disabled takes a predicate for everything else. All three
produce a genuinely disabled button, so keyboard users cannot land on a day the mouse cannot
reach either.
<Calendar
min={today}
max={addDays(today, 60)}
disabled={(date) => date.getDay() === 0 || date.getDay() === 6}
/>Keyboard
The grid is one tab stop, not forty-two — only the focused day is tabbable, so tabbing past a calendar takes a single press.
| Key | Moves |
|---|---|
| ← → | a day |
| ↑ ↓ | a week |
| Home End | to the start or end of the week |
| Page Up Page Down | a month |
| Shift + Page Up / Page Down | a year |
| Enter Space | choose the focused day |
Moving off the edge of the month turns the page, and focus follows. Focus is kept separate from selection throughout, so a reader can move across the grid reading dates without choosing one.
Page Up from the 31st lands on the 30th of the month before, not on the 1st of the month
before that. Naive month arithmetic overflows — new Date(2025, 2, 31) for February is the
3rd of March — and a date picker that skips a month when you use it on the wrong day is a
bug nobody reports and everybody works around.
Localisation
Weekday and month names come from Intl and follow the browser unless you pass locale. The
week starts on Monday; pass weekStartsOn={0} for Sunday.
<Calendar locale="en-GB" weekStartsOn={1} />Each day's accessible name is the fully written date — "Monday 12 May 2025" — rather than the number in the cell, which on its own tells a screen reader user nothing about which month or weekday they have landed on.
Accessibility
The grid is a real role="grid" with column headers, so it is navigable as a table. The
three-letter weekday heading is shown and the full name is read.
Today carries aria-current="date" and is marked with a ring rather than a fill, so it cannot
be mistaken for the selection. The month caption is aria-live="polite" — the month changing
is worth hearing after the day you arrowed onto, not instead of it.
API Reference
Prop
Type