Skip to content
Components

Calendar

A month grid for picking one date or a range of dates inline, for booking or availability surfaces where the date is the point of the screen. In a form, use DatePicker, which opens this grid in a popover.

Preview

October 2026

Props

  • mode
  • value
  • defaultValue
  • disabled
  • minDate
  • maxDate
  • locale
  • weekStartsOn
  • numberOfMonths
  • rangeStartLabel
  • rangeEndLabel
import { Calendar } from "@/components/ui/calendar"

<Calendar />

Usage

When to use

  • A whole month on the page when the date is the point of the screen, such as availability or booking.
  • Limiting the choice to real dates, with disabled days or a min and max date.

When not to use

  • Picking one date in a form, next to other fields. Use DatePicker.
  • Showing events on their dates. This is a picker, not a schedule.

Variants

weekStartsOn

The first day of the week: 0 Sunday or 1 Monday. Follows the locale when not set.

  • October 2026

    0Sunday

  • October 2026

    1Monday

import { Calendar } from "@/components/ui/calendar"

<Calendar />
<Calendar weekStartsOn={1} />

Examples

Inline

Calendar on the page, for when the date is the point of the screen.

October 2026
import { Calendar } from "@/components/ui/calendar"

<Calendar weekStartsOn={1} />

A range

Two months for a range that crosses a month. The first click sets the start, the second the end.

October 2026
November 2026
import { Calendar } from "@/components/ui/calendar"

<Calendar mode="range" numberOfMonths={2} />

Do and don't

Do

  • Use Calendar inline when the date is the page's whole purpose, such as a booking or availability surface.
  • Use DatePicker instead in forms, next to Input fields.
  • Disable days that can never be picked, such as past days or closed days, over a minDate and maxDate.
  • Use two months for ranges that often cross a month.

Don't

  • Don't use a range for one date; keep the single mode.
  • Don't put a Calendar in your own popover for a form field; DatePicker already does that, with the focus handling.

Props

NameTypeValuesDefaultDescription
modeenumsingle | rangesinglesingle picks one Date; range picks a start and an end ({ from, to }). After a complete range the next click starts a new one; a click on the day of a one-day range clears it. A range never spans a disabled day.
valuestring--The picked Date, or in range mode the picked range ({ from, to }), when you control it. An explicit undefined clears the selection; omitting value uses internal state. Pair it with onValueChange.
defaultValuestring--The Date, or in range mode the range, picked on first render, when you don't control it.
onValueChangefunction--Called with the picked Date, or undefined when the user unpicks it. In range mode it is called on every click: { from } after the first, { from, to } after the second, undefined when a one-day range is cleared.
disabledboolean-falsetrue disables every day; a function receives a Date and disables single days, such as weekends or past dates.
minDatestring--No day before this Date can be picked or navigated to.
maxDatestring--No day after this Date can be picked or navigated to.
localestring--A date-fns Locale object for month and weekday names. The default is enUS.
weekStartsOnenum0 | 1-The first day of the week: 0 Sunday or 1 Monday. Follows the locale when not set.
numberOfMonthsnumber-1How many months show at once, side by side from sm and stacked below it. Use two for ranges that often cross a month.
rangeStartLabelstring-start of rangeRange mode. Added to the start day's accessible name; translate it with the locale.
rangeEndLabelstring-end of rangeRange mode. Added to the end day's accessible name; translate it with the locale.

Anatomy

PartDescription
rootThe month view, padded md. With numberOfMonths above 1 the months sit side by side from sm and stack below it.
labelThe caption row: the month and year, md medium text, centered between the navigation buttons.
iconTabler ChevronLeft and CaretRight, 16px, secondary, in the previous and next month buttons.
headOne weekday column header, xs medium secondary text.
itemOne day: the cell and its button. The button is the md control height, square; the cell carries the selected fill. In range mode the two ends take the accent solid, rounded on the outer side and square toward the band.
rangeA day between the two ends of a range; the cells form one band.

Accessibility

Role: grid

KeysAction
Space, EnterPicks the focused day.
ArrowDown, ArrowUp, ArrowLeft, ArrowRightMoves focus to the day a week later or earlier, or the day before or after, skipping disabled days.
Home, EndMoves focus to the first or last day of the week.
PageUp, PageDownMoves focus to the same day in the previous or next month. Shift goes by year.
  • One day button at a time is tabbable; the arrow keys move focus inside the grid, following the WAI-ARIA APG grid pattern. With several months they cross from one month into the next.
  • The picked day's cell has aria-selected, the disabled days have native disabled buttons, and the caption is a polite live region, so a screen reader hears the month it lands on.
  • In range mode every day in the range has aria-selected. The start and end buttons add rangeStartLabel and rangeEndLabel ("start of range", "end of range") to their accessible names; a one-day range gets both. The ends differ from the middle by fill and shape, and the DatePicker trigger or the caption names the dates, so the state never relies on color alone.
  • The weekday header row is a real row of column headers.

Install

npx shadcn add https://werewolf-ui.vercel.app/r/calendar.json

This also adds styles/werewolf.css, the tokens every component uses, and any other component it needs. Import that file once in your root CSS, after @import "tailwindcss", with a path relative to that CSS file (from app/globals.css it is @import "../styles/werewolf.css").