Date Picker

Organism

Calendar surface for selecting a single date, a range, a month, or a year. Floats as a panel under a Text Input trigger (the input shows the chosen date; click it to open the picker). Mirrors Figma node 105-1786 / 105-2329 / 105-2431 (light) + 5913-1594 / 5913-1609 / 5913-1616 (dark) 1:1. Panel chrome matches Dropdown — same `--shadow-3`, 8px radius, 1px border. Cells invert on Active state (dark fill + white text in light, white fill + dark text in dark).

When to use

✅ Do
  • Use for date selection in forms (booking, filtering, scheduling)
  • Use the Range variant for start/end date selection (check-in/check-out, date filters)
  • Always attach to a Text Input trigger that shows the selected date
  • Pre-select today or the most common default date to reduce user effort
❌ Don't
  • Use for time selection — the picker is date-only
  • Use without a Text Input trigger — the panel must always float below a trigger element
  • Allow past dates when the context requires future dates only
  • Use inline (always visible) for non-primary date fields — keep it as a floating panel

DigiLawyer Patterns

No specific DigiLawyer patterns documented yet — to be added.

Anatomy

A Date Picker is a floating panel with a header, weekday row, and day grid. Panel chrome matches Dropdown.

June 2026
MoTuWeThFrSaSu
  1. 1Panel.datepicker. Floating container, 320px wide, border + shadow-3.
  2. 2Header.datepicker-header. Nav buttons + centered month/year title.
  3. 3Nav buttons.datepicker-nav. Previous/next month. Arrow icons.
  4. 4Weekday row.datepicker-weekdays. 7 column headers (Mo–Su).
  5. 5Day grid.datepicker-grid. 7-column grid of day cells.
  6. 6Day cell.datepicker-day. States: .is-active (selected, inverts), .is-today, .is-other (dimmed).

Default

A .datepicker panel sized 320px wide. Header has previous / next nav buttons + a centered title. Below it sits the 7-column weekday row and a 7-column day grid. Days from the previous / next month dim via .is-other; the currently selected day uses .is-active.

Variants

Four picker variants. Single is the day grid above. Range shows two months side-by-side; range endpoints use .is-active, the days between use .is-in-range. Month picker swaps the day grid for a 4×3 month-name grid. Year picker swaps for a 4×3 year grid (12 years at a time, navigated with the same prev/next arrows).

States

Five cell states. Default (no class). Hover — pointer-driven, also forced via .is-hover for static demos. Active (.is-active or aria-pressed="true") — the selected date / month / year. In-range (.is-in-range) — the days between two range endpoints. Disabled / Other-month (.is-disabled / .is-other) — non-selectable cells dimmed via --datepicker-cell-text-disabled.

Interactive

A real Text Input wired to a Date Picker — click the input to open the calendar, click a date to fill the input and close the picker, click outside to dismiss. The calendar navigates months with ‹/›; the title is itself a button that flips the view to Month picker → Year picker → back to Day. All state lives in the page script; lifting it into ds.js would make this a reusable widget.

Click the field to open the calendar.

Full matrix

Cell states across the three picker views — Day / Month / Year. Each row shows the same five states applied to the same cell-type so you can compare visual treatment at a glance.

Default
Hover
Active
In range
Disabled
Day
Month
Year

Accessibility

Follows the WAI-ARIA Date Picker Dialog pattern.

Attribute / BehaviourValueNotes
Panel rolerole="dialog" + aria-modal="true"The calendar panel is a modal dialog when open
Triggeraria-haspopup="dialog" + aria-expandedSet on the Text Input trigger to communicate open/closed state
Keyboard — openEnter on triggerOpens the calendar panel
Keyboard — navigate days Move focus between day cells
Keyboard — selectEnter / SpaceSelects the focused day and closes the panel
Keyboard — closeEscCloses without selecting; returns focus to the trigger
Nav buttonsaria-label="Previous month" / "Next month"Icon-only buttons must have descriptive labels

API

Class / attrOnDescription
.datepicker<div role="dialog">Panel wrapper. 320px wide, 16px padding, white surface, 8px radius, --shadow-3 drop, 1px border. Holds the header + a content grid (day / month / year).
.datepicker-range.datepickerTwo-month side-by-side variant. Width is auto (sum of the two .datepicker-month children + gap).
.datepicker-month<div>Inner month container, used only inside .datepicker-range to host one of the two months.
.datepicker-header<div>Top row — flex with previous nav arrow + title + next nav arrow.
.datepicker-nav<button>The ‹ / › arrow buttons. 24×24, transparent, 16×16 icon. Hover uses --datepicker-cell-bg-hover.
.datepicker-title<button>The "July 2024" centered title. Click cycles the view to the Month picker when a dropdown chevron is shown.
.datepicker-weekdays<div>7-column grid of weekday abbreviations (Mo Tu We Th Fr Sa Su). Body 2 Medium typography.
.datepicker-days<div>7-column grid of .datepicker-cell buttons. Padded leading / trailing days from the prev / next month carry .is-other.
.datepicker-months<div>4-column grid of 12 month names. 16px row gap.
.datepicker-years<div>4-column grid of 12 years. Same row gap as months.
.datepicker-cell<button>The day / month / year cell. 32px tall (36px in month/year grids), centered content, 8px radius, Body 2 Medium typography.
.is-active / aria-pressed="true".datepicker-cellMarks the selected cell. Inverts via tokens — dark fill + white text in light, white fill + dark text in dark.
.is-in-range.datepicker-cellRange middle — uses --datepicker-cell-bg-in-range and drops the cell's border-radius so neighbours flow together visually.
.is-range-start / .is-range-end.datepicker-cellThe endpoint cells in a range. They keep their .is-active fill but drop the matching corner radius so they butt up against the in-range neighbour.
.is-disabled / disabled.datepicker-cellNon-selectable cell — dims via --datepicker-cell-text-disabled, cursor not-allowed.
.is-other.datepicker-cellPadded day from prev / next month. Same dimmed colour as Disabled but the cursor stays default.