Pular para o conteúdo

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.

Quando esta spec se aplica

Triggers primários

Todos os triggers

Corpo da especificação

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"

Referências