SWIVEL · 08 · DRAFT

Date & Time.

We don't reinvent the date picker. Swivel uses Material Design 3's date and time components — their structure, keyboard model, and ARIA — and recolors every surface, container, and accent to the Swivel palette.

01

Policy

What we adopt and what we override.

Date and time pickers are deceptively expensive to build well — keyboard nav, locale, range selection, year jumps, accessibility. Material Design 3 already solves all of that. So we inherit the M3 component specs end-to-end and only override the color tokens. Visually, a picker reads as Swivel; structurally and behaviorally, it reads as M3.

What we inherit from M3

  • Component structure — modal and docked date pickers, single + range modes, dial + numeric time pickers.
  • Interaction model — arrow-key navigation, type-to-jump, year/month dropdowns, click-and-drag range selection, dial drag.
  • Accessibility — ARIA roles, focus rings, screen-reader landmarks, RTL handling, locale-aware day-of-week order.
  • Layout dimensions — header height, grid cell size (40×40 single, 36×36 range), elevation levels, motion timings.

What we override

  • Color tokens only. Every primary, surface, outline in the M3 token tree maps to a Swivel token (table below).
  • Type falls back to our display + body stack (Avantt / Antarctica). M3's "Roboto" recommendation is replaced by the platform font tokens.
  • Radius follows our radius scale. M3's 16px container becomes --radius-xl (12px) so pickers don't look out of family.
02

Token mapping

M3 color roles → Swivel tokens.

Drop these CSS custom properties at the picker's root and the M3 component sheet picks them up unchanged. Every example below is rendered against exactly this mapping.

M3 role Swivel token Where it lands
primary --switch-contrast Selected day fill, time-dial hand, focused outline, primary text buttons
on-primary --graphite-contrast Day number, dial mark, and range endpoint on the selected fill. Not --white: switch cyan is bright in both themes, so white ink reads at ~1.4:1 in dark mode.
primary-container --switch-bg Range fill between start/end, active hour/minute readout
surface --surface-base Picker plate
surface-container --surface-muted Header band, time dial background
outline --surface-stroke Outer hairline, text-field border
outline-variant --graphite-10 Inner dividers between header and grid
on-surface --graphite-90 Headline, day numbers, hour/minute readouts
on-surface-variant --graphite-bg Eyebrow, day-of-week row, muted days outside the month
03

Date picker

M3 single-date modal — recolored.

Use for single-date selection — campaign start, deploy on, archive after. Falls back to the date-input field (§06) on dense forms where opening a picker is overkill.

Idle · today highlighted
Select date

Mon, May 18

May 2026 arrow_drop_down
Selection · May 22
Select date

Fri, May 22

May 2026 arrow_drop_down
04

Date range

Two-month grid with range fill.

Use for any "from / to" pair — campaign flight, report window, analytics range. Range fill uses --switch-bg so the bracket reads as soft accent rather than a second selection.

Select date range

Mon, May 12 → Fri, May 22

April 2026
May 2026
05

Time picker

Dial picker for hour selection.

Default to the dial. The active hour/minute readout fills with --switch-bg so the user always knows which segment they're editing. AM/PM uses an outlined toggle pair with the active state filled.

Select time
02
:
30
06

Date input field

M3 outlined text field with calendar trailing icon.

For dense forms where opening a picker is friction. Accepts MM/DD/YYYY typed input; the trailing icon opens the modal picker on click. Same focus and error states as the rest of our text fields.

Start date calendar_today
Start date calendar_today
Start date calendar_today
Start date calendar_today Invalid date
07

Usage

When to pick which.
  • Date input field — default for forms. Compact, types-or-clicks, validates inline.
  • Date picker (modal) — when the user needs a calendar view to orient: holidays, weekends, "next available."
  • Date range — any from/to pair. Never present two separate date pickers — the range component handles the relationship.
  • Time picker (dial) — default for time. Faster for round times (00, 15, 30, 45).
  • Time picker (numeric) — when sub-minute precision is needed, or in dense forms. M3 toggle between dial and numeric is preserved.
  • Don't roll your own. If a picker feels wrong, fix the token mapping, not the component.