Skip to content

Calendar

Shows a month of days to pick a date or a range.

Library
What’s this?
June 2026

Installation

pnpm dlx shadcn@latest add @wisty/calendar

The CLI installs the Radix or Base UI file to match your project, with what it builds on: button.

If your project doesn't have Wisty UI's theme yet, add it once with npx shadcn@latest add @wisty/cosmos. Until then, components keep shadcn's default look. Installation has the details.

When to use

Use it

  • People pick a date and seeing the month helps, like a close date near today or a day of the week.
  • People pick a range of days, like a trip or a reporting period, as the Range example shows.
  • Inside a Popover under a date field, as the grid of a date picker.

Don't use it

  • People type a date they already know, like a birth date. Use Input with a format hint.
  • The date is years away. Use captionLayout="dropdown" so people jump by month and year, or Input.
  • People pick a time too. Pair the calendar with a time field.

Usage

In a form, put the calendar in a FieldSet with a FieldLegend variant="label", so the month sits in a group named by the field's label. Point the fieldset's aria-describedby at the help text: the calendar's root takes aria-labelledby but not aria-describedby.

"use client"

import { useState } from "react"

import { Calendar } from "@/components/ui/calendar"
import { FieldDescription, FieldLegend, FieldSet } from "@/components/ui/field"

export function CloseDate() {
  const [date, setDate] = useState<Date | undefined>()

  return (
    <FieldSet className="w-fit" aria-describedby="close-date-help">
      <FieldLegend variant="label">Close Date</FieldLegend>
      <Calendar mode="single" selected={date} onSelect={setDate} />
      <FieldDescription id="close-date-help">
        The quarter ends on June 30.
      </FieldDescription>
    </FieldSet>
  )
}

CalendarDayButton is the day cell Calendar renders. To change what a day shows, pass your own DayButton in components and render CalendarDayButton inside it, so the day keeps its states.

Examples

Year picklist

The month between the arrows and a year picklist, set with `captionLayout="dropdown-years"`, `startMonth` and `endMonth`.

JunJune 2026

Range

A five-day reporting period, the 9th to the 13th, picked with `mode="range"`, with its ends marked.

June 2026

Disabled days

Days before today and weekends turned off with `disabled` matchers, as when booking an onsite visit.

June 2026

API reference

PartBuilt on
CalendarDayPicker (opens in a new tab)
CalendarDayButtonDayPicker DayButton (opens in a new tab)

Calendar

PropTypeDefaultDescription
buttonVariant"link" | "default" | "destructive" | "success" | "outline" | "secondary" | "ghost" | "outline-brand" | "destructive-text" | "inverse" | null"ghost"
captionLayout"label" | "dropdown" | "dropdown-months" | "dropdown-years""label"Show dropdowns to navigate between months or years.
showOutsideDaysbooleantrueShow the outside days (days falling in the next or the previous month).

CalendarDayButton

PropTypeDefault
localePartial<Locale>

SLDS mapping

Calendar is the grid of SLDS 2's slds-datepicker, drawn with react-day-picker. The arrows are slds-button_icon-container buttons and the weekday headers are SLDS's column headers. Each day is slds-day: today is slds-is-today, a selected day slds-is-selected, a range slds-is-selected-multi, and a disabled day slds-disabled-text.

Known gaps:

  • Weekdays are two letters (Su, Mo). SLDS shows three (Sun, Mon): pass formatters={{ formatWeekdayName: (date) => date.toLocaleString("en", { weekday: "short" }) }}.
  • SLDS puts the year picklist after the arrows. Here the picklists sit between them.
  • SLDS's Today link under the grid isn't part of the calendar. Add a link Button below it if you need one.
  • Icons are lucide's, not SLDS's utility icons.
PropValueSLDSNotes
captionLayoutlabelslds-datepicker__filter_monthThe month and year as text between the arrows. SLDS always adds a year picklist.
captionLayoutdropdownslds-selectMonth and year picklists between the arrows.
captionLayoutdropdown-monthsslds-selectA month picklist and the year as text.
captionLayoutdropdown-yearsslds-datepicker__filterThe month as text and a year picklist, SLDS's header. Both sit between the arrows; SLDS puts the picklist after them.
buttonVariantghostslds-button_icon-containerThe month arrows. Any Button variant works.

Keyboard interactions

The grid is one tab stop: Tab lands on the selected day, or today.

Keys Action
Arrow keys Move focus a day or a week, crossing into the next month.
Home, End Move focus to the first or last day of the week.
Page Up, Page Down Move focus a month back or forward.
Shift+Page Up, Page Down Move focus a year back or forward.
Enter or Space Selects the focused day.

Accessibility

  • Name the calendar with the field it fills, as Usage shows. On its own the grid is named by the month and year alone.
  • The arrows and picklists are named in English ("Go to the Next Month"). Pass labels to translate them, with locale for the day and month names.
  • In Lightning Blue, today is Salesforce's own pair, the link color on the light brand blue, at 3.67:1. It fails AA, and Salesforce's stylesheet fails the same way.

Responsive behavior

  • The calendar has a fixed width and doesn't shrink. Put it in a Popover on small screens, or let its container scroll.
  • With numberOfMonths, months sit side by side from the md breakpoint and stack below it.
  • A six-week month is one row taller than a five-week one. Pass fixedWeeks to keep the height steady.

Rules

  • With captionLayout="dropdown" or "dropdown-years", set startMonth and endMonth, so the picklists list a sensible range.
  • Don't restyle the day colors with classes. They come from the theme tokens through the cell's state, and a class pins one look into every theme.
  • For a date field, put the calendar in a PopoverContent with className="w-auto p-0", opened from a Button that shows the date.