Skip to content

Pickers (date + time)

components specs/components/pickers.kmd

Compound controls for selecting a date, time, date range, or date-time. Material parity (`/components/date-pickers` and `/components/time-pickers`). Covers docked vs modal date pickers, dial vs input time pickers, date ranges, locale rules, and keyboard navigation.

When this spec applies

Primary triggers

All triggers

Specification body

Spec — Pickers (date + time)

Facet Visual of Koder Design. Material parity: https://m3.material.io/components/date-pickers and https://m3.material.io/components/time-pickers.

Variant matrix

PickerModesUse
Date — modalCalendar grid, dialogDefault for date selection
Date — modal inputText input within dialogPower-user keyboard entry
Date — dockedCalendar attached to anchor inputContinuous form context
Date range — modalTwo-month calendar with range selectionTrip / report range
Time — dialAnalog clock faceDefault mobile entry
Time — inputTwo numeric fields HH:MMDesktop / keyboard preference

R1 — Date picker — modal (calendar)

Anatomy:

┌──────────────────────────────────────┐
│ Select date           ⌄  ⌃ Year      │
│ ────────────────────────────────────  │
│ ◂  May 2026                    ▸     │
│                                       │
│  S  M  T  W  T  F  S                 │
│                  1  2                 │
│  3  4  5  6  7  8  9                 │
│ 10 11 12 13 14 15 16                  │
│ 17 18 19 20 21 22 23                  │
│ 24 25 26 27 28 29 30                  │
│ 31                                    │
│                                       │
│                [Cancel]  [OK]         │
└──────────────────────────────────────┘
  • Container: dialog, 360 dp wide (mobile portrait) / 328 dp (desktop)
  • Header: shows current selection in headline-small
  • Month navigation: arrows + month name; tap month opens year picker
  • Day grid: 7 columns, 6 rows max; current day with circle outline; selected day with filled circle (accent color)
  • Footer: Cancel + OK text buttons
  • Today indicator: 1 px circle outline on today's date
  • Selected: filled circle, primary bg, on-primary text

R2 — Date picker — docked

Calendar floats below an anchor text field (no modal scrim). Used in forms with multiple date fields, where modal context-switch is heavy.

  • Anchor: text field with calendar icon trailing
  • On focus / icon tap: calendar appears below (above if no room)
  • Closes when user picks a date OR clicks outside
  • No scrim — page stays interactive

R3 — Date range picker

Two-month side-by-side calendar:

┌──────────────────────────────────────────────────────┐
│  Start date  →  End date                              │
│  Mar 12      →  Mar 28                                │
│ ──────────────────────────────────────────────────── │
│ ◂  March 2026         April 2026                ▸    │
│                                                       │
│  S  M  T  W  T  F  S    S  M  T  W  T  F  S          │
│  ...                     ...                          │
│                                                       │
│                       [Cancel]  [Save]                │
└──────────────────────────────────────────────────────┘
  • Two months visible; tap-and-drag OR two-tap to define range
  • Selected range: tonal bg (secondary-container) for days in range; filled circle on start + end
  • Mobile (Compact): one month with scroll, range still applies

R4 — Time picker — dial

Anatomy:

┌───────────────────────────────┐
│        Select time             │
│ ─────────────────────────────  │
│         ┌─ 10 : 30 ─┐  AM  PM │
│         └───────────┘          │
│                                │
│            ╭─────╮             │
│         11 │  ● 12             │
│       10  ╱│       ╲ 1         │
│         9  │   ●   │ 2         │
│           ╲│       │            │
│         8  └─────┘  3          │
│           7  6  5  4            │
│                                │
│            [Cancel]  [OK]      │
└───────────────────────────────┘
  • Display: HH : MM in large text; tap each segment to edit
  • Dial: hour ring (12 positions) or minute ring (60 positions, shown as 12 multiples of 5)
  • AM / PM segmented button (12-hour locale only)
  • Switch to keyboard: small icon toggles to Input variant

R5 — Time picker — input

Two text fields for HH and MM with : separator, plus AM/PM segmented button (12-hour locale only).

  • Each field: numeric keyboard on mobile; type-to-edit on desktop
  • Auto-advance: typing 2 digits in HH jumps to MM field
  • Validation: HH ∈ [0, 23] or [1, 12]; MM ∈ [0, 59]
  • Smaller dialog than dial variant (no clock face)

Used as default on desktop AND as toggle from dial when user prefers keyboard.

R6 — Locale rules

Pickers ALWAYS respect locale (per i18n/contract.kmd):

