# Datepicker Field
A form field for entering a date, with an optional calendar popup for selection
Section: components
Docs: https://nebula.kaluza.com/components/datepicker-field
---
## When to use
- When the user needs to enter a date that is in the recent past or near future, where seeing a calendar helps them pick the right day
- When the user might copy the date from another source (such as a letter or document) and type it directly
- When the date relates to a booking, appointment, or event where surrounding context (day of the week, proximity to today) matters
## When not to use
- **For historical dates the user knows from memory**, such as date of birth or passport issue date. The calendar offers no benefit and navigating back years is tedious. Use [DateField](/components/date-field) instead.
- **For dates far in the future** where the user would need to navigate many months forward. Consider a [Text Field](/components/text-field) with clear format instructions if the date range is very large.
- **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**
2. **Required/Optional indicator**
3. **Hint** (optional; describe constraints such as the available date range rather than the input format)
4. **Input** (accepts a date typed in dd/mm/yyyy format)
5. **Calendar button** (opens the calendar popup)
6. **Error message**
7. **Calendar popup** (contains month navigation, a day grid, a today button, and a clear button)
8. **Today indicator** (highlights today's date in the calendar grid)
## Basic usage
```tsx
```
## Variants and features
### OverlayProvider wrapper
DatepickerField requires an `OverlayProvider` wrapper to position the calendar popup correctly and make it accessible. Wrap the page or section that contains the datepicker, not each individual field.
Without it the popup is not accessible: the page behind the calendar is not hidden from screen readers, so assistive technology users can read content they cannot interact with.
This example is not live, because the docs site provides its own `OverlayProvider` around every page.
```tsx
import { OverlayProvider } from '@react-aria/overlays';
{/* One per page or section, not one per field */}
```
### Typing a date directly
Users can type a date directly into the input without opening the calendar. The expected format is dd/mm/yyyy. The placeholder shows this format automatically.
The calendar updates to reflect the typed value as soon as it parses as a valid date.
### Unavailable dates
Use the `isDateUnavailable` prop to mark specific dates as unavailable in the calendar. Unavailable dates are still visible but cannot be selected.
This does not stop a date being typed into the input, so always validate on submit as well.
```tsx
date.getDay() === 0 || date.getDay() === 6}
/>
```
### Validating the selected date
Use the exported `getDatepickerDate` utility to parse the field's string value into a `Date` object. It returns `null` if the value is not a valid date.
```tsx
const Example = () => {
const [errors, setErrors] = useState([]);
const onSubmit = (e) => {
e.preventDefault();
const newErrors = [];
const { datepicker } = Object.fromEntries(new FormData(e.currentTarget));
const datepickerDate = getDatepickerDate(datepicker)
if (!datepickerDate) {
newErrors.push({ id: 'datepicker', message: 'Enter a valid date in the format dd/mm/yyyy'});
}
setErrors(newErrors);
if (!newErrors.length) {
alert('success');
}
}
return (
);
}
```
### Date ranges
DatepickerField does not have a built-in date range mode. To collect a start and end date, place two DatepickerField components side by side. Use `isDateUnavailable` on the second field to prevent the user from selecting a date before the first.
Align the two fields to the bottom of their container so the inputs stay aligned when one has hint text or an error.
```tsx
const Example = () => {
const [startDate, setStartDate] = useState('');
const isDateUnavailable = (date) => {
const start = getDatepickerDate(startDate);
if (!start) {
return false;
}
return date.getTime() < start.getTime();
};
return (
);
}
```
### 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 input. Use `getDatepickerDate` to check whether the string value is a valid date before deciding whether to show an error.
**Hint text:** use hint text to describe date constraints (for example, "Within the next 28 days"), not the input format. The placeholder already shows dd/mm/yyyy.
**Required and optional:** fields are required by default. Use the `optional` prop to mark a field as optional.
**Field width:** DatepickerField defaults to a fixed width that fits the dd/mm/yyyy format. Use `fullWidth` to control responsive behaviour.
## Rules and guidance
### Usage
- **DatepickerField** MUST NOT be used to collect a date range from a single field. Rationale: DatepickerField collects a single date. Use two side-by-side DatepickerField components for a start and end date. [id: datepickerfield.date-range-two-fields; testability: manual]
- **DatepickerField** MUST NOT be passed the disabled prop. Rationale: The prop passes through to the native input with no Nebula styling and removes the field from the tab order, making it invisible to keyboard users with no accessible indication of why. [id: datepickerfield.no-disabled; testability: auto]
- **DatepickerField** 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: datepickerfield.non-editable-as-text; testability: manual]
- **DatepickerField** SHOULD NOT be used for historical dates the user is expected to know from memory, such as date of birth. Rationale: Navigating back years in a calendar is tedious. Use DateField for dates the user knows by heart that predate calendar-based selection. [id: datepickerfield.not-for-historical-known-dates; testability: context]
### Content
- **DatepickerField hint text** SHOULD describe date constraints rather than the input format. Rationale: The placeholder already shows the dd/mm/yyyy format. Hint text adds more value when it tells the user what dates are valid, for example "Within the next 28 days". [id: datepickerfield.hint-describes-constraints; testability: manual]
- **DatepickerField hint text** SHOULD NOT repeat the date format in hint text. Rationale: The input already shows a dd/mm/yyyy placeholder. Restating the format adds noise without helping the user. [id: datepickerfield.no-format-in-hint; testability: manual]
### Interaction
- **DatepickerField calendar** MUST NOT hide unavailable dates from the calendar grid. Rationale: Unavailable dates must remain visible so users understand the full shape of the calendar. Use isDateUnavailable to mark them as unselectable, not to hide them. [id: datepickerfield.unavailable-dates-not-removed; testability: auto]
- **DatepickerField calendar** MAY have the today button used by the user to navigate quickly to the current date. Rationale: Stated here as an explicit permission. The today button is always present and should not be removed. [id: datepickerfield.today-button; testability: manual]
### Validation
- **DatepickerField** SHOULD use getDatepickerDate to parse and validate the field value. Rationale: getDatepickerDate handles the dd/mm/yyyy parsing and returns null for invalid dates, including partial inputs. Avoid reimplementing the parsing logic. [id: datepickerfield.validate-with-utility; testability: auto]
### Composition
- **DatepickerField** MUST be rendered inside an OverlayProvider. Rationale: The calendar popup uses React Aria's overlay system. Without OverlayProvider the popup will not be accessible and may render in the wrong position. [id: datepickerfield.overlay-provider; testability: auto]
- **OverlayProvider** SHOULD NOT be placed around each individual DatepickerField. Rationale: One OverlayProvider per page or section is sufficient and avoids stacking overlay contexts unnecessarily. [id: datepickerfield.overlay-provider-not-per-field; testability: manual]
## Accessibility
### In brief
Datepicker Field is a text input for typing a date as dd/mm/yyyy, with a calendar button that opens a calendar in a dialog. A screen reader announces the label, then a text field, and the button is a separate stop that opens the calendar.
### What Nebula provides
- A real `label` linked to the text input, `aria-required` unless the field is `optional`, and an `error` linked with `aria-invalid` and `aria-describedby`.
- A calendar button with `aria-haspopup="dialog"` and `aria-expanded`, built on React Aria.
- A calendar dialog that keeps focus inside while it is open, returns focus when it closes, closes with `Esc` or a click outside, and has a hidden dismiss button for screen reader users.
- A calendar grid that announces the visible month when it changes. Dates marked unavailable are dimmed and cannot be selected in the calendar.
### Keyboard behaviour
The input and then the calendar button are tab stops. In the calendar, the arrow keys move between days, `Page Up` and `Page Down` change month, `Home` and `End` move within the week, `Enter` or `Space` selects a day and `Esc` closes it.
#### Focus order
1. The date input
2. The calendar button
3. Inside the open calendar: the year and month buttons, the day grid, then Today and Clear
#### Interactions
- **Tab** - Moves focus to the input, then to the calendar button
- **Enter or Space** - Opens the calendar from the calendar button
### Accessible design
#### Always put the date format in the hint, not only in the placeholder
The placeholder (dd/mm/yyyy) disappears as soon as the user types and is shown at half opacity, so a user who is partway through a date loses the format. Write it in the hint, for example "For example, 21/02/2024".
WCAG: [3.3.2 Labels or Instructions](https://www.w3.org/WAI/WCAG22/Understanding/labels-or-instructions.html)
#### If some dates are unavailable, say which in the hint
The calendar only dims them, and a user who types a date is not stopped. A short hint such as "Monday to Friday" tells every user the rule before they choose.
WCAG: [3.3.2 Labels or Instructions](https://www.w3.org/WAI/WCAG22/Understanding/labels-or-instructions.html)
### Accessible development
#### Always validate the typed text on submit, and say what is wrong
The input accepts any text and `isDateUnavailable` does not apply to it. Check it with `getDatepickerDate` and show an `error` that names the format or the rule it broke.
WCAG: [3.3.1 Error Identification](https://www.w3.org/WAI/WCAG22/Understanding/error-identification.html) [3.3.3 Error Suggestion](https://www.w3.org/WAI/WCAG22/Understanding/error-suggestion.html)
#### Always pass errors through `error`, or through a `Form` with the field `name`
This is what sets `aria-invalid` and links the message to the input. 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)
### 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 | Partially 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
### DatepickerField
Also accepts all props from `HTMLAttributes`, `InputHTMLAttributes`.
- `id` (type `string`, required). Unique id for the input, used to link the label, hint and error to it. Also used as the base for related ids such as `{id}-error`.
- `error` (type `ReactNode`, optional). Error message shown below the input. When set, the input gets `aria-invalid` and an error border, and the message is linked to it with `aria-describedby`. Takes precedence over an error provided by a surrounding `Form`.
- `label` (type `string`, required). Visible label for the field. It is associated with the input, so it forms the input's accessible name.
- `name` (type `string`, optional). The `name` attribute passed to the input. Also used to look up the field's error from a surrounding `Form`.
- `hint` (type `ReactNode`, optional). Helper text shown below the label. It is part of the label element, so it is read as part of the input's accessible name.
- `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 input only when the field is not optional.
- `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 the width set by `characters`, and `small` is full width on small screens only.
- `characters` (type `number`, optional, default `10`). Width of the input, as an approximate number of characters (the width is set to `0.8em` per character, rounded up). Does not limit how much can be typed.
- `affixWidth` (type `number`, optional, default `40`). Extra width in pixels added to the width calculated from `characters`, to make room for icons or buttons inside the input.
- `iconLeft` (type `IconName | (string & {})`, optional). Name of an icon to show inside the left of the input. The input padding increases to make room for it.
- `iconRight` (type `IconName | (string & {})`, optional). Name of an icon to show inside the right of the input. The input padding increases to make room for it.
- `onChange` (type `(value: string) => void`, optional). Called with the new text when the user types (exactly as typed, not validated or reformatted) or picks a date from the calendar (formatted `dd/mm/yyyy`), and with an empty string when the date is cleared from the calendar.
- `defaultValue` (type `string`, optional). Initial text of the input when uncontrolled, in `dd/mm/yyyy` format.
- `defaultOpen` (type `boolean`, optional). Whether the calendar popover is open when first rendered.
- `isDateUnavailable` (type `IsDateUnavailable`, optional). Called for each date in the calendar. Return `true` to make that date unavailable so it cannot be selected in the calendar. It does not stop a date being typed into the input.