Calendar
A date-field calendar built on React DayPicker — single, multiple and range selection, month and year dropdowns, week numbers and custom cell sizes.
- Status
- Since
0.10.0- Accessibility pattern
- APG date grid via React DayPicker
Last updated
Install
Configure registry credentials first using the Quickstart. Before copying this item or any transitive registry dependency, verify the signed manifest and save the exact bytes for Calendar before installation. Retain the digest printed by this command.
pnpm exec vegastack-design verify calendarInstall with the pinned shadcn CLI. Bare shadcn does not verify VegaStack signature or integrity.
pnpm dlx shadcn@4.21.0 add @vegastack/calendarRun the exact offline --post-write command printed by the preflight, using its saved item path and independently retained --expected-integrity digest. Use the saved preflight for every transitive registry dependency too; stop on any mismatch before using the components.
The same command installs the registry items it composes: @vegastack/button.
It also adds the sanctioned engine to your package.json: react-day-picker (calendar interaction/rendering engine).
Usage
import { Calendar } from "@/components/ui/calendar";
const [date, setDate] = React.useState<Date | undefined>(new Date());
<Calendar
mode="single"
selected={date}
onSelect={setDate}
className="rounded-lg border"
/>;Anatomy
Examples
About
Calendar is React DayPicker with our chrome on top: the day grid, the range logic and the month
navigation are the library's; the tokens, the CalendarDayButton and the nav buttons are ours.
Every prop DayPicker takes passes straight through.
The day grid, the range logic and the month navigation are React DayPicker; the chrome, the tokens and the day button are ours.
Date Picker
Put a Calendar inside a Popover behind a Button and you have a date picker; see
Date Picker for the composed component.
Put this inside a Popover behind a Button and you have a date picker.
Persian / Hijri / Jalali Calendar
The chrome mirrors for a right-to-left document on its own. To change the calendar system, edit
components/ui/calendar.tsx and swap the import:
- import { DayPicker } from "react-day-picker"
+ import { DayPicker } from "react-day-picker/persian"The chrome mirrors for a right-to-left document on its own. Swapping the calendar system is one import in components/ui/calendar.tsx: react-day-picker/persian.
Selected Date (With TimeZone)
Pass timeZone so a selected day is the day the user sees. Detect it in an effect, not during
render — the server and the client are not in the same zone, and reading it during render is a
hydration mismatch.
Time zone: detecting…
Basic
className="rounded-lg border" is all the surface a bare calendar needs.
Range Calendar
mode="range" selects a span; numberOfMonths shows more than one month at a time.
Month and Year Selector
captionLayout="dropdown" replaces the caption with month and year dropdowns.
Presets
Drive month and selected from outside to offer shortcuts.
Date and Time Picker
A Card with the calendar in its content and two time fields in its footer.
Booked dates
disabled blocks the days; modifiers and modifiersClassNames mark them, so unavailability is
carried by the strike-through as well as by the dimming.
Custom Cell Size
--cell-size sets the grid's cell, and it takes breakpoint-specific values. A custom DayButton
renders anything you like inside a day.
Week Numbers
showWeekNumber adds the ISO week column.
RTL
The two nav chevrons rotate and the range caps swap ends, so a right-to-left document reads correctly without any extra work.
States
Selected, today, disabled and outside days in one frame.
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
buttonVariant | "default" | "destructive" | "ghost" | "link" | "outline" | "secondary" | null | — |
Everything else is React DayPicker's own API — see its
documentation. CalendarDayButton accepts DayPicker's DayButton props
plus an optional locale.
Accessibility
- React DayPicker renders the month as a real
<table>with column headers, and the day buttons carryaria-selected,aria-disabledanddata-day; ←/→, ↑/ ↓, Home/End and PageUp/ PageDown all move the focused day. - The day button is a
Button size="icon"at--cell-size(28px by default), so it clears the 24px target floor (A11Y-2) without a hit-area pseudo-element. - Focus is the global 2px
:focus-visibleoutline (FOC-1); upstream'sring-[3px]glow on the focused day is removed (FOC-6). - Booked or unavailable days should carry a non-colour marker as well as the dim — the Booked dates example uses a strike-through (A11Y-8).
- Today's day button carries
aria-current="date", so a screen reader hears it as the current date; visually it is an outline with medium weight, never the hover grey (A11Y-25). Calendaris a client component: it holds the focused day and reacts to selection.
| Contract | States tested |
|---|---|
| Behaviour | default, selected, range, disabled, outside, today |
| Accessibility | native-or-base-ui-semantics, browser-accessibility-test, keyboard-navigation, selected, disabled |
| Visual | default, hover, selected, range, disabled, today |
Do / Don't
Deviations
Upstream's file plus packages/ui/upstream/patches/calendar.patch. Every hunk:
- FOC-1, FOC-6 — the focused day's
group-data-[focused=true]/day:border-ring,:ring-[3px]and:ring-ring/50glow is removed. The day button is a real<button>, so the global:focus-visibleoutline inbase.cssmarks it; therelative z-10that lifted the focused cell above its neighbours stays. - A11Y-2 —
dropdown_rootgainsh-full min-h-6. WithcaptionLayout="dropdown"the real<select>isabsolute inset-0of that root, which upstream sizes from the caption's 20px line box — a 20px-tall pointer target. The dropdowns row is alreadyh-(--cell-size), so filling it costs no layout. - TYP-18 — the weekday and week-number labels'
text-[0.8rem]becomestext-xs. - A11Y-25 — today is marked without the hover grey: the
todaycell drops upstream'sbg-muted, and the day button takesfont-mediumplus, only while today is unselected, a 1pxborder-foreground/40outline (a border, never a ring — BRD-1). A selected today keeps the ordinary primary or range fill. Today's button carriesaria-current="date", which react-day-picker does not set. A read-only calendar, which renders no day buttons, draws the same outline and weight on today's cell. - DOC-2 —
cnis imported from@vegastack/design. - DOC-1 — prettier reflow plus the
registry-stamp.mjsprovenance header the three-copy model needs; no decision of its own.