Locale aspectDriven by
First day of weekLocale (Sun / Mon / Sat)
Month / weekday namesLocale
Date format in headerLocale
12 / 24-hourLocale (en-US 12h; pt-BR 24h)
Number systemLocale (Latin / Arabic-Indic / etc.)

Override via prop only if domain requires (e.g., 24-hour for medical schedules in en-US). Document why.

R7 — Constraints

Pickers support:

  • min / max dates → out-of-range days disabled (38% opacity, not selectable)
  • disabledDates array → specific dates blocked (holidays, booked slots)
  • disabledRanges for date range picker

Disabled dates are still navigable via keyboard but cannot be selected; screen reader announces "Disabled".

R8 — Keyboard

KeyDate pickerTime picker
TabEnters → cycles through controlsSame
Arrow Up/DownPrevious/next weekIncrement by 1
Arrow Left/RightPrevious/next daySwitch HH ↔ MM
Page Up/DownPrevious/next monthIncrement by 5 (minute) / 1 (hour)
Home / EndStart/end of weekn/a
EnterSelects date / timeSelects
EscCancelsCancels

R9 — Accessibility

  • Dialog wrapper: role="dialog" + aria-modal="true" + aria-labelledby (pointing to "Select date")
  • Calendar grid: role="grid" + aria-label="Date picker"; each cell role="gridcell" with aria-selected on current
  • Out-of-range / disabled cells: aria-disabled="true"
  • Today cell: aria-current="date"
  • Time dial: role="slider" per ring (hour / minute) with aria-valuenow / valuemin / valuemax
  • Time input fields: native <input type="number"> semantics; labels read "Hour" / "Minute"
  • Screen reader announces selection: "Selected: Monday, May 11, 2026"

R10 — Animation

  • Modal open: scale-in 0.95 → 1 + fade (motion-medium)
  • Month change: slide horizontal (motion-medium)
  • Year picker: vertical scroll list (in-place, no slide)
  • Dial click: hand rotates to target position (motion-emphasized-decelerate)
  • Range drag: tonal fill grows in real-time
  • Reduced motion: instant transitions; no rotation animation (snap dial)

R11 — Per-preset variation

PresetDate visualTime visual
material3Tonal selection, rounded buttonsDial first, input toggle
material2Solid accent selectionDial first, no input toggle
ios_cupertinoWheel pickers stacked (year/month/day)Wheel pickers (HH/MM)
gnomeAdwaita calendar w/ accent todaySpin buttons for HH/MM
windows_11Combo button revealing flyoutCombo button revealing flyout
brutalistSharp grid, no animationSharp dial, no rotation
terminal_classicText prompt YYYY-MM-DD>Text prompt HH:MM>

R12 — Density

DensityCell sizeFooter height
Compact36 px48 px
Default40 px52 px
Comfortable48 px64 px

R13 — Forbidden patterns

  • ❌ Date picker without min / max for time-bounded contexts (birthdate, expiry, etc.)
  • ❌ Time picker dial < 240 dp diameter (touch targets too small)
  • ❌ Ignoring locale first-day-of-week
  • ❌ Picker that requires server roundtrip per month change (latency)
  • ❌ Picker without keyboard fallback (Input variant always available for accessibility)
  • ❌ Calendar grid that scrolls vertically through years (use year picker overlay instead)
  • ❌ Disabled dates without explanation (provide tooltip or description)
  • ❌ Range picker that allows end < start without auto-swap
  • i18n/contract.kmd — locale-aware formatting
  • themes/color-roles.kmdprimary for selected, secondary-container for range
  • themes/typography.kmdheadline-small for header, body-large for cells
  • components/text-fields.kmd — anchor for docked variant
  • components/dialogs.kmd — modal wrapper pattern
  • interaction/states.kmd — disabled / selected layers
  • foundations/elements.kmd — Control + Container families

Requirements (testable)

