Use a date field for dates people know or can copy from a document: last service date, registration date, MOT expiry on a certificate, a driver's date of birth. Use a date picker when people need to find a date relative to today (booking a bay next Tuesday).
Ask the question in the legend ("When was the vehicle last serviced?") and give an example in the hint ("For example, 14 3 2026").
Plain text inputs with inputmode="numeric" — not type=number (spinners, lost leading zeros).
Accept "3", "03" and month names ("March"); check real dates (no 31 February) and ranges ("must be in the past").
Never auto-advance between inputs.
Anatomy
Fieldset + legend — the question (fieldset.fieldset.date-field)
Hint — example date, linked to the fieldset
Error (optional) — one message for the whole date
Day, Month, Year — .date-field__item each with a label and a .input.date-field__input sized in characters
Examples
Hover and focus-visible are frozen with .is-hover and .is-focus-visible, which share their rules with the real pseudo-classes.
Date field
<fieldset class="fieldset date-field" aria-describedby="service-hint service-error" [data-locale-order="en-US"]>
<legend class="fieldset__legend">When was the vehicle last serviced?</legend>
<p class="field__hint" id="service-hint">For example, 14 3 2026</p>
<p class="field__error" id="service-error">…<span class="sr-only">Error: </span>Date the vehicle was last serviced must include a year.</p>
<div class="date-field__inputs">
<div class="date-field__item" data-part="day">
<label class="label" for="service-day">Day</label>
<input class="input date-field__input date-field__input--2ch" id="service-day" name="service-day" type="text" inputmode="numeric">
</div>
<div class="date-field__item" data-part="month">…Month (--2ch)…</div>
<div class="date-field__item" data-part="year">…Year (--4ch), aria-invalid="true" only on the part that is wrong…</div>
</div>
</fieldset>
<!-- Birth dates: autocomplete="bday-day | bday-month | bday-year" -->
API
Hook
Values
Purpose
.date-field (+ .fieldset)
block class on fieldset
Groups the three parts under the question
.date-field__inputs, .date-field__item[data-part]
parts
Row of labelled inputs; data-part = day | month | year
.date-field__input--2ch | --4ch
modifier class
Character-based widths (day/month, year)
aria-invalid="true"
attribute on inputs
Only on the wrong part(s); all three when the whole date is wrong
disabled on the fieldset
attribute
Disables every part
data-locale-order
attribute (js/date-field.js)
Reorders parts for a locale (empty = page locale)
autocomplete="bday-*"
attribute
Birth dates only
Do and don't
Do ask a clear question, label each part and show an example.Don't use one masked field or a placeholder as the only format hint.
Accessibility
Keyboard interaction
Key
Behavior
Tab
Day → Month → Year (in locale order); focus never jumps on its own
Typing
Numbers, or a month name in Month (normalised on blur)
Group: fieldset + legend, so each input is announced as "When was the vehicle last serviced?, group, Day, edit text".
Hint and error are linked to the fieldset with aria-describedby; aria-invalid marks only the part(s) to fix.
No auto-advance: it breaks correcting a typo and confuses screen-reader users.
Locale order changes the DOM, so reading and tab order match what is seen.