# DateField

_Box Kite 2.2.6 · a markdown copy of https://www.box-kite.dev/date-field/_

A date, a time or both, typed one segment at a time in the reader's own order — and the value is an ISO string with its UTC reading beside it.

**Import**

```jsx
import DateField from '@box-kite/react/components/dateField';
```

**Usage**

```jsx
<DateField label="Due" value={value} onValueChange={setValue} />
```

## Date, time, or both

`type` says what it edits, and the value is the string of that kind: `2026-10-19`, `14:30:00`, or for a date-time the instant it names on the field's zone. A time is a 12- or 24-hour clock the way the locale reads one, and `granularity="second"` adds the seconds.

```jsx
<DateField type="time" label="Opens at" />
<DateField type="time" label="Lap" granularity="second" hourCycle={24} />
<DateField type="datetime" label="Meeting" />
```

## UTC or local

A date-time is typed on somebody's wall clock, and `zone` says whose: `'local'` (the default, the reader's), `'utc'`, any IANA zone, or `'none'` for a wall clock that belongs to nobody — an alarm, a timetable. A zone that is not the reader's is named after the time, so nobody types a New York time thinking it is theirs.

```jsx
<DateField type="datetime" zone="America/New_York" label="Starts (New York)"
  onValueChange={(value, { utc, local }) => save(utc)} />
```

`onValueChange` hands over every reading an app is likely to want beside the string: `utc`, the reader's `local` wall clock, the field zone's `zoned` one, a `Date` and the epoch — and `null` for all of them while a segment is empty.

## The reader's order, with nothing to parse

Free text is ambiguous — `03/04/2026` is two different days depending on who typed it — so the field never parses what was typed. Each part is its own spin button, laid out in the order and with the separators `Intl` writes for the locale, and the reader types numbers into the parts.

```jsx
<DateField label="ro-MD" locale="ro-MD" defaultValue="2026-10-19" />
```

A paste is the one place text is read, because a whole value arrives at once: ISO in any spelling, or a date the way the reader writes one.

## Typing it

Digits move on by themselves

`4` in a month is April at once; `1` waits for a `0`, `1` or `2`. A separator key moves on too, the way it would in a text box.

The arrows step and wrap

Up and Down step a segment and wrap like a clock; Page Up and Page Down take a bigger step; Home and End go to its ends. An empty segment starts from today.

Read out in the reader's words

Each segment is named by its unit in the reader's language (`Monat`, `месяц`), and a month reads as its name.

## In a form

`name` posts the canonical string, so a server reads `2026-10-19` whatever the reader's locale. `required` is a real input the browser validates, so an empty field stops the form with the browser's own message, and a form reset puts the default back. `min`, `max` and `isDateUnavailable` mark a value outside them `aria-invalid` without throwing it away.

```jsx
<DateField label="Check-in" name="checkIn" required min="2026-10-19" isDateUnavailable={(day) => day.endsWith('-25')} />
```

## Styling

The frame is `textbox`'s, extended: `dateField` with `compact`, `invalid` and `disabled` variants, and `dateField.segment`, `dateField.literal`, `dateField.zone` and `dateField.label` inside it.

```jsx
Box.components({
  dateField: {
    children: { segment: { styles: { focus: { bgColor: 'emerald-600' } } } },
  },
});
```

## DateField props

