AntaInputDate
Switch to dark theme
Search documentation
On this page

Date input

InputDate is a date field that replaces the native type="date" (and, with time, type="datetime-local"): a text field you can type into, backed by a Calendar that opens when you click the field (or press ), marked by a leading calendar icon. Calendar, the month grid it builds on, is documented lower down.

The field accepts free text and resolves it on commit (blur / Enter): a lenient parser reads the locale’s order (06/07/2026 is June 7 in en-US, July 6 in de-DE), month names, and run-together digits, then rewrites the entry to the canonical format. An unrecognized entry marks the field until you fix it. The value is an ISO YYYY-MM-DD string, never a Date; it works controlled (value + onValueChange) or uncontrolled (defaultValue), and submits under name.

Playground

The date field

Type a date and it resolves on blur or Enter, or click the field to open the calendar and pick a day. Either way the field settles on the locale’s canonical format and the value is ISO YYYY-MM-DD. A click keeps focus in the field, so you can keep typing or click a day; opens the calendar and moves focus into the grid, and Esc closes it back to the field. While you type, the calendar previews the entry — it jumps to that month and highlights the day — and commits it on Enter (which also closes the calendar); an unrecognized entry keeps the calendar open and marks the field.

<InputDate label="Due date" defaultValue="2026-06-15" onValueChange={(v) => save(v)} />

The placeholder shows the locale’s format, and parsing follows the same order, so de-DE reads and writes TT.MM.JJJJ while en-US uses MM/DD/YYYY. min / max bound both the calendar and what a typed date will accept.

<InputDate label="US English" locale="en-US" defaultValue="2026-06-15" />
<InputDate label="German" locale="de-DE" defaultValue="2026-06-15" />

time turns it into a date-time field (value YYYY-MM-DDTHH:mm). The menu grows a time row under the calendar: an InputTime (segmented hour / minute, plus AM/PM in 12-hour locales) and a confirm (✓) button. Picking a day keeps the menu open so you can set the time. The clock follows the locale (en-US is 12-hour, most others 24), overridable with hour12. The field parses a trailing time after a space (06/15/2026 2:30pm or … 14:30), and typing a 24-hour hour into the 12-hour time field converts it (18 → 6 PM).

{/* `size` scales the calendar, the time inputs, the AM/PM toggle, and the buttons together. */}
<InputDate size="small" label="Small" time defaultValue="2026-06-15T09:00" />
<InputDate size="medium" label="Medium" time defaultValue="2026-06-15T09:00" />
<InputDate size="large" label="Large" time defaultValue="2026-06-15T09:00" />

InputDate props

Prop Type Default Description
value? string Controlled value — ISO YYYY-MM-DD (or YYYY-MM-DDTHH:mm with time), '' for empty. Pair with onValueChange; the field and calendar follow it and a pick only requests a change.
defaultValue? string Initial value for the uncontrolled case.
min? string Earliest selectable date (ISO). Earlier days disable, and a typed date before it stays uncommitted.
max? string Latest selectable date (ISO).
locale? string navigator.language BCP-47 locale driving the display format, the placeholder mask, parsing order, and the calendar.
name? string Form field name — the ISO value submits under this key.
size? smallmediumlarge medium Field size. Also sizes the calendar and the time row.
disabled? boolean Disable the field and the calendar.
label? ReactNode Field label, above the control.
hint? ReactNode Helper text under the field. Replaced by a format hint while the entry is unrecognized.
placeholder? string the locale's format mask (e.g. `MM/DD/YYYY`, `MM/DD/YYYY HH:MM` with `time`) Placeholder shown when empty.
status? neutralbrandinfosuccesswarningcritical neutral Validation/feedback tone. An unrecognized entry forces critical until fixed.
round? booleannumberstring Round the field corners — true for fully round, or a number / CSS length.
placement? leftrightbottomtopbottom-startbottom-endtop-starttop-endright-startright-endleft-startleft-end bottom-start Preferred placement of the calendar menu relative to the field. Same values as Menu; it auto-flips and clamps when space runs out.
offset? number 4 Gap in pixels between the field and the calendar menu.
clearable? boolean Show a clear button once the field has a value.
icon? falseIconShape calendar-days Leading icon at the start of the field — the calendar affordance. Pass another shape to change it, or false to drop it.
time? boolean Include a time. The value becomes ISO YYYY-MM-DDTHH:mm, the field parses a trailing time after a space (06/15/2026 14:30, … 2:30pm), and the menu shows a time row (hours : minutes, an AM/PM toggle in 12-hour locales, then a Done button) under the calendar. Picking a day keeps the menu open so you can set the time.
hour12? boolean Force the time cycle when time is on: true for 12-hour (AM/PM), false for 24-hour. Omit to follow the locale (en-US → 12-hour, most others → 24).
onValueChange? (value, attrs) => void Fired after the value resolves (a recognized entry, a calendar pick, a time change, or a clear), with the new ISO value ('' when cleared) and a { value, name } snapshot. An unrecognized entry does not fire it.
Inherited props (className, id, slot, style, tabIndex, title)
Prop Type Default Description
className? string CSS class on the component's root element (merged with the component's own classes). Use it directly for layout and positioning — grid/flex placement, margins, alignment — rather than wrapping the component in a <div>/<span>.
id? string HTML id attribute.
slot? string Assigns the element to a named <slot> of a parent web component (e.g. slot="header" inside a <Card>, slot="footer" inside a <Dialog>).
style? CSSProperties Inline styles on the component's root element. Set layout/positioning here (or via className) directly on the component instead of adding a wrapper.
tabIndex? number Tab order. Set to -1 to skip the element when tabbing.
title? string HTML title attribute — native browser tooltip on hover.

