# Date Field
A three-part form field for entering a day, month, and year separately
Section: components
Docs: https://nebula.kaluza.com/components/date-field
---
> **Deprecated** - dateField is deprecated. For most new work, use [DatepickerField](/components/datepicker-field) 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](/components/datepicker-field) instead.
- **For any new work** - dateField is deprecated. Use [DatepickerField](/components/datepicker-field) instead.
- **To indicate that a field is unavailable** - the disabled state is not supported. Use [Summary List](/components/summary-list) to show values that cannot be edited, or conditionally hide the field if it is not relevant in the current context.
## Anatomy
1. **Label** (describes the date being collected, rendered as a fieldset legend)
2. **Required/Optional indicator**
3. **Hint** (shown below the label, above the inputs; always include the example format)
4. **Day input** (numeric input labelled "Day")
5. **Month input** (numeric input labelled "Month")
6. **Year input** (numeric input labelled "Year")
7. **Error message**
## Basic usage
```tsx
const Example = () => {
const [date, setDate] = useState({ day: '', month: '', year: '' });
return (
);
}
```
## 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:
```tsx
const Example = () => {
const [date, setDate] = useState({ day: '21', month: '7', year: '1997' });
return (
Current value: {date.day}/{date.month}/{date.year}
);
}
```
Uncontrolled, with `defaultValue`. The field manages its own value after the initial render:
```tsx
```
### Read-only
Use the `readOnly` prop to display a date that the user cannot edit.
```tsx
```
### Common form features
The following features are shared across all Nebula form fields. See [Form guidance](/patterns/forms) 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.
## Rules and guidance
### Usage
- **DateField** MUST NOT be used for dates where calendar context is helpful, such as booking dates or appointment scheduling. Rationale: DateField provides no calendar interaction. Use DatepickerField for dates where the user benefits from seeing surrounding days or navigating by month. [id: datefield.no-date-picker-use; testability: context]
- **DateField** MUST NOT be passed the disabled prop. Rationale: The 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. [id: datefield.no-disabled; testability: auto]
- **DateField** SHOULD be used only for dates the user is expected to know from memory. Rationale: DateField provides no calendar, making it unsuitable for dates the user needs to look up or navigate to. Use DatepickerField for recent or upcoming dates. [id: datefield.known-dates-only; testability: context]
- **DateField** SHOULD be replaced with plain text. For example in a SummaryList. When displaying a value the user cannot edit. Rationale: A field that looks interactive but cannot be changed is confusing. Plain text communicates the value without implying editability. [id: datefield.non-editable-as-text; testability: manual]
- **DateField** SHOULD NOT be used in new work. Rationale: DateField is deprecated. Use DatepickerField for most date inputs. Exception: maintaining existing code that already uses DateField. [id: datefield.deprecated; testability: manual]
### Content
- **DateField hint text** SHOULD include an example date in the format "For example, 21 7 1997". Rationale: Three separate inputs without an example format create uncertainty about whether day or month comes first, and whether zero-padding is required. [id: datefield.hint-text-required; testability: manual]
### Interaction
- **DateField** WILL set aria-invalid on all three day, month, and year inputs automatically when an error is present. [id: datefield.will-aria-invalid-all-inputs; testability: auto]
- **DateField** WILL derive the month input id as "{id}-month" and the year input id as "{id}-year" from the id prop automatically. [id: datefield.will-derived-ids; testability: auto]
- **DateField error message** WILL be associated with the day input via aria-describedby. The component sets this automatically on the day input. [id: datefield.error-links-to-day-input; testability: auto]
- If value prop is provided: **DateField** MUST provide both value and onChange when used in controlled mode. Rationale: Providing 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. [id: datefield.controlled-requires-value-and-onchange; testability: auto]
### Validation
- **DateField** MUST accept both padded ("07") and unpadded ("7") values for day and month. Rationale: Users should not be penalised for including or omitting leading zeros. Validate the semantic date value, not the string format. [id: datefield.accept-padded-and-unpadded; testability: auto]
- **DateField** MUST NOT rely on the component to validate the date. Rationale: DateField 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. [id: datefield.no-built-in-validation; testability: context]
## Accessibility
### 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
1. Day
2. Month
3. 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](https://www.w3.org/WAI/WCAG22/Understanding/labels-or-instructions.html)
#### 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](https://www.w3.org/WAI/WCAG22/Understanding/error-suggestion.html)
### 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](https://www.w3.org/WAI/WCAG22/Understanding/error-identification.html)
#### 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](https://www.w3.org/WAI/WCAG22/Understanding/error-identification.html)
### 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 each test covers:
- 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.
What these statuses mean:
- 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.
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.
## Properties
### DateField
- `id` (type `string`, required). Id for the day input. The month and year inputs use `{id}-month` and `{id}-year`, and the error uses `{id}-error`.
- `name` (type `string`, optional). 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` (type `string`, required). Legend for the group of day, month and year inputs.
- `onChange` (type `(date: DateFormat) => void`, optional). Called with the full `{ day, month, year }` value, with the changed part updated, when any input changes. Only called when `value` is provided.
- `value` (type `DateFormat`, optional). Controlled value as strings for `day`, `month` and `year`. Use together with `onChange`.
- `defaultValue` (type `DateFormat`, optional). Initial value for uncontrolled use, as strings for `day`, `month` and `year`.
- `hint` (type `ReactNode`, optional). Helper text shown below the label, inside the legend.
- `error` (type `ReactNode`, optional). 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` (type `boolean`, optional, default `false`). 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` (type `boolean`, optional, default `false`). Makes all three inputs read-only.
- `fullWidth` (type `'always' | 'never' | 'small'`, optional, default `'small'`). 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` (type `string`, optional). Test id for the day input. The month and year inputs use `{data-testid}-month` and `{data-testid}-year`.
## Styles
Reference only; you do not need this to use the component. This is the CSS its styled elements use, with theme tokens shown by name. `${...}` is an interpolation (a theme token, a mixin or another value from the code). A `&[prop]` or `@when ` block holds CSS that applies only under that condition; these are notation, not valid CSS.
### inline-fields
```css
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
```css
width: 25%;
```
### month-field
```css
width: 25%;
```
### year-field
```css
width: 50%;
```
### date-part
```css
width: 100%;
```