Pickers (date + time)

mandatory

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.

Spec — Pickers (date + time)

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

Variant matrix

Picker Modes Use
*ate — modal* Calendar grid, dialog Default for date selection
*ate — modal input* Text input within dialog Power-user keyboard entry
*ate — docked* Calendar attached to anchor input Continuous form context
*ate range — modal* Two-month calendar with range selection Trip / report range
*ime — dial* Analog clock face Default mobile entry
*ime — input* Two numeric fields HH:MM Desktop / 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]         │
└──────────────────────────────────────┘
  • *ontainer* dialog, 360 dp wide (mobile portrait) /

    328 dp (desktop)

  • *eader* shows current selection in headline-small
  • *onth navigation* arrows + month name; tap month opens year

    picker

  • *ay grid* 7 columns, 6 rows max; current day with circle outline;

    selected day with filled circle (accent color)

  • *ooter* Cancel + OK text buttons
  • *oday indicator* 1 px circle outline on today's date
  • *elected* 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

Twomonth sideby-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; tapanddrag 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]      │
└───────────────────────────────┘
  • *isplay* HH : MM in large text; tap each segment to edit
  • *ial* hour ring (12 positions) or minute ring (60 positions,

    shown as 12 multiples of 5)

  • *M / PM*segmented button (12-hour locale only)
  • *witch 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; typetoedit 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 aspect Driven by
First day of week Locale (Sun / Mon / Sat)
Month / weekday names Locale
Date format in header Locale
12 / 24-hour Locale (enUS 12h; ptBR 24h)
Number system Locale (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 → outofrange 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

Key Date picker Time picker
Tab Enters → cycles through controls Same
Arrow Up/Down Previous/next week Increment by 1
Arrow Left/Right Previous/next day Switch HH ↔ MM
Page Up/Down Previous/next month Increment by 5 (minute) / 1 (hour)
Home / End Start/end of week n/a
Enter Selects date / time Selects
Esc Cancels Cancels

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

  • Outofrange / 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

  • *odal open* scalein 0.95 → 1 + fade (motionmedium)
  • *onth change* slide horizontal (motion-medium)
  • *ear picker* vertical scroll list (in-place, no slide)
  • *ial click* hand rotates to target position

    (motionemphasizeddecelerate)

  • *ange drag* tonal fill grows in real-time
  • Reduced motion: instant transitions; no rotation animation

    (snap dial)

R11 — Per-preset variation

Preset Date visual Time visual
material3 Tonal selection, rounded buttons Dial first, input toggle
material2 Solid accent selection Dial first, no input toggle
ios_cupertino Wheel pickers stacked (yearmonthday) Wheel pickers (HH/MM)
gnome Adwaita calendar w/ accent today Spin buttons for HH/MM
windows_11 Combo button revealing flyout Combo button revealing flyout
brutalist Sharp grid, no animation Sharp dial, no rotation
terminal_classic Text prompt YYYY-MM-DD> Text prompt HH:MM>

R12 — Density

Density Cell size Footer height
Compact 36 px 48 px
Default 40 px 52 px
Comfortable 48 px 64 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 firstdayof-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

Source: ../home/koder/dev/koder/meta/docs/stack/specs/components/pickers.kmd