Plus every standard DOM event handler (onClick, onFocus, onKeyDown, …) and any data-* or aria-* attribute — forwarded as-is to the underlying <a-*> element.

Calendar

The sections below document Calendar, the month grid InputDate opens. Reach for it directly when you want the grid inline, without a field.

Calendar uses Anta components in light DOM; it has no hidden shadow-root UI. The day cells and previous/next chevrons are <Button>s inside a form-associated <a-calendar> grid. Date calculations use the Temporal API through temporal-polyfill.

For a raw grid, import buildMonth from @antadesign/anta. It returns localized weekday headers and day records with ISO values, accessible labels, and selected, disabled, today, and outside-month state. It does not manage navigation, roving focus, selection, or rendering. The value is an ISO YYYY-MM-DD string, never a Date. Use value with onStateChange to control it, or defaultValue for uncontrolled use. It participates in forms under name.

Not SSR-safe. Calendar (and InputDate, which embeds it) reads the current date (Temporal.Now) and locale (navigator.language) at render, so a server render disagrees with the client on the highlighted “today” and on localized labels, and the page hydrates with a mismatch. Render it client-side only: Astro client:only, or a dynamic import inside useEffect. Every demo on this page uses client:only for that reason.

Selecting a date

Click a day, or focus the grid and press Enter / Space. The selected day takes the tertiary-button selected look toned brand — a brand-tinted filled cell with a ring. Today shows as a secondary Button, a resting fill.

<Calendar defaultValue="2026-06-12" />

Size

size scales the whole calendar: the day cells, the prev/next chevrons, and the weekday and month-heading type. It follows Button’s small / medium (default) / large scale, with day-cell heights of 24 / 28 / 32px.

<Calendar size="small" defaultValue="2026-06-28" />
<Calendar defaultValue="2026-06-28" /> {/* medium (default) */}
<Calendar size="large" defaultValue="2026-06-28" />

Range

min and max (ISO strings) bound the selectable range. Days outside it render disabled and skip keyboard focus, and the prev/next arrows stop at the range edge. The jump menu adapts to how far the range reaches.

No min / max: the jump menu lists today ±3 years, each a submenu of its months.
<Calendar size="small" defaultValue="2026-06-15" />
A range across years lists each year (here 2026 and 2027) as a submenu of months. June 2026 disables days 1–7, and the back arrow stops at the range’s start.
<Calendar size="small" defaultValue="2026-06-15" min="2026-06-08" max="2027-06-24" />
A range inside one year skips the year level and lists months directly, March through November enabled and the rest disabled.
<Calendar size="small" defaultValue="2026-06-15" min="2026-03-01" max="2026-11-30" />
A range inside one month leaves nowhere to jump. Both arrows and the month/year button are disabled, and only the in-range days stay selectable.
<Calendar size="small" defaultValue="2026-06-15" min="2026-06-08" max="2026-06-24" />

