Components · Inputs
Date picker
BetaPicks a date, such as a program start date.
- Source
components/ui/calendar.tsx - shadcn/ui Date picker
- Figma: not linked yet (Q13)
Week 1 of the program starts on this day.
When to use
- For a date field in a form: a program start date, a session date, a payment due date.
- For a date range in a toolbar or a filter: the period of a check-in or revenue report.
When not to use
- When the calendar is the main content of the view: show a Calendar inline.
- For a date of birth or other dates years away: use the calendar's month and year menus, or three inputs.
- For a time of day: there is no time picker yet; use an Input or a Select of time slots.
Anatomy
- 1. Label: always visible, above the trigger
- 2. Trigger: an outline button that opens the calendar
- 3. Calendar icon, at the start
- 4. Value: the chosen date, or a placeholder
A date picker is a composition, as in shadcn's date picker recipe: a Button as the trigger, a Popover, and a Calendar inside it. Open the example to see the popover: the calendar with its month caption, month buttons and day grid, aligned to the trigger's start edge. Picking a day closes the popover and writes the date into the trigger.
Variants
- Single date: the default above. The popover closes on select.
- Range:
mode="range"with two months side by side. The popover stays open until the coach clicks outside or presses Escape, so both ends can be picked. The trigger shows "2 Nov – 15 Nov 2026". - With presets: coach-admin's report filters offer "Last 7 days", "This month" next to the calendar. Put them as buttons above the calendar inside the popover.
States
The program has started.
Pick a start date to send the program.
| State | What changes | Token |
|---|---|---|
| Placeholder | Lighter text | text-muted-foreground |
| Hover (trigger) | Accent fill | bg-accent, text-accent-foreground |
| Focus-visible (trigger) | Border and a 3px ring | border-ring, ring-ring/50 |
| Open | Popover with border and shadow, on the popover layer | bg-popover, shadow-md, z-(--z-popover) |
| Disabled | Half opacity, no pointer events | opacity-(--opacity-disabled) |
| Invalid | Red border and ring, error text below | border-destructive, ring-destructive/20, text-destructive-text |
Day states inside the popover (today, selected, range, disabled) are listed on the Calendar page.
Usage guidelines
- Disable dates that can't be picked, such as past dates for a program start (
disabled={{ before: new Date() }}), and say why in help text if it isn't obvious. - Open the calendar on the month of the current value, or on today when empty.
- Close the popover on select for single dates. Move focus back to the trigger, which Radix does on close.
- Show the error after the popover closes without a date, not while the coach is still choosing.
Content guidelines
- Label: what the date is for: "Program start date", "Report period". Not "Date".
- Placeholder: "Pick a date" or "Pick a date range".
- Value: the Content format: "2 Nov 2026"; ranges "2 Nov – 15 Nov 2026". In Arabic: "2 نوفمبر 2026", with Western digits.
- Help text says what happens on that date: "Week 1 of the program starts on this day."
Accessibility
| Key | Does |
|---|---|
| Enter, Space (on the trigger) | Opens the calendar; focus moves to the selected day, or today |
| Arrow keys, Page up / down | Move through days and months, as on the Calendar |
| Enter, Space (on a day) | Selects it; a single date picker closes and focus returns to the trigger |
| Escape | Closes without changing the value; focus returns to the trigger |
- The trigger is a button with
aria-haspopup="dialog"andaria-expanded, from the Radix popover. It is labelled by both the label and the current value (aria-labelledbylists the two ids), so a screen reader announces "Program start date 2 Nov 2026, button, collapsed". Without that, the label replaces the button text and the date is never read. - Help and error text are linked with
aria-describedby; an invalid trigger hasaria-invalid="true". autoFocuson the calendar moves focus into the grid when the popover opens.
RTL
- The calendar icon is at the start of the trigger, on the right in RTL. It doesn't mirror.
- The popover aligns to the trigger's start edge (
align="start"), its right edge in RTL. - Inside the popover, the calendar mirrors as described on the Calendar page: weeks run right to left and the month chevrons point outwards.
- Pass
locale={lang === "ar" ? ar : enUS}fromreact-day-picker/localeandnumerals="latn", and format the trigger's value with the same locale.
Tokens used
The trigger is an outline Button and the panel a Popover. The calendar inside reads:
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 date picker has no component of its own. These are the Calendar props; see Popover for the popover parts.
<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: 41 style values, 47 uses.
coach-admin has a date range picker with presets (date-range-picker) and a separate one in the finance pages (finance-date-range-picker). Both become this component in range mode.
Classes and selectors
.date-range-picker__input4.datefilter4.date-range-picker__presets4.finance-date-range-picker__trigger3.finance-date-range-picker__dropdown3.date-range-picker__dropdown3.date-range-picker__required2.date-range-picker__icon1.date-range-picker__arrow1.date-range-picker-button .apply-btn-container1.finance-date-range-picker__list1.date-range-picker__dropdown--with-presets1
Files
src/reusableComponents/formComponents/date-range-picker/date-range-picker.scss32
The inventory lists the first 12 locations of each value, so file counts are a lower bound.
SCSS variables in these styles
$input-border-colornot a color: spacing, size or layout$input-focus-shadownot a color: spacing, size or layout$card-surface-bgnot a color: spacing, size or layout$error-color→--destructive$input-placeholder-color→ nearest neutral-300 (no semantic token)$input-affix-color→ nearest neutral-400 (no semantic token)$border-color→--border$input-shadownot a color: spacing, size or layout$dropdown-panel-shadownot a color: spacing, size or layout$z-index-dropdownnot a color: spacing, size or layout