/// Inputs
DateInput
Segmented day/month/year field with spinbutton segments — the keyboard-first, type-to-fill alternative to the calendar.
import { DateInput } from "@protocore/pds";Basics
Each of the day, month, and year segments is a spinbutton. Type digits to fill (focus auto-advances when a segment is full) or use the arrow keys to step. A complete, valid date is emitted via onValueChange; incomplete input emits null.
Locale-aware order
Segment order and separators come from the locale via Intl.DateTimeFormat — en-US renders month · day · year with slashes, de-DE renders day · month · year with dots. Nothing to configure beyond the locale.
In a Field, bounded
Wrap it in a Field for a label and hint, and constrain valid input with min / max. A complete date outside the window is treated as invalid (emits null).
Type it — DD / MM / YYYY.
When to use it
Use DateInput whenever users know the date and would rather type it than hunt through a grid — dates of birth, invoice dates, anything far from today. It is the most accessible date control in the library: no popover, no pointer required, every segment reachable by keyboard. Pair it with a DatePicker when some users prefer to browse a calendar; they share the same value contract. Use Calendar when the grid should be visible inline.
Usage
Do
- Give the field an accessible group name via a `Field` label or `aria-label`.
- Prefer DateInput for distant dates (birthdays, historical records) — typing beats scrolling.
- Let the locale drive segment order instead of hard-coding DD/MM/YYYY.
- Read the emitted `Date` (or `null`) rather than parsing the rendered text.
Don't
- Don't add your own separators or masking on top — the segments own formatting.
- Don't treat an incomplete field as a date; `onValueChange` reports `null` until it's valid.
- Don't remove the arrow-key stepping — it's a core affordance of a spinbutton.
- Don't wrap each segment in its own label; the whole field is one labelled group.
Accessibility
| Keys | Action |
|---|---|
| 0–9 | Fill the focused segment; focus auto-advances when it's full. |
| Arrow Up / Down | Increment / decrement the focused segment (wraps at the bounds). |
| Arrow Left / Right | Move between segments. |
| Backspace / Delete | Clear the focused segment. |
- Each segment is a `role="spinbutton"` with `aria-valuemin` / `aria-valuemax` / `aria-valuenow` and a name (Day, Month, Year), so screen readers announce it like a native stepper.
- The segments are grouped in a `role="group"` that carries the field's accessible name and `aria-invalid`.
- No pointer is required for any operation — the control is fully keyboard-operable.
DateInput props
| Prop | Type | Default | Description |
|---|---|---|---|
aria-label | string | — | Accessible group label describing the field (e.g. "Invoice date"). |
aria-labelledby | string | — | Id of an element labelling the field (e.g. a `Field` label). |
className | string | — | |
defaultValue | Date | null | — | Initial value in uncontrolled mode. |
disabled | boolean | — | Disable the whole field. |
invalid | boolean | — | Mark the field invalid — danger border plus `aria-invalid`. |
locale | string | — | BCP-47 locale controlling segment order and separators. |
max | Date | — | Latest valid day (inclusive). |
min | Date | — | Earliest valid day (inclusive) — a complete date outside the range is treated as invalid. |
onValueChange | ((date: Date | null) => void) | — | Fires with a complete valid `Date`, or `null` when the field is cleared/incomplete. |
size | enum | md | Field height: `sm` (32) · `md` (36, default) · `lg` (40). |
style | CSSProperties | — | |
value | Date | null | — | Controlled value. Pass `null` for an empty field. |