Components · Inputs
Calendar
BetaShows and picks dates, such as scheduled sessions.
- Source
components/ui/calendar.tsx - shadcn/ui Calendar
- Figma: not linked yet (Q13)
Session with Sara: Thu, 12 Nov
When to use
- To pick a date when seeing the month helps: which days are free for a session, which week a check-in falls in.
- To pick a range, such as the period of a check-in report.
- Inline on a page or in a card, when the calendar is the main thing the coach works with.
When not to use
- In a form field: use a Date picker, which opens this calendar in a popover and keeps the form compact.
- For dates far in the past, such as a date of birth, a calendar is slow. Use the month and year menus (below) or three inputs for day, month and year.
- To show a schedule of sessions with times: that is a scheduling view, not a date input.
Anatomy
- 1. Month caption: month and year in the calendar's locale
- 2. Previous and next month buttons, at the edges of the caption row
- 3. Weekday names, starting on the locale's first day of the week
- 4. Day: a button in the grid; here the selected day
The calendar is shadcn's wrapper around React DayPicker. Dates come from the locale you pass, so month and weekday names, the first day of the week and the screen reader labels all follow the language.
Variants
- Single date (
mode="single"): one selected day, as in the first example. - Range (
mode="range"): start and end in primary, the days between in accent. Use it for report periods and plan phases. - Several dates (
mode="multiple"): for picking training days across a month. - Month and year menus (
captionLayout="dropdown"): replaces the caption with two menus. SetstartMonthandendMonthto a sensible span. - Several months (
numberOfMonths={2}): side by side on wide screens, stacked on narrow ones. Good for ranges that cross a month.
States
Fridays and 1–7 Nov are unavailable.
| State | What changes | Token |
|---|---|---|
| Hover | Accent fill on the day | bg-accent, text-accent-foreground |
| Focus-visible | Ring around the focused day, raised above its neighbours | border-ring, ring-ring/50, z-(--z-raised) |
| Today | Accent fill | bg-accent, text-accent-foreground |
| Selected | Primary fill | bg-primary, text-primary-foreground |
| Range start and end | Primary fill, rounded on the outer side | bg-primary, rounded-s-md, rounded-e-md |
| Range middle | Accent fill, square corners | bg-accent, text-accent-foreground |
| Outside the month | Muted text, still selectable | text-muted-foreground |
| Disabled | Muted text; the day button is disabled, so it also drops to half opacity | text-muted-foreground, opacity-(--opacity-disabled) |
The selected day uses the same pair as the primary button. In Coach, white on orange is 2.8:1 until decision #3 is settled.
Usage guidelines
- Disable days that can't be picked (the coach's days off, fully booked days) and say why in text next to the calendar. Color alone doesn't explain it.
- Open on the month that matters: the selected date's month, or the program's start month (
defaultMonth). - Keep outside days visible (the default). They help coaches see the week a date belongs to.
- Pass the locale and
numerals="latn"every time, so Arabic screens show Arabic month names with Western digits (QUESTIONS Q22).
Content guidelines
- Selected dates shown next to the calendar follow the Content formats: "Thu, 12 Nov" and "الخميس، 12 نوفمبر"; ranges "2–15 Nov".
- Explain disabled days in one line: "Fridays and 1–7 Nov are unavailable."
- Give the calendar an
aria-labelthat says what the date is for: "Session date", not "Calendar".
Accessibility
| Key | Does |
|---|---|
| Tab | Moves between the month buttons and the grid; the grid is one tab stop |
| Arrow keys | Move one day or one week (left and right follow the reading direction) |
| Home / End | First or last day of the week |
| Page up / down | Previous or next month |
| Shift + Page up / down | Previous or next year |
| Enter, Space | Selects the focused day |
- The month is a table with
role="grid", labelled with the month and year. Each day is a button labelled with the visible number first, then the full date: a screen reader announces "12, Thursday, November 12th, 2026, selected" or, on Arabic screens, the same in Arabic. Starting with the number keeps the visible label in the name (WCAG 2.5.3), so voice-control users can say "click 12". - Today is announced with "Today" in front of the date.
- Disabled days are skipped by the arrow keys and announced as unavailable.
- The month buttons are labelled "Go to the Previous Month" and "Go to the Next Month", in Arabic on Arabic screens. These labels come from the
react-day-picker/localepackage, so import the locale from there rather than fromdate-fns/locale, which has no DayPicker labels.
RTL
- The calendar takes its direction from the Radix
DirectionProvider, so the week starts on the right and runs right to left. - The previous-month button is on the right and the next-month button on the left; their chevrons rotate 180° so they point outwards.
- Left and right arrow keys follow the visual order: Arrow left goes to the next day.
- Range ends round on the logical side (
rounded-s-md,rounded-e-md), so a range reads correctly in both directions. - With the Arabic locale, weeks start on Saturday, as in the Gulf. Digits stay Western with
numerals="latn".
Tokens used
Read from components/ui/calendar.tsx at build time, so this list changes when the component does. Opacity modifiers such as /90 are shadcn hover and focus tints on top of the token.
| Token | Utilities | SuperCoach | Coach |
|---|---|---|---|
| --accent | bg-accent | brand-50 | orange-50 |
| --accent-foreground | text-accent-foreground | brand-700 | orange-700 |
| --background | bg-background | white | white |
| --input | border-input | neutral-200 | neutral-200 |
| --muted-foreground | text-muted-foreground | neutral-600 | neutral-600 |
| --popover | bg-popover | white | white |
| --primary | bg-primary | brand-600 | orange-500 |
| --primary-foreground | text-primary-foreground | white | white |
| --ring | border-ring ring-ring/50 | brand-600 | orange-500 |
- Layer
z-(--z-raised) - Opacity
opacity-(--opacity-disabled) - Radius
rounded-md rounded-e-md rounded-s-md - Radius
rounded-none - Shadow
shadow-xs - Type
font-medium - Type
leading-none - Type
font-regular - Type
text-sm - Type
text-xs
Props
The table lists the props the wrapper adds; the calendar also takes every DayPicker prop, such as mode, selected, onSelect, disabled, locale, numerals and captionLayout.
<Calendar>
Also takes every prop of <DayPicker>
| Prop | Type | Default |
|---|---|---|
| buttonVariant? | React.ComponentProps<typeof Button>["variant"] | "ghost" |
<CalendarDayButton>
Also takes every prop of <DayButton>
No props of its own.
Replaces in coach-admin
From the read-only audit of coach-admin: 16 style values, 19 uses.
coach-admin's calendar view is built on react-big-calendar (rbc- classes) with task cards on each day. Its month grid and toolbar styles map to this component; the task cards and the scheduling view around them are a separate pattern.
Classes and selectors
.rbc-header4.rbc-date-cell3.rbc-month-view2.rbc-off-range-bg1.calendar-toolbar1.rbc-month-row1.rw-calendar1.rbc-month-header1
Files
src/reusableComponents/calendar/Calendar.tsx5
The inventory lists the first 12 locations of each value, so file counts are a lower bound.
Related
Date picker, Popover, Form, Content, RTL.