Locale

Calendar respects the browser locale by default (navigator.language). Pass a BCP-47 locale to override it: the first day of week comes from the locale’s week info (never hard-coded to Sunday), and the weekday and month names are localized.

<Calendar defaultValue="2026-06-28" locale="en-US" /> {/* week starts Sunday */}
<Calendar defaultValue="2026-06-28" locale="fr-FR" /> {/* week starts Monday */}
<Calendar defaultValue="2026-06-28" locale="ja-JP" />

Controlled

Uncontrolled by default: pass defaultValue and read the date from onValueChange or at form submit. Make it controlled with value + onStateChange to drive it from your store. The grid then follows value, and a pick only requests a change: answer by updating value, reject by doing nothing.

const [date, setDate] = useState('2026-06-28')
<Calendar
value={date}
onStateChange={(_e, { next }) => setDate(next ?? '')}
/>

onStateChange fires before the element applies, with (event, { next, prev, reason }) where next / prev are ISO strings. For a 'user' pick, event.preventDefault() vetoes it (uncontrolled). A controlled calendar with no onStateChange is read-only.

Disabled

disabled dims the whole calendar and removes it from interaction.

<Calendar defaultValue="2026-06-28" disabled />

Forms

Calendar is form-associated. Give it a name and it submits the selected ISO date under that key, resets to defaultValue on form reset, and reports through the standard constraint-validation pipeline.

<form>
<Calendar name="due-date" defaultValue="2026-06-28" />
</form>

Accessibility

The focused Input field is associated directly with its Calendar dialog through ARIA element reflection. The relationship does not require a generated popup ID, so separate application roots can render InputDate instances without ID collisions. An explicitly authored relationship on a raw Anta Input takes precedence.

The calendar is a labelled group of day buttons, a flat grid rather than a nested grid/row/gridcell tree. The visible month names the group directly. Each day is a <button> whose aria-label is its full date including weekday (“Sunday, June 28, 2026”), with aria-pressed on the selected day and aria-current="date" on today. The decorative weekday headers are aria-hidden, since each day already names its weekday. A roving tabindex keeps a single tab stop, and an aria-live region announces the month when it changes.

KeyMoves focus
/ by one day
/ by one week
Home / Endto the start / end of the week
PageUp / PageDownby one month
Shift + PageUp / PageDownby one year
Enter / Spaceselect the focused day

Customization

InputDate uses Input, Menu, and Calendar. Calendar uses an <a-calendar> element for the day grid and a JSX wrapper for navigation. The sections below describe which layer provides each behavior and how to recreate the interface without React.

Build it from elements

InputDate has no a-inputdate element. Its wrapper renders an editable <Input> and a <Menu> containing a <Calendar>. When time is set, the hour/minute fields and AM/PM Tabs appear below the grid. The wrapper manages draft text, parsing on commit, and the ISO value.

Clicking the field opens the menu while keeping focus in the field. Pressing opens the menu and moves focus to the grid. The wrapper passes a focusSignal to Calendar, which renders data-focus; <a-calendar> then focuses the active day. Calendar and Tabs emit their own statechange events, but Menu responds only to changes in its own open state.

The <a-calendar> element provides grid behavior. It connects the supplied day <button>s to click and keyboard navigation, including arrows, Home, End, PageUp, and PageDown. It stores the form value through ElementInternals and emits statechange / change on selection and navigate when keyboard navigation leaves the displayed month. It is framework-agnostic and does not track a current month, min, or max.

The JSX wrapper renders the controls above the grid as siblings of <a-calendar>. The previous/next arrows are <Button>s. The month/year picker is a <Button> that opens a <Menu> of year MenuItems, each with a month submenu. The wrapper stores the displayed-month cursor in state and derives day cells and the arrows’ disabled states from min and max. A click on an arrow, a menu selection, or a navigate event updates the cursor and re-renders the grid. The wrapper enforces the range. The element only ignores a day that was rendered with disabled.

The element has no min or max because the wrapper provides the surrounding controls. Without React, render the grid with buildMonth and add the navigation behavior shown below:

