Date Field

Segmented date input.

Usage

Compose Root > Label + Input, and render a Segment per part from the snippet Input hands you. Values are DateValue objects from @internationalized/date, a peerDependency.

Departure date
mmddyyyy

Why not a native input

This is not a text input with a mask. Every part is its own role="spinbutton" with aria-valuenow, aria-valuetext and its own aria-label — so arrow keys adjust it and a screen reader announces "month, 12" instead of reading a formatted string.

Typed numbers fill the focused segment and advance to the next, and the value is only committed once every segment is filled — so there is no half-parsed intermediate state to guard against in your code.

So the trade against <input type="date"> is: the same keyboard model, with markup and styling you control and identical rendering across browsers instead of whatever each vendor decided.

The separators are rendered too, as data-segment="literal" with aria-hidden — otherwise the field would announce "slash" between the parts. Key your each by index: segment parts repeat, so keying by part throws.

Locale reorders the field

locale decides the segment order, not just the labels.

en-US renders month / day / year; en-GB renders day / month / year. Passing the wrong locale does not merely relabel the field — it rearranges it, and a user typing their own date format into the wrong order silently enters the wrong date. That is a correctness bug, not a preference, and it is asserted in the tests.

Granularity

granularity decides which segments exist at all. day (the default) gives a plain date; minute adds hour and minute segments, plus AM/PM in a 12-hour locale. Ask for the smallest unit you actually need — every extra segment is another thing the user has to fill.

Props

DateField.Rootvalue and placeholder are bindable.

PropTypeDefault
value The date value. Bindable. A `DateValue`, not a JS `Date`.DateValue | undefined
onValueChange Called when the value changes.(value: FieldValue) => void
placeholder Which date the empty segments are formatted from. Bindable.DateValue
minValue Earliest accepted value.DateValue
maxValue Latest accepted value.DateValue
granularity Smallest unit the user can edit, which decides which segments appear. `day` gives a plain date; `minute` adds time segments.'day' | 'hour' | 'minute' | 'second''day'
locale BCP-47 locale. It decides the SEGMENT ORDER — month/day/year versus day/month/year — so getting it wrong does not just relabel the field, it rearranges it. Pass the user's locale.string'en'
name Name for form submission; Bits renders a hidden input.string
required booleanfalse
disabled booleanfalse
readonly booleanfalse
validate Return a message to mark the value invalid. Bits then sets `data-invalid` and wires `aria-invalid` on the segments.(value: never) => string | string[] | void
class Extra classes merged onto the root.string
children Snippet

Label, Input and Segment take class plus their native attributes. Segment needs the part from the snippet.