Calendar
Shows a month of days to pick a date or a range.
Installation
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`.
Range
A five-day reporting period, the 9th to the 13th, picked with `mode="range"`, with its ends marked.
Disabled days
Days before today and weekends turned off with `disabled` matchers, as when booking an onsite visit.
API reference
| Part | Built on |
|---|---|
Calendar | DayPicker (opens in a new tab) |
CalendarDayButton | DayPicker DayButton (opens in a new tab) |
Calendar
| Prop | Type | Default | Description |
|---|---|---|---|
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. |
showOutsideDays | boolean | true | Show the outside days (days falling in the next or the previous month). |
CalendarDayButton
| Prop | Type | Default |
|---|---|---|
locale | Partial<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
linkButton below it if you need one. - Icons are lucide's, not SLDS's utility icons.
| Prop | Value | SLDS | Notes |
|---|---|---|---|
captionLayout | label | slds-datepicker__filter_month | The month and year as text between the arrows. SLDS always adds a year picklist. |
captionLayout | dropdown | slds-select | Month and year picklists between the arrows. |
captionLayout | dropdown-months | slds-select | A month picklist and the year as text. |
captionLayout | dropdown-years | slds-datepicker__filter | The month as text and a year picklist, SLDS's header. Both sit between the arrows; SLDS puts the picklist after them. |
buttonVariant | ghost | slds-button_icon-container | The 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
labelsto translate them, withlocalefor 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 themdbreakpoint and stack below it. - A six-week month is one row taller than a five-week one. Pass
fixedWeeksto keep the height steady.
Rules
- With
captionLayout="dropdown"or"dropdown-years", setstartMonthandendMonth, 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
PopoverContentwithclassName="w-auto p-0", opened from a Button that shows the date.