Requirement: Modal date picker selection and confirmation {#req-pickers-modal-date-selection}

The modal date picker SHALL present a calendar grid of 7 columns within a dialog, render today's date with a 1 px circle outline and the selected day as a filled circle using the primary background with on-primary text, and SHALL commit the chosen date only when the user activates the OK button, discarding it on Cancel.

Scenario: Selecting a day and confirming with OK

  • GIVEN a modal date picker is open showing May 2026 with no prior selection
  • WHEN the user taps day 11 and then activates the OK button
  • THEN the picker commits May 11, 2026 as the selected date
  • AND day 11 is rendered as a filled circle with primary background and on-primary text
  • AND the dialog closes

Scenario: Cancel discards the tentative selection

  • GIVEN a modal date picker is open with an existing selected value of May 4, 2026
  • WHEN the user taps day 20 and then activates the Cancel button
  • THEN the picker retains May 4, 2026 as the committed value
  • AND the tentative selection of day 20 is discarded

Scenario: Today is marked with an outline distinct from selection

  • GIVEN a modal date picker is open and today is May 7, 2026
  • WHEN the calendar grid renders
  • THEN the cell for day 7 shows a 1 px circle outline
  • AND that outline is visually distinct from the filled-circle selected state

Requirement: Date range picker ordering {#req-pickers-range-order}

The date range picker SHALL render two months and apply a tonal secondary-container background to every day within the selected range with a filled circle on the start and end days, and SHALL NOT allow an end date earlier than the start date — when the user picks an earlier end, the picker SHALL auto-swap the two so that start precedes end.

Scenario: End earlier than start auto-swaps

  • GIVEN a date range picker with start date already set to March 28, 2026
  • WHEN the user picks March 12, 2026 as the end date
  • THEN the picker auto-swaps the values so the range is March 12 → March 28, 2026
  • AND the end date is never stored earlier than the start date

Scenario: Days inside the range receive the tonal fill

  • GIVEN a date range picker with a completed range of March 12 → March 28, 2026
  • WHEN the two-month calendar renders
  • THEN days 12 and 28 show a filled circle
  • AND every day between 12 and 28 shows the secondary-container tonal background

Requirement: Locale-driven formatting {#req-pickers-locale}

Pickers SHALL respect the active locale for first day of week, month and weekday names, header date format, and 12-hour versus 24-hour time, using the locale default unless an explicit prop override is supplied.

Scenario: pt-BR uses Monday-first weeks and 24-hour time

  • GIVEN the active locale is pt-BR
  • WHEN a date picker and a time picker render with no override props
  • THEN the calendar's first column is Monday
  • AND the time picker presents a 24-hour display with no AM/PM segmented button

Scenario: en-US uses Sunday-first weeks and 12-hour time

  • GIVEN the active locale is en-US
  • WHEN a date picker and a time picker render with no override props
  • THEN the calendar's first column is Sunday
  • AND the time picker presents a 12-hour display with an AM/PM segmented button

Requirement: Constraint enforcement on out-of-range and disabled dates {#req-pickers-constraints}

Pickers SHALL disable days that fall outside the min/max bounds or that appear in disabledDates, rendering them at 38% opacity and refusing selection while keeping them reachable via keyboard navigation, and the screen reader SHALL announce such a cell as "Disabled".

Scenario: Out-of-range day cannot be selected

  • GIVEN a date picker with max set to May 20, 2026
  • WHEN the user taps day 25
  • THEN no selection is committed
  • AND day 25 is rendered at 38% opacity

Scenario: Disabled date is navigable but announced as disabled

  • GIVEN a date picker with disabledDates containing May 15, 2026
  • WHEN the user navigates onto May 15 with the keyboard
  • THEN focus lands on the May 15 cell without selecting it
  • AND the screen reader announces "Disabled"

Requirement: Keyboard navigation and accessibility semantics {#req-pickers-a11y-keyboard}

Pickers SHALL be fully keyboard operable — in the date picker the arrow keys move focus by day (Left/Right) or by week (Up/Down) and Page Up/Down move by month, Enter selects and Esc cancels — and SHALL expose the dialog with role="dialog" plus aria-modal="true", the calendar as role="grid" with each day cell role="gridcell" carrying aria-selected, today's cell as aria-current="date", and disabled cells as aria-disabled="true".

Scenario: Arrow and Page keys move focus across the grid

  • GIVEN a modal date picker is open with focus on May 11, 2026
  • WHEN the user presses Arrow Down and then Page Down
  • THEN focus moves to May 18 after Arrow Down (next week)
  • AND focus moves to the corresponding day in June after Page Down (next month)

Scenario: Enter selects and Esc cancels via keyboard

  • GIVEN a modal date picker is open with focus on May 11, 2026
  • WHEN the user presses Enter
  • THEN May 11, 2026 is selected
  • WHEN the user reopens the picker and presses Esc
  • THEN the picker cancels without committing a new value

Scenario: ARIA roles and states are exposed on the grid

  • GIVEN a modal date picker is open with May 11 selected, today being May 7, and May 15 disabled
  • WHEN the accessibility tree is inspected
  • THEN the dialog exposes role="dialog" and aria-modal="true"
  • AND the calendar exposes role="grid" with the day-11 cell aria-selected="true"
  • AND the day-7 cell exposes aria-current="date" and the day-15 cell exposes aria-disabled="true"

References