Skip to content
Tier

Date field

Pro

A date typed as text, one field for the whole date: "12/11/2026". It reads the text in the order the person's locale writes dates (day first in the UK, month first in the US), and always reads ISO 2026-11-12 too, so a date pasted from anywhere reads the same. The placeholder is the locale's pattern ("dd/mm/yyyy").

It reads the text when the person leaves the field or presses Enter, never while they type. Then it rewrites the text in the locale's format and says under the field, in words, which date it read ("Thursday, 12 November 2026"), so a day-first or month-first reading is never silent. Text it can't read is never guessed into a date: it says so, with the pattern to write, and onValueChange isn't called; onProblemChange says so to the app. A day past the month's end ("31/02") and a date outside min and max each get their own message. The messages and the reading describe the input, so they are read when focus returns to it; on Enter they are announced as well, but not as the person moves on.

Dates are read and written in the Gregorian calendar, whatever the locale's own: in a locale whose calendar is another (Thai's Buddhist, Persian's Solar Hijri), the date in words under the field carries its era, so a year typed in the locale's calendar isn't confirmed as if it were Gregorian.

onValueChange gets the day at local midnight, or null when the field is emptied. A date outside min or max is still reported, marked invalid, so the app's value is the one the field shows; check it before acting on it. With name, a hidden input carries ISO yyyy-mm-dd for a form (empty while the text can't be read). Use Field for a hint and the app's errors, or label for a plain label. For a calendar to pick from, use DatePicker.

Markdown
Version 5.2.0· other versions

This page is for 5.2.0, the current release. An earlier release’s page is the Markdown that release published, so the docs match the version you have installed (candor-lock.json records it).

Not in the docs of 1.3.0, 1.4.0, 1.5.0, 1.5.1, 1.6.0, 1.7.0, 1.8.0, 1.8.1, 1.9.0, 1.9.1, 2.0.0, 2.1.0, 2.2.0, 2.3.0, 2.4.0, 2.5.0, 2.5.1, 2.6.0, 2.6.1, 2.6.2, 2.7.0, 2.8.0, 2.9.0, 2.10.0, 3.0.0, 4.0.0, 4.0.1, 4.0.2, 4.1.0 and 4.2.0: the page came later, or had another name then.

Docs pages weren’t kept for 1.0.0, 1.0.1, 1.1.0, 1.1.1, 1.2.0 and 1.2.1: they are kept from 1.3.0 on, and before that only llms.txt was published.

Preview

Loading the preview

Install

With the Candor CLI

First time? Get started sets up the registry and theme. Or with shadcn directly: npx shadcn@latest add @candor/date-field

Brings in: bidi, input, label, locale, locked, tokens, use-announcer, utils

Usage

TSX · The example above
"use client"

import * as React from "react"
import { DateField, DateRangeField, type DateFieldRange } from "@/components/candor/ui/date-field"
import { Field } from "@/components/candor/ui/field"

// Typed dates are read in the order the person's locale writes them, and ISO always; the field says in words which
// date it read, so 03/04 is never silently the wrong one. Text it can't read is never guessed into a date.
export default function Example() {
  const [due, setDue] = React.useState<Date | null>(null)
  const [period, setPeriod] = React.useState<DateFieldRange>({ from: null, to: null })
  return (
    <div className="flex w-full max-w-md flex-col gap-6">
      <Field label="Finish by" hint="The agent plans its work to end on this day.">
        <DateField value={due} onValueChange={setDue} min={new Date(2026, 0, 1)} name="due" />
      </Field>
      <DateRangeField label="Report period" value={period} onValueChange={setPeriod} />
    </div>
  )
}

<DateField>

