Calendar entry without ambiguity, keyboard included.
| Prop | Type | Default | Description |
|---|---|---|---|
| mode | single · range | single | One date, or a start→end span |
| value | Date · {start, end} | — | Controlled selection |
| onChange | (value) => void | — | Fires on each committed pick |
| weekStartsOn | 0 · 1 | 0 | Sunday or Monday as the first column |
| min / max | Date | — | Bounds; outside days are disabled |
| initialDate | Date | today | Which month opens first |
| Key | Result |
|---|---|
| ← → ↑ ↓ | Move focus by a day or a week |
| PageUp / PageDown | Move by a month |
| Home / End | Jump to the start or end of the week |
| Enter / Space | Select the focused day |
| Title click | Jump days → months → years, then back down |
Let the title jump. Reaching a birthday in 1994 should take two clicks, not forty.
Don't trap the user in single-month steps. Distant dates become a scavenger hunt.
Echo the selection back in words. A readable readout confirms the pick was understood.
Don't leave the format ambiguous. "Jul 8" never gets confused with the 7th of August.
Check-in to check-out: the range fills the span so the length of stay is obvious.
Collapsed into a field, the picker opens in a popover only when the user needs it.
Presets do the common jobs; the calendar handles the one-off "custom range".
| Token | Value | Role |
|---|---|---|
| --cal-w | 264px | Panel width (7 × 32 + gaps + padding) |
| --cal-bg | #0E0F12 | Panel surface |
| --cal-border | #1E1F24 | Panel & footer hairline |
| --cal-day | 32px · radius 8 | Day cell hit target |
| --cal-selected | #FF7A00 | Selection & range endpoints, near-black label |
| --cal-range | rgba(255,122,0,.14) | In-range span fill |
| --cal-today | #FF9D45 | Today text + a #FF7A00 dot |
| --cal-muted | #4A4D55 | Outside-month days |
| --cal-focus | rgba(255,122,0,.55) | 2px keyboard focus ring |
<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>