Skip to content
Protocore Design Systemv1.6.9

/// Inputs

Calendar

Month-grid date picker with full keyboard navigation, single or range selection, and locale-aware labels.

import { Calendar } from "@protocore/pds";
View as Markdown

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.

July 2026
MON
TUE
WED
THU
FRI
SAT
SUN
Selected: Mon Jul 06 2026

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.

July 2026
MON
TUE
WED
THU
FRI
SAT
SUN

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.

July 2026
MON
TUE
WED
THU
FRI
SAT
SUN

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).

March 1994
MON
TUE
WED
THU
FRI
SAT
SUN

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

KeysAction
Arrow keysMove the focused day left/right by a day, up/down by a week.
PageUp / PageDownMove to the same day of the previous / next month.
Shift + PageUp / PageDownMove by a whole year.
Home / EndJump to the first / last day of the current week.
Enter / SpaceSelect 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

PropTypeDefaultDescription
captionLayoutenumlabelHeader layout: `label` (static caption) or `dropdown` (month + year Selects).
classNamestring
defaultMonthDateInitial visible month in uncontrolled mode.
defaultRangeValueDateRangeInitial selected range in uncontrolled range mode.
defaultValueDate | nullInitial selected date in uncontrolled single mode.
isDateDisabled((date: Date) => boolean)Predicate to disable arbitrary days (e.g. weekends, holidays).
localestringBCP-47 locale for month, weekday, and day-cell labels. Defaults to the runtime locale.
maxDateLatest selectable day (inclusive); later days are disabled.
minDateEarliest selectable day (inclusive); earlier days are disabled.
monthDateControlled first visible month (any day within it).
numberOfMonthsnumber1Number 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).
rangebooleanfalseSwitch to range selection — pairs with `rangeValue` / `onRangeValueChange`.
rangeValueDateRangeControlled selected range (range mode).
styleCSSProperties
valueDate | nullControlled selected date (single mode). Pass `null` for no selection.
weekStartsOnenum1First column of the week: 0 = Sunday … 6 = Saturday.
yearRangenumber10Range of years offered by the year dropdown, relative to the view year.

Related

  • DatePickerSunken field showing a formatted date that opens a Calendar in a raised overlay for point-and-click selection.
  • DateInputSegmented day/month/year field with spinbutton segments — the keyboard-first, type-to-fill alternative to the calendar.
  • DateRangePickerSunken field that opens a two-month Calendar for picking a start/end range, with live hover preview and auto-close.