PropTypeDefaultDescription
idstring | undefined—The input's id, for a label's `htmlFor`. `Field` passes one.
"aria-describedby"string | undefined—Ids of anything else that describes the field (a hint, an error); the field's own reading or message is added after them.
"aria-invalid"boolean | "true" | "false" | undefined—Marks the field invalid for an error of the app's (from `Field`), as well as for the field's own messages.
"aria-required"boolean | "true" | "false" | undefined—The field must be filled (from `Field`'s `required`).
"aria-label"string | undefined—Names the input when there is no visible label.
attendboolean | undefined—Plays one attention ring on the input, as `Input`'s `attend` does, after a failed submit (`Field` passes it).
onAnimationEndReact.AnimationEventHandler | undefined—Called when the input's ring ends (`Field` passes it to clear the ring).
valueDate | null | undefined—The date, when you control it; `null` is empty.
defaultValueDate | null | undefined—The date at first, when you don't control it.
onValueChange((date: Date | null) => void) | undefined—Called with the date the field read (at local midnight), or `null` when the person empties it. Never called for text it couldn't read. The date is in the Gregorian calendar, whatever the locale's own.
onProblemChange((problem: DateFieldProblem | null) => void) | undefined—Called each time the field reads its text (when the person leaves it or presses Enter, after any `onValueChange`), with what is wrong with what it read, or `null` when it read a date within `min` and `max`, or an empty field. A controlled app checks this before saving: after `unreadable` or `pastMonthEnd` the text on screen isn't the app's `value`, which is still the last date the field read.
minDate | undefined—The earliest date allowed. An earlier one is reported, marked invalid and says so.
maxDate | undefined—The latest date allowed. A later one is reported, marked invalid and says so.
labelstring | undefined—A visible label above the field. Leave it out inside `Field`, which draws its own.
lockedstring | null | undefined—Why the date can't be changed right now. The field stays focusable and readable, can't be typed in, and says why.
requiredboolean | undefined—The field must be filled: sets `aria-required` (the browser's own bubble isn't used).
namestring | undefined—The form field's name: a hidden input carries the date as ISO `yyyy-mm-dd`.
errorstring | null | undefined—A problem the app found with the date, shown under the field after the reading, and marking it invalid.
classNamestring | undefined—Classes for the field's outer box.

<DateRangeField>

Two DateFields for a range, the start and the end, in a fieldset whose legend is label. Each reads and says its date as DateField does. When both are read and the end is before the start, the end field says "The end is before the start." and is marked invalid, and onValueChange still reports both: the app decides what to do, and the field has said so. While either end's text can't be read, the app still holds that end's last date, so the check waits until both are read. min and max apply to both ends. With name, two hidden inputs carry the ends for a form, as DateField's does; error is a problem the app found with the range, shown under both fields.

PropTypeDefaultDescription
valueDateFieldRange | undefined—The range, when you control it.
defaultValueDateFieldRange | undefined—The range at first, when you don't control it. Left out, both ends are empty.
onValueChange((range: DateFieldRange) => void) | undefined—Called with both ends whenever the person changes either.
onProblemChange((problems: { from: DateFieldProblem | null; to: DateFieldProblem | null }) => void) | undefined—Called each time either end reads its text, with each end's problem as `DateField`'s `onProblemChange` gives it (`null` for an end that is fine). A controlled app checks this before saving.
minDate | undefined—The earliest date either end can be.
maxDate | undefined—The latest date either end can be.
labelrequiredstring—The range's name, the fieldset's legend: "Report period".
labels{ from?: string | undefined; to?: string | undefined } | undefined—The two fields' labels. Default "From" and "To".
lockedstring | null | undefined—Why the range can't be changed right now; both fields say why.
requiredboolean | undefined—Both ends must be filled.
namestring | undefined—The form field's name: hidden inputs named `{name}.from` and `{name}.to` carry each end as ISO `yyyy-mm-dd` (empty while that end is empty or its text can't be read).
errorstring | null | undefined—A problem the app found with the range, shown under the two fields, describing both and marking both invalid.
classNamestring | undefined—Classes for the fieldset.

Strings

The fixed words, in English by default. Change any of them under dateField in <LocaleProvider strings={{ … }}>; the table is exported as dateFieldStrings.

KeyEnglish
daydd
monthmm
yearyyyy
unreadableCouldn't read “{text}” as a date. Write it as {pattern}.
pastMonthEnd{month} has {days} days.
beforeMinChoose a date on or after {min}.
afterMaxChoose a date on or before {max}.
fromFrom
toTo
endBeforeStartThe end is before the start.
readingWithProblem{reading}. {problem}

Types

DateFieldProblem
What is wrong with what a `DateField` last read, as it says under the field: `unreadable` and `pastMonthEnd` text gave no date (the app still holds its last one), `beforeMin` and `afterMax` a date that was reported but is outside the range. `message` is the words shown.
{ kind: "unreadable" | "pastMonthEnd" | "beforeMin" | "afterMax"; message: string }
DateFieldRange
A range of two days; either end is `null` while it is empty.
{ from: Date | null; to: Date | null }