Skip to content

Date picker / calendar

In review
Live · web

Field + popover · single date

Due date

Selected: Jun 18, 2026

Inline · min/max bounds · today marked

June 2026
MoTuWeThFrSaSu
No past dates · selectable through Jun 30

Selected: Jun 15, 2026

Range · start → end with in-range tint

June 2026
MoTuWeThFrSaSu
Click start, then end · click again to restart

Range: Jun 9, 2026 → Jun 13, 2026 · 5 days

// Web: React, consuming @dizyx/nockerl-tokens. (Web is the laggard platform;
// this is the intended API, not yet exported from @dizyx/nockerl-react (promotion tracked).
// Neither app ships a custom calendar, so the web demo is designed from the laws.)
export function ScheduleForm() {
const [due, setDue] = useState<Date | null>(null);
const [span, setSpan] = useState<{ start: Date | null; end: Date | null }>({ start: null, end: null });
return (
<>
{/* Field + popover, single date, no past dates. */}
<DatePicker label="Due date" value={due} onChange={setDue} min={new Date()} />
{/* Always-open inline calendar. */}
<DatePicker label="Run on" value={due} onChange={setDue} inline />
{/* Range mode: two-endpoint range with in-range tint (absorbs the former DateRangePicker). */}
<DatePicker range label="Window" value={span} onChange={setSpan} />
</>
);
}
PropTypeDefaultDescription
label *stringPersistent field label above the trigger (never placeholder-as-label).
value *Date | nullSelected date (controlled). null renders the empty Pick a date prompt.
onChange *(date: Date) => voidFires when a day is picked (click, or Enter/Space on the focused day).
inlinebooleanfalseWhen true, the calendar is always open (no field trigger / popover).
minDateInclusive earliest selectable day. Earlier days render disabled but legible.
maxDateInclusive latest selectable day. Later days render disabled but legible.
weekStartsOn0 | 11First column weekday: 1 = Monday (Nockerl default), 0 = Sunday.
disabledbooleanfalseInert and clearly seen (never faded to invisible). The trigger is non-interactive.

Range mode (range) swaps value/onChange for value: { start: Date | null; end: Date | null } and a matching onChange; the two endpoints get the accent fill and the days between get the in-range tint. (The former standalone DateRangePicker was merged into this range mode; recover its preset-rail / dual-month demo from git history if that richer surface is revived.)

Cross-platform drift (tracked for reconciliation)
  • No shipped custom calendar. Neither Android nor Voice draws its own month grid. Android stores dueDate as an ISO string and only formats it; Voice formats a chart axis. Kotlin/Swift above document the stock Material 3 / SwiftUI pickers; the web island is designed originally from the laws (recessed field-trigger, lifted calendar card, cyan selected fill, today ring, in-range tint).
  • Range: web ships range as a mode of DatePicker (the standalone DateRangePicker was merged in); SwiftUI has no range picker (model it as two DatePickers); Material 3 has DateRangePicker but the apps don’t use it yet.
  • Week start (resolved): weekStartsOn defaults to the device locale, with an explicit prop override. The Nockerl web demo (Monday-first) and the stock pickers converge on that shared default + prop.