<!-- `.calendar` scopes this example. Use an application selector instead. -->
<div class="calendar" style="display: inline-flex; flex-direction: column; gap: 6px">
<div style="display: flex; align-items: center">
<a-button priority="tertiary" data-prev aria-label="Previous month"><a-icon shape="chevron-left"></a-icon></a-button>
<a-button priority="tertiary" data-heading style="flex: 1; justify-content: center" aria-haspopup="menu"></a-button>
<a-button priority="tertiary" data-next aria-label="Next month"><a-icon shape="chevron-right"></a-icon></a-button>
</div>
<!-- The element provides selection, keyboard behavior, form value, and events. -->
<a-calendar role="group" name="date"></a-calendar>
</div>
<script type="module">
import '@antadesign/anta/elements'
import { buildMonth, parseISODate } from '@antadesign/anta'
import { Temporal } from 'temporal-polyfill'
const root = document.querySelector('.calendar')
const grid = root.querySelector('a-calendar')
const heading = root.querySelector('[data-heading]')
let cursor = Temporal.Now.plainDateISO()
function render() {
const m = buildMonth({ anchor: cursor, locale: navigator.language })
heading.textContent = m.heading
grid.replaceChildren()
for (const w of m.weekdays) {
const s = Object.assign(document.createElement('span'), { textContent: w.narrow })
s.setAttribute('data-part', 'weekday')
s.setAttribute('aria-hidden', 'true')
grid.append(s)
}
for (const d of m.weeks.flat()) {
const b = Object.assign(document.createElement('a-button'), { textContent: String(d.day) })
b.setAttribute('priority', 'tertiary')
b.setAttribute('data-part', 'day-cell')
b.setAttribute('data-date', d.iso) // the element keys off this
b.tabIndex = d.iso === cursor.toString() ? 0 : -1
if (d.selected) b.setAttribute('selected', '')
if (d.disabled) b.setAttribute('disabled', '')
grid.append(b)
}
}
root.querySelector('[data-prev]').addEventListener('click', () => { cursor = cursor.subtract({ months: 1 }); render() })
root.querySelector('[data-next]').addEventListener('click', () => { cursor = cursor.add({ months: 1 }); render() })
// Keyboard paging past the rendered grid: the element asks for a month flip.
grid.addEventListener('navigate', (e) => { cursor = parseISODate(e.detail.date) ?? cursor; render() })
render()
</script>

Tracking the cursor, painting the grid from buildMonth, and re-rendering on navigate is what the React Calendar packages, plus the year/month jump menu, which uses the same Menu composition shown on the Menu page.

Calendar props

Prop Type Default Description
value? string Controlled selected date — ISO YYYY-MM-DD. When provided, the application controls selection: the grid follows this prop and a pick only requests a change via onStateChange. Leave undefined for uncontrolled.
defaultValue? string Initial selected date for the uncontrolled case (ISO YYYY-MM-DD).
min? string Earliest selectable date (ISO YYYY-MM-DD) — earlier days render disabled.
max? string Latest selectable date (ISO YYYY-MM-DD) — later days render disabled.
locale? string navigator.language BCP-47 locale tag driving first-day-of-week, weekday, and month names.
name? string Form field name — the selected ISO date submits under this key.
size? smallmediumlarge 'medium' Size of the whole calendar — scales the day cells, the chevrons, and the weekday / month-heading type together (uses Button's small / medium / large scale).
disabled? boolean Disable the whole calendar (not focusable or selectable).
focusSignal? number Move keyboard focus onto the active day. Change this to a new value (e.g. increment a counter) to focus the cursor cell — InputDate bumps it when the calendar is opened from the field by keyboard (ArrowDown), so focus lands in the grid. The initial value never focuses; only a change does.
aria-label? string Accessible name for the grid (defaults to the visible month heading).
onStateChange? (event, detail) => void Fired whenever the selection changes — event-first. detail is { next, prev, reason }: next / prev are ISO date strings (null = none); reason is 'user' | 'reset' | 'restore'. A 'user' pick fires before applying and is cancelableevent.preventDefault() vetoes it (uncontrolled), or in controlled mode answer by updating value. 'reset' / 'restore' are not cancelable.
onChange? (event) => void Fired after the selection changes (post-apply). Not cancelable; for a controlled calendar it fires once you've updated value.
onValueChange? (event, attrs) => void Like onChange, but with a { value, name } snapshot as the second argument, matching Input.
Inherited props (className, id, slot, style, tabIndex, title)
Prop Type Default Description
className? string CSS class on the component's root element (merged with the component's own classes). Use it directly for layout and positioning — grid/flex placement, margins, alignment — rather than wrapping the component in a <div>/<span>.
id? string HTML id attribute.
slot? string Assigns the element to a named <slot> of a parent web component (e.g. slot="header" inside a <Card>, slot="footer" inside a <Dialog>).
style? CSSProperties Inline styles on the component's root element. Set layout/positioning here (or via className) directly on the component instead of adding a wrapper.
tabIndex? number Tab order. Set to -1 to skip the element when tabbing.
title? string HTML title attribute — native browser tooltip on hover.

