Date Picker

Calendar entry without ambiguity, keyboard included.

v1.0.0·Updated Jul 8, 2026 · 10:45
Live Demo — Fully Interactive
Click the title to jump to months, then years — Home/End, arrows and PageUp/Down navigate
Configuration
Mode
Week starts
Constraints
Footer
<DatePicker mode="single" />
Behavior — The Engineering Contract
Props
PropTypeDefaultDescription
modesingle · rangesingleOne date, or a start→end span
valueDate · {start, end}Controlled selection
onChange(value) => voidFires on each committed pick
weekStartsOn0 · 10Sunday or Monday as the first column
min / maxDateBounds; outside days are disabled
initialDateDatetodayWhich month opens first
Events & keyboard
KeyResult
← → ↑ ↓Move focus by a day or a week
PageUp / PageDownMove by a month
Home / EndJump to the start or end of the week
Enter / SpaceSelect the focused day
Title clickJump days → months → years, then back down
Live behavior — every demo is real
Optimistic reschedule · min = todayPATCH
Event is on Jul 11, 2026
pick a new date to reschedule
Past dates are struck through and unclickable. Rescheduling reflects instantly, then the server confirms — one in three attempts hits a conflict and honestly rolls back. Arrow keys, PageUp/Down and Home/End all navigate.
Anatomy
July 2026
293012345678910111213141516171819202122232425262728293031123456789
Jul 15, 2026Today
12345
1NavigationPrev / next arrows and a title that jumps to the month and year grids.
2Weekday rowFixed labels in the machine's mono voice; Sunday or Monday first.
3Day gridSix weeks, always. Outside-month days stay visible but dimmed.
4StatesToday wears a warm dot; the selection is the one solid fill; a range tints the span.
5FooterA plain-language readout of the choice, and a Today shortcut back to now.
Do's and Don'ts
✓ Do
Title → months → years

Let the title jump. Reaching a birthday in 1994 should take two clicks, not forty.

✕ Don't
‹ only, one month at a time

Don't trap the user in single-month steps. Distant dates become a scavenger hunt.

✓ Do
Jul 12 → Jul 18

Echo the selection back in words. A readable readout confirms the pick was understood.

✕ Don't
07/08/2026 — which is it?

Don't leave the format ambiguous. "Jul 8" never gets confused with the 7th of August.

In context: Examples
A · A booking range
July 2026
293012345678910111213141516171819202122232425262728293031123456789
Jul 12 → Jul 18Today

Check-in to check-out: the range fills the span so the length of stay is obvious.

B · A due-date field
Jul 15, 2026

Collapsed into a field, the picker opens in a popover only when the user needs it.

C · A date filter
TodayLast 7 daysThis monthCustom range…

Presets do the common jobs; the calendar handles the one-off "custom range".

Measurements & Tokens
TokenValueRole
--cal-w264pxPanel width (7 × 32 + gaps + padding)
--cal-bg#0E0F12Panel surface
--cal-border#1E1F24Panel & footer hairline
--cal-day32px · radius 8Day cell hit target
--cal-selected#FF7A00Selection & range endpoints, near-black label
--cal-rangergba(255,122,0,.14)In-range span fill
--cal-today#FF9D45Today text + a #FF7A00 dot
--cal-muted#4A4D55Outside-month days
--cal-focusrgba(255,122,0,.55)2px keyboard focus ring
Code
<div class="o-cal">
  <div class="o-cal__head">
    <button class="o-cal__nav" aria-label="Previous">‹</button>
    <button class="o-cal__title">July 2026</button>   <!-- click to jump -->
    <button class="o-cal__nav" aria-label="Next">›</button>
  </div>
  <div class="o-cal__wk"><span>Su</span>…<span>Sa</span></div>
  <div class="o-cal__grid" role="grid">
    <button class="o-cal__day is-today">8</button>
    <button class="o-cal__day is-selected" aria-selected="true">15</button>
    <button class="o-cal__day" disabled>16</button>
  </div>
</div>