/// Inputs
Calendar
Month-grid date picker with full keyboard navigation, single or range selection, and locale-aware labels.
import { Calendar } from "@protocore/pds";Basics
A single-date calendar. Controllable via value / defaultValue / onValueChange; the visible month is controllable too via month / defaultMonth. Weeks start on Monday by default — set weekStartsOn to change it.
Min / max & disabled days
Bound the selectable window with min and max; disable arbitrary days (weekends, blackout dates) with isDateDisabled. Out-of-range days are non-interactive and marked aria-disabled.
Range selection
Set range to select a start and end across two clicks. Hovering previews the pending range; the endpoints render as inverted fills and the days between get a subtle accent wash.
Month & year dropdowns
Pass captionLayout="dropdown" to swap the static caption for month and year Selects — useful for jumping across distant months (e.g. a date of birth).
When to use it
Use Calendar when the month grid should stay on screen — inline scheduling, a booking sidebar, a report window. When the calendar should live behind a field, use DatePicker (single) or DateRangePicker (range), both of which wrap this same grid in a popover. When users know the exact date and would rather type it, offer DateInput as the keyboard-first alternative.
Usage
Do
- Let users reach any date by keyboard — arrows move by day, PageUp/PageDown by month.
- Set `min` / `max` to the real booking window instead of validating after selection.
- Pick a `weekStartsOn` that matches your audience (Monday for most of Europe).
- Pass a `locale` when the surrounding UI is localised so weekday and month names match.
Don't
- Don't use a Calendar for a far-past date like a birth year without `captionLayout="dropdown"`.
- Don't reimplement range logic on top of single mode — pass `range` and read `onRangeValueChange`.
- Don't colour-code availability with hue alone; pair it with a disabled state or label.
- Don't trap focus inside the grid — it is a composable region, not a modal.
Accessibility
| Keys | Action |
|---|---|
| Arrow keys | Move the focused day left/right by a day, up/down by a week. |
| PageUp / PageDown | Move to the same day of the previous / next month. |
| Shift + PageUp / PageDown | Move by a whole year. |
| Home / End | Jump to the first / last day of the current week. |
| Enter / Space | Select the focused day. |
- The grid uses `role="grid"` with `gridcell` children; the selected cell carries `aria-selected` and today is marked `aria-current="date"`.
- A roving tabindex keeps exactly one day in the tab order, so Tab enters and leaves the grid in a single step.
- Each day exposes a full accessible name (e.g. "Monday, July 6, 2026") built with Intl.DateTimeFormat in the active locale.
- Weekday headers are `columnheader`s; out-of-range days are `aria-disabled` rather than removed, preserving the grid shape.
Calendar props
| Prop | Type | Default | Description |
|---|---|---|---|
captionLayout | enum | label | Header layout: `label` (static caption) or `dropdown` (month + year Selects). |
className | string | — | |
defaultMonth | Date | — | Initial visible month in uncontrolled mode. |
defaultRangeValue | DateRange | — | Initial selected range in uncontrolled range mode. |
defaultValue | Date | null | — | Initial selected date in uncontrolled single mode. |
isDateDisabled | ((date: Date) => boolean) | — | Predicate to disable arbitrary days (e.g. weekends, holidays). |
locale | string | — | BCP-47 locale for month, weekday, and day-cell labels. Defaults to the runtime locale. |
max | Date | — | Latest selectable day (inclusive); later days are disabled. |
min | Date | — | Earliest selectable day (inclusive); earlier days are disabled. |
month | Date | — | Controlled first visible month (any day within it). |
numberOfMonths | number | 1 | Number of consecutive month grids to render side by side. |
onMonthChange | ((month: Date) => void) | — | Fires when the visible month changes via navigation. |
onRangeValueChange | ((range: DateRange) => void) | — | Fires with the ordered `{ start, end }` as the range is built (range mode). |
onValueChange | ((date: Date) => void) | — | Fires with the newly selected day (single mode). |
range | boolean | false | Switch to range selection — pairs with `rangeValue` / `onRangeValueChange`. |
rangeValue | DateRange | — | Controlled selected range (range mode). |
style | CSSProperties | — | |
value | Date | null | — | Controlled selected date (single mode). Pass `null` for no selection. |
weekStartsOn | enum | 1 | First column of the week: 0 = Sunday … 6 = Saturday. |
yearRange | number | 10 | Range of years offered by the year dropdown, relative to the view year. |