Skip to content
Component installs need the registry setup— the Base UI shadcn project, the @vegastack namespace and the Cloudflare Access service token.
VegaStack Design

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
stable
Since
0.10.0
Accessibility pattern
APG date grid via React DayPicker

Last updated

January 2026

Install

Add Calendar from the VegaStack registry. The CLI verifies the item's integrity hash before writing it.

pnpm dlx shadcn@latest add @vegastack/calendar

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"
/>;
January 2026

Anatomy

Calendar — data-slot="calendar"
CalendarDayButton

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.

January 2026

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.

January 2026

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"
January 2026

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.

January 2026

Time zone: detecting…

Basic

className="rounded-lg border" is all the surface a bare calendar needs.

January 2026

Range Calendar

mode="range" selects a span; numberOfMonths shows more than one month at a time.

January 2026
February 2026

Month and Year Selector

captionLayout="dropdown" replaces the caption with month and year dropdowns.

January 2026

Presets

Drive month and selected from outside to offer shortcuts.

February 2026

Date and Time Picker

A Card with the calendar in its content and two time fields in its footer.

January 2026

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.

January 2026

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.

December 2026

Week Numbers

showWeekNumber adds the ISO week column.

January 2026
01
02
03
04
05

RTL

The two nav chevrons rotate and the range caps swap ends, so a right-to-left document reads correctly without any extra work.

January 2026
January 2026

States

Selected, today, disabled and outside days in one frame.

January 2026

API Reference

PropTypeDefaultDescription
buttonVariant"default" | "destructive" | "ghost" | "link" | "outline" | "secondary"

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 carry aria-selected, aria-disabled and data-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-visible outline (FOC-1); upstream's ring-[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).
  • Calendar is a client component: it holds the focused day and reacts to selection.
ContractStates tested
Behaviourdefault, selected, range, disabled, outside, today
Accessibilitynative-or-base-ui-semantics, browser-accessibility-test, keyboard-navigation, selected, disabled
Visualdefault, hover, selected, range, disabled, today

Do / Don't

Do
Pass timeZone when the selected date is stored or sent anywhere — otherwise a late-evening pick lands on the wrong day.
Don't
Reach into the day grid with element selectors to restyle it — pass classNames, modifiers or a DayButton.

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/50 glow is removed. The day button is a real <button>, so the global :focus-visible outline in base.css marks it; the relative z-10 that lifted the focused cell above its neighbours stays.
  • A11Y-2dropdown_root gains h-full min-h-6. With captionLayout="dropdown" the real <select> is absolute inset-0 of that root, which upstream sizes from the caption's 20px line box — a 20px-tall pointer target. The dropdowns row is already h-(--cell-size), so filling it costs no layout.
  • DOC-2cn is imported from @vegastack/design.
  • DOC-1 — prettier reflow plus the registry-stamp.mjs provenance header the three-copy model needs; no decision of its own.

On this page