On this page
Date Field
A three-part form field for entering a day, month, and year separately
- Overview
- Rules and guidance
- Properties
- Styles
- Accessibility
Deprecated - dateField is deprecated. For most new work, use DatepickerField instead. DateField is documented here for teams maintaining existing code.
When to use
- When the user needs to enter a date they know by heart, such as their date of birth or passport expiry date
- When the date is historical and calendar navigation would be impractical
- When maintaining existing code that already uses DateField
When not to use
- For recent or upcoming dates - a calendar helps users pick dates they do not know precisely. Use DatepickerField instead.
- For any new work - dateField is deprecated. Use DatepickerField instead.
- To indicate that a field is unavailable - the disabled state is not supported. Use Summary List to show values that cannot be edited, or conditionally hide the field if it is not relevant in the current context.
Anatomy
- Label (describes the date being collected, rendered as a fieldset legend)
- Required/Optional indicator
- Hint (shown below the label, above the inputs; always include the example format)
- Day input (numeric input labelled "Day")
- Month input (numeric input labelled "Month")
- Year input (numeric input labelled "Year")
- Error message
Basic usage
Edit live code
() => { // ignore - for live code example only. const Example = () => { const [date, setDate] = useState({ day: '', month: '', year: '' }); return ( <DateField id="dob" label="Date of birth" hint="For example, 21 7 1997" value={date} onChange={setDate} /> ); } return <Example />; }
Variants and features
Controlled and uncontrolled
DateField accepts either value + onChange for controlled use, or defaultValue for uncontrolled use. The value type is { day: string; month: string; year: string }.
Controlled, with value and onChange. The parent owns the value, so the field shows whatever is in state:
Edit live code
() => { // ignore - for live code example only. const Example = () => { const [date, setDate] = useState({ day: '21', month: '7', year: '1997' }); return ( <Stack spaceBetween={3}> <DateField id="dob-controlled" label="Date of birth" hint="For example, 21 7 1997" value={date} onChange={setDate} /> <P>Current value: {date.day}/{date.month}/{date.year}</P> </Stack> ); } return <Example />; }
Uncontrolled, with defaultValue. The field manages its own value after the initial render:
Edit live code
<DateField id="dob-uncontrolled" label="Date of birth" hint="For example, 21 7 1997" defaultValue={{ day: '21', month: '7', year: '1997' }} />
Read-only
Use the readOnly prop to display a date that the user cannot edit.
Edit live code
<DateField id="dob-readonly" label="Date of birth" readOnly defaultValue={{ day: '21', month: '7', year: '1997' }} />
Common form features
The following features are shared across all Nebula form fields. See Form guidance for full detail.
Validation and errors: when a field is invalid, an error message appears below the inputs. DateField has no built-in date validation; validate the value in your own code before displaying an error.
Hint text: always include hint text showing the expected format: "For example, 21 7 1997". Accept both padded ("07") and unpadded ("7") values for day and month.
Required and optional: fields are required by default. Use the optional prop to mark a field as optional.
Field width: use fullWidth to control responsive behaviour. The default (small) makes the field full width below the smallest breakpoint and constrains it above. always keeps it full width at all breakpoints; never keeps it at its fixed width.
usage
- DateField must not be used for dates where calendar context is helpful, such as booking dates or appointment schedulingDateField provides no calendar interaction. Use DatepickerField for dates where the user benefits from seeing surrounding days or navigating by month.Copy rule ID for datefield.no-date-picker-use
- DateField must not be passed the disabled propThe prop passes through to the native inputs with no Nebula styling and removes the fields from the tab order, making them invisible to keyboard users with no accessible indication of why.Copy rule ID for datefield.no-disabled
- DateField should be used only for dates the user is expected to know from memoryDateField provides no calendar, making it unsuitable for dates the user needs to look up or navigate to. Use DatepickerField for recent or upcoming dates.Copy rule ID for datefield.known-dates-only
- DateField should be replaced with plain text. For example in a SummaryList. When displaying a value the user cannot editA field that looks interactive but cannot be changed is confusing. Plain text communicates the value without implying editability.Copy rule ID for datefield.non-editable-as-text
- DateField should not be used in new workDateField is deprecated. Use DatepickerField for most date inputs.Exception: maintaining existing code that already uses DateFieldCopy rule ID for datefield.deprecated
content
- DateField hint text should include an example date in the format "For example, 21 7 1997"Three separate inputs without an example format create uncertainty about whether day or month comes first, and whether zero-padding is required.Copy rule ID for datefield.hint-text-required
interaction
- DateField will set aria-invalid on all three day, month, and year inputs automatically when an error is presentCopy rule ID for datefield.will-aria-invalid-all-inputs
- DateField will derive the month input id as "{id}-month" and the year input id as "{id}-year" from the id prop automaticallyCopy rule ID for datefield.will-derived-ids
- DateField error message will be associated with the day input via aria-describedby. The component sets this automatically on the day inputCopy rule ID for datefield.error-links-to-day-input
- If value prop is providedDateField must provide both value and onChange when used in controlled modeProviding value without onChange makes the field read-only. The onChange handler receives the full DateFormat object; update all three parts to avoid losing the user's other inputs.Copy rule ID for datefield.controlled-requires-value-and-onchange
validation
- DateField must accept both padded ("07") and unpadded ("7") values for day and monthUsers should not be penalised for including or omitting leading zeros. Validate the semantic date value, not the string format.Copy rule ID for datefield.accept-padded-and-unpadded
- DateField must not rely on the component to validate the dateDateField has no built-in date validation. The consuming code is responsible for checking that the combination of day, month, and year forms a valid date.Copy rule ID for datefield.no-built-in-validation
DateField
| Name | Type | Default | Required | Description |
|---|---|---|---|---|
id | string | — | Yes | Id for the day input. The month and year inputs use `{id}-month` and `{id}-year`, and the error uses `{id}-error`. |
name | string | — | No | Name for the day input. The month and year inputs use `{name}-month` and `{name}-year`. Also used to look up the field's error from a surrounding `Form`. |
label | string | — | Yes | Legend for the group of day, month and year inputs. |
onChange | (date: DateFormat) => void | — | No | Called with the full `{ day, month, year }` value, with the changed part updated, when any input changes. Only called when `value` is provided. |
value | DateFormat | — | No | Controlled value as strings for `day`, `month` and `year`. Use together with `onChange`. |
defaultValue | DateFormat | — | No | Initial value for uncontrolled use, as strings for `day`, `month` and `year`. |
hint | ReactNode | — | No | Helper text shown below the label, inside the legend. |
error | ReactNode | — | No | Error message shown below the inputs. When set, all three inputs get `aria-invalid` and the day input is linked to the message with `aria-describedby`. Takes precedence over an error provided by a surrounding `Form`. |
optional | boolean | false | No | Marks the field as not required. This appends "(optional)" to the label (or, when the theme marks required fields, hides the required asterisk) and sets `aria-required` on the inputs only when the field is not optional. |
readOnly | boolean | false | No | Makes all three inputs read-only. |
fullWidth | always never small | 'small' | No | Controls when the field stretches to the full width of its container. `always` is always full width, `never` always uses a fixed width, and `small` is full width on small screens only. |
data-testid | string | — | No | Test id for the day input. The month and year inputs use `{data-testid}-month` and `{data-testid}-year`. |
Styles
inline-fields
display: flex;
margin-left: -${halfRem(space[2])};
margin-right: -${halfRem(space[2])};
> * {
margin-left: ${halfRem(space[2])};
margin-right: ${halfRem(space[2])};
}
@when fullWidth === 'small' {
${mediaQueries.smallAndUp} {
width: ${'26.5rem'};
}
}
@when fullWidth !== 'small' {
${fullWidth === 'never' ? ` width: ${dateWidth}; ` : ''}
}
day-field
width: 25%;
month-field
width: 25%;
year-field
width: 50%;
date-part
width: 100%;
In brief
Date Field is deprecated in favour of Datepicker Field. It is a group of three native inputs, Day, Month and Year, inside a fieldset whose legend is the label, so a screen reader announces each input with its own name and the group's.
What Nebula provides
- A fieldset and legend for the group, and a real label on each of the Day, Month and Year inputs.
- aria-required on all three inputs unless the field is optional, which adds "(optional)" to the legend.
- An error sets aria-invalid on all three inputs. The message has an icon as well as colour.
- The hint is part of the legend, so it is read as part of the group's name.
Keyboard behaviour
The three inputs are native inputs. Each is a tab stop and shows a focus ring when focus comes from the keyboard.
Focus order
- Day
- Month
- Year
Interactions
- Tab - Moves focus to Day, then Month, then Year
Accessible design
Keep the hint short
The hint sits inside the legend, so a screen reader reads it as part of the group's name. An example such as "For example, 21 7 1997" is enough.
WCAG: 3.3.2 Labels or Instructions
Write the error so it says what to change
"Enter your birthday" tells the user what to do and "Invalid" does not. The same message applies to all three inputs, so it has to make sense without saying which part is wrong.
WCAG: 3.3.3 Error Suggestion
Accessible development
Always pass errors through error, or through a Form with the field name
This is what sets aria-invalid on the three inputs and shows the message. Text placed beside the field is not announced with it.
WCAG: 3.3.1 Error Identification
If an error summary links to the field, link to the day input
The day input has the id you pass, and the month and year inputs use {id}-month and {id}-year. An ErrorSummaryNotification entry whose id matches links to Day, the first input.
WCAG: 3.3.1 Error Identification
Accessibility testing status
Reviewed on 9 October 2026 against v17.0.1.
| Test | Status | Tested with |
|---|---|---|
| Default state | Tested | axe-core via the Storybook test runner (CI) |
| Advanced states | Partially tested | axe-core via the Storybook test runner (CI) |
| Keyboard navigation | Manually tested | keyboard (reported by the owner) |
| Screen reader | Partially tested | VoiceOver with Chrome (reported by the owner) |
What these tests and statuses mean
The table records which checks were run on this component on its own. It is not a statement of WCAG conformance, and it does not cover how the component is used.
The tests
- Default state: The component as it first appears, before anything is clicked or changed.
- Advanced states: Every other state and variant: error, open, selected, each size and style, and hovering.
- Keyboard navigation: Whether every function can be reached and used with the keyboard alone, with a visible focus ring.
- Screen reader: Whether a screen reader announces what is on screen correctly: the name, the role and any state.
The statuses
- Tested: Every check in this row passed in an automated run that repeats on every pull request.
- Manually tested: Every check in this row was run once, by a person or with a tool they started, and all of them passed. It is not re-run on every change.
- Partially tested: Some checks in this row were run once and passed; the rest are still to do.
- Issue found: A check in this row failed. The ticket is shown beside the status.
- Not tested: No check in this row has been run yet.
- Not applicable: This row does not apply to the component, for the reason given beside it.