Everything below is this component’s own. All 235 of Box’s style props work on it too, and those are on [/box](https://www.box-kite.dev/box.md) rather than repeated here.

| Prop | Type | Default | What it does |
| --- | --- | --- | --- |
| `type` | `DateFieldType` | `'date'` | What it edits: a day (`'date'`, default), a time of day (`'time'`), or both (`'datetime'`). |
| `value` | `string \| null` | — | Controlled value, an ISO string of the field's kind: `2026-10-19`, `14:30:00`, or for a date-time an instant with its offset (`2026-10-19T14:30:00+03:00`), `…Z` in UTC, or a wall clock with `zone="none"`. `null` is empty. Any spelling `parse` reads is accepted; what comes back is canonical. |
| `defaultValue` | `string \| null` | `null` | The value it starts with when the field owns its state. |
| `onValueChange` | `ChangeHandler<string \| null, DateChangeReason, DateChangeDetails>` | — | Fires with the new value — `null` while a segment is empty — and its readings: `utc`, the reader's `local` wall clock, a `Date`, the epoch, and why it changed. |
| `zone` | `ZoneInput \| 'none'` | — | Whose wall clock a date-time is typed on. Default `'local'`, the reader's; `'utc'`, any IANA zone or `+03:00` for another; `'none'` for a wall clock with no zone at all (an alarm, a timetable). A date and a time have no zone, so this only says what "today" is for them. |
| `locale` | `string` | — | The language the segments are ordered and named in. Default the reader's. |
| `hourCycle` | `12 \| 24` | — | A 12- or 24-hour clock. Default the locale's. |
| `granularity` | `DateFieldGranularity` | `'minute'` | Whether a time is edited to the minute (default) or the second. |
| `min` | `string \| null` | — | The earliest value that is valid. A value before it is kept and marked `aria-invalid`. |
| `max` | `string \| null` | — | The latest value that is valid. |
| `isDateUnavailable` | `(date: string) => boolean` | — | Days nobody can choose — a weekend, a holiday. Handed the `YYYY-MM-DD` day. |
| `disabled` | `boolean` | `false` | Not editable and not focusable, and read out as such. |
| `readOnly` | `boolean` | `false` | Focusable and read out, but not editable. |
| `required` | `boolean` | `false` | The form is not valid until it is filled in — the browser's own check, announced and translated for free. |
| `invalid` | `boolean` | — | Marks the value invalid whatever the bounds say. Default: invalid when out of bounds or unavailable. |
| `name` | `string` | — | What the value posts under in a form: the canonical string, or nothing while empty. |
| `label` | `React.ReactNode` | — | The field's visible name, rendered above it. A group of segments is not named by its contents. |
| `labelledBy` | `string` | — | Names the field after an element already on the page, instead of `label`. |
| `describedBy` | `string` | — | An element that describes the field — a hint, an error. |
| `placeholderValue` | `string` | — | What an empty segment starts from on an arrow key. Default today, at midnight. |
| `disambiguation` | `Disambiguation` | — | What a wall-clock time a zone skips or repeats means. Default `'compatible'`. |
| `showZone` | `boolean` | — | Whether a date-time names its zone after the time. Default: when the zone is not the reader's own. |
| `end` | `React.ReactNode` | — | Something inside the field, after the segments — a picker's button. |
| `autoFocus` | `boolean` | — | Focus the first segment on mount. |
| `emptyText` | `string` | `'Empty'` | What an empty segment is read out as. Default `'Empty'`. |
| `tabIndex` | `number` | `0` | Every segment's tab index. Default `0`; `-1` keeps the field out of the page's tab order, which a grid cell wants. |

## DateField keyboard

| Key | Result |
| --- | --- |
| `Tab` | Every segment is its own tab stop. |
| `Left / Right` | The previous or next segment, in the reading order. |
| `Up / Down` | One more or one less, wrapping round the segment's range. |
| `PageUp / PageDown` | A bigger step: five years, two months, a week, two hours, fifteen minutes. |
| `Home / End` | The segment's lowest and highest value. |
| `0-9` | Types into the segment, and moves on once no further digit could fit. |
| `A / P` | AM or PM, in the AM/PM segment. |
| `Backspace` | Takes back the last digit, then empties the segment, then moves to the one before. |

## DateField accessibility

Implements [the WAI-ARIA APG pattern](https://www.w3.org/WAI/ARIA/apg/patterns/spinbutton/).

- The field is a `role="group"` named by `label`; each segment is a `role="spinbutton"` named by its unit in the reader's language (`Monat`, `месяц`), with `aria-valuetext` saying the month by name.
- An empty segment still carries `aria-valuenow` (its placeholder, which is what an arrow starts from) and reads as `emptyText`.
- `required` and `name` are a real input the browser validates, so an empty required field stops the form and the message is the browser's own.

Swept with axe on every release, in this state: `DateField`. No violations, with contrast and landmark rules left to a human. Screen-reader results are not published yet.

## DateField style tree

Every part the component draws is a node with a name, so a default can be restyled with `Box.components()` instead of a selector — and a variant is a name too.

`dateField`extends `textbox`variants: compact, invalid, disabled

`dateField.label`

`dateField.segment`variants: placeholder, disabled

`dateField.literal`

`dateField.zone`

`dateField.validation`

---

_Every page: https://www.box-kite.dev/llms.txt · every prop, measured: https://www.box-kite.dev/props.md_

