A two-click date-range grid with hover preview, a continuous selection track, and full keyboard roaming. Calendar-date math only — no timezone drift, no date library.
Single monthTwo monthsBoundedCompact
June 2026
July 2026
2026-06-08 → 2026-06-17
<RangeCalendar bind:start bind:end months={2} onchange={apply} />Installation
pnpm add @chanrg/4uiImport the stylesheet once, at the root of your app:
import '@chanrg/4ui/styles.css';Usage
import { RangeCalendar } from '@chanrg/4ui';<RangeCalendar bind:start bind:end months={2} onchange={apply} />Two months
Show two grids side by side when the range usually spans a month boundary.
June 2026
July 2026
2026-06-08 → 2026-07-02
<RangeCalendar bind:start bind:end months={2} />Bounded
Pass min and max to clamp selection; month navigation disables at the edges.
June 2026
Selectable 2026-06-08 → 2026-06-22
<RangeCalendar bind:start bind:end min="2026-06-01" max="2026-06-30" />Compact
Denser cells for a calendar embedded in a narrow form column.
June 2026
2026-06-08 → 2026-06-12
<RangeCalendar bind:start bind:end compact />API reference
Props
| Prop | Type | Default | Description |
|---|---|---|---|
start / end | 'YYYY-MM-DD' | null | null | Range endpoints; bindable and auto-ordered low→high. |
min / max | 'YYYY-MM-DD' | null | null | Selectable bounds; also disables month navigation past them. |
months | 1 | 2 | 1 | Side-by-side grids. |
weekStartsOn | 0 | 1 | 1 | 0 = Sunday, 1 = Monday. |
locale | string | runtime | Month and weekday labels. |
compact | boolean | false | Denser cells for forms. |
onchange | ({ start, end }) => void | — | Fires once a full range is picked. |
CSS variables
| Variable | Description |
|---|---|
--4ui-surface | Primary component surface. |
--4ui-text | Primary foreground color. |
--4ui-border | Default control and panel border. |
Accessibility
- The grid uses role=grid/row/gridcell with a roving tabindex; arrows move by day and week, Enter or Space picks.
- Endpoints expose aria-selected and out-of-bounds days are disabled and aria-disabled.
- Every cell carries a fully localized date label, so the range never depends on reading the column header.