OPERATOR

DatePicker

Usage

Inspection date

Committed date: 2026-01-31

const [date, setDate] = useState<string | null>("2026-01-31");
const [dateValid, setDateValid] = useState(true);
 
<DatePicker
  id="inspection-date"
  label="Inspection date"
  value={date}
  onValueChange={setDate}
  onDraftValidityChange={setDateValid}
  required
/>
<Button disabled={!dateValid}>Review</Button>

Date values and unfinished edits

The controlled value is a Gregorian YYYY-MM-DD string in years 0001–9999, or null. It carries no time or timezone. min and max are inclusive dates in the same format. Invalid supplied values or bounds produce readable errors.

Always connect onDraftValidityChange when a form can review or submit the value. It reports initial validity and changes to it. A partial, impossible, out-of-bounds, or required-empty draft reports false even when value still contains an earlier valid date. Keep Review and Save blocked until it becomes true.

An impossible date such as February 31 is never emitted as a different day. If the underlying segmented field restores its controlled display after rejecting an edit, the visible error and false validity remain until a date edit, a calendar selection, or an externally changed value resolves it. Moving focus with Tab does not clear the rejection. An optional, fully empty field is valid.

Focus and disabled controls

The id and forwarded HTMLDivElement ref identify the date-control group. Calling focus() on that group enters the first date segment, so FormErrorSummary can target the supplied ID. Escape, or choosing a day, closes the calendar and returns focus to the calendar button, however the calendar was opened. disabled and readOnly both prevent date changes and calendar opening.

The callback receives (valid, { pending }). Use valid to gate Review/Save and pending to track unfinished local editing for leave protection or draft persistence. A pristine required-empty field is invalid but has pending: false; partial input has pending: true. Malformed supplied values also remain clean until edited. Existing callbacks that accept only valid continue to work.

Props

PROPTYPEDEFAULTREQUIRED
labelstring—yes
value

YYYY-MM-DD in years 0001–9999, or null. Values contain no time or timezone.

string—yes
onValueChange

Requests a complete valid date or null after clearing. Invalid or coerced dates are not emitted.

function—yes
onDraftValidityChange

Callback (valid, { pending }). Pending identifies uncommitted local edits; pristine required-empty or malformed supplied values are invalid but not pending. Reports initial and changed validity. False for partial, impossible, out-of-bounds or required-empty edits. Block Review/Save while false, even if value still contains a previous valid date.

function—no
min

Inclusive YYYY-MM-DD lower bound.

string—no
max

Inclusive YYYY-MM-DD upper bound.

string—no
disabledbooleanfalseno
readOnlybooleanfalseno
requiredbooleanfalseno
motionenum
auto | none
"auto"no
id

ID of the focusable date-control group. Focusing it enters the first segment; the forwarded HTMLDivElement ref has the same behavior.

string—no
classNamestring—no

Accessibility

  • React Aria labels the editable segments and calendar dialog and provides keyboard date navigation.
  • Errors remain visible as text and the segments expose invalid state. An edit outside min or max names the bound it crossed, as a supplied value does: "Choose a date on or before 2026-12-31."
  • The calendar button is named "Choose date" followed by the field label. A required field's " *" is shown but not read; the segments say they are required.
  • Segment and calendar targets meet the 24 CSS pixel minimum. Text is 11px or larger, with the shared focus indicator.
  • The calendar uses the shared popover motion policy, including keyboard and reduced-motion handling.

Manifest entry

<DatePicker id="inspection-date" label="Inspection date" value={date} onValueChange={setDate} onDraftValidityChange={setDateValid} required />
View agent manifest
{
  "description": "Controlled Gregorian date-only picker with segmented editing, calendar overlay and a draft-validity signal for incomplete or rejected edits.",
  "props": {
    "label": {
      "type": "string",
      "required": true
    },
    "value": {
      "type": "string",
      "required": true,
      "description": "YYYY-MM-DD in years 0001–9999, or null. Values contain no time or timezone."
    },
    "onValueChange": {
      "type": "function",
      "required": true,
      "description": "Requests a complete valid date or null after clearing. Invalid or coerced dates are not emitted."
    },
    "onDraftValidityChange": {
      "type": "function",
      "description": "Callback (valid, { pending }). Pending identifies uncommitted local edits; pristine required-empty or malformed supplied values are invalid but not pending. Reports initial and changed validity. False for partial, impossible, out-of-bounds or required-empty edits. Block Review/Save while false, even if value still contains a previous valid date."
    },
    "min": {
      "type": "string",
      "description": "Inclusive YYYY-MM-DD lower bound."
    },
    "max": {
      "type": "string",
      "description": "Inclusive YYYY-MM-DD upper bound."
    },
    "disabled": {
      "type": "boolean",
      "default": false
    },
    "readOnly": {
      "type": "boolean",
      "default": false
    },
    "required": {
      "type": "boolean",
      "default": false
    },
    "motion": {
      "type": "enum",
      "values": [
        "auto",
        "none"
      ],
      "default": "auto"
    },
    "id": {
      "type": "string",
      "description": "ID of the focusable date-control group. Focusing it enters the first segment; the forwarded HTMLDivElement ref has the same behavior."
    },
    "className": {
      "type": "string"
    }
  },
  "composable_with": [
    "FormSection",
    "FormErrorSummary"
  ],
  "never_inside": [
    "Button"
  ],
  "example": "<DatePicker id=\"inspection-date\" label=\"Inspection date\" value={date} onValueChange={setDate} onDraftValidityChange={setDateValid} required />"
}

On this page