Plus every standard DOM event handler (onClick, onFocus, onKeyDown, …) and any data-* or aria-* attribute — forwarded as-is to the underlying <a-*> element.

Web Component

InputDate has no host element. Rebuilding its text parsing, dropdown calendar, localized day grid, month navigation, keyboard navigation, and focus management in vanilla JavaScript is significant work, even with Anta’s buildMonth and parseISODate helpers. Use the InputDate wrapper when React or Preact is an option. Otherwise, a native HTML date input is the practical baseline.

Native HTML date input

Add data-anta to a date, datetime-local, month, time, or week input. Nothing else is required: Anta styles the field while the browser keeps its picker, icon, validation, and keyboard model. The picker itself and its date/time formatting remain browser- and OS-owned.

data-anta-size, round, and a custom-color tone use the matching Input field treatments.

<!-- `data-anta` styles the field, not the native picker. -->
<input data-anta type="date" name="due-date" value="2026-06-15">
<input data-anta type="datetime-local" name="starts-at" value="2026-06-15T09:00">
<input data-anta type="month" name="billing-month" value="2026-06">
<input data-anta type="time" name="reminder-at" value="09:00">
<input data-anta type="week" name="sprint-week" value="2026-W25">

Styling

The calendar is light DOM, so everything is plain CSS, with no shadow parts or hidden internals. The days and chevrons are real <a-button>s, so their colors, hover, focus ring, and selected look come from the Button styles and theme themselves; they sit inside the <a-calendar> grid, and the switcher header sits in the .anta-calendar shell. An un-layered consumer rule beats @layer anta without !important. The class below is for the demo.

/* Bigger, circular day cells. `className` lands on the outer .anta-calendar
shell; `--calendar-cell-width` / `--calendar-cell-height` drive the grid track
and the cell box (inherited down), and you target the day Buttons directly. */
.anta-calendar.roomy { --calendar-cell-width: 40px; --calendar-cell-height: 40px; }
.anta-calendar.roomy a-button[data-part="day-cell"] { border-radius: 50%; }

Each day is a <a-button data-part="day-cell" data-date="YYYY-MM-DD">, so a specific date is addressable by its data-date: tint it, or hang an event marker off a ::after (give the cell position: relative first). Below, three days carry a brand dot and one is flagged in the critical color.

/* Day cells are `<a-button data-part="day-cell" data-date="YYYY-MM-DD">`. Give them a
positioning context, dot event days with a `::after`, and tint a date's text. */
.anta-calendar.events a-button[data-part="day-cell"] { position: relative; }
.anta-calendar.events a-button[data-part="day-cell"]:is(
[data-date="2026-06-05"], [data-date="2026-06-12"], [data-date="2026-06-20"]
)::after {
content: ""; position: absolute; inset-block-end: 3px; inset-inline-start: 50%;
inline-size: 4px; block-size: 4px; transform: translateX(-50%);
border-radius: 999px; background: var(--text-2-brand);
}
.anta-calendar.events a-button[data-part="day-cell"][data-date="2026-06-25"] {
color: var(--text-2-critical); font-weight: 600;
}

The engine (buildMonth, firstDayOfWeek, …) is exported, so you can render a fully custom grid and keep only the <a-calendar> form plumbing.