Date Input
Shared label and supporting-text behaviour with catnip-input is summarised on Inputs overview.
catnip-input-date
Props
Value & mode
| Prop | Type | Default | Options | Description |
|---|---|---|---|---|
| modelValue | CatnipInputDateModelValue | null | — | Committed value; model-value in HTML / .modelValue in frameworks. |
| mode | string | "single" | single, range | Single date/datetime or range selection. |
| showTime | boolean | false | — | Adds a time input and serializes endpoints as YYYY-MM-DDTHH:mm. Shows the panel action footer. |
| showPresets | boolean | false | — | Range-only. Shows the preset sidebar (Today, Last 7 days, Custom range, …). Ignored in single mode. |
| periodPresets | CatnipInputDatePeriodPresetConfig[] | [] | Built-in preset ids | Subset, reorder, or relabel built-in range presets. Empty uses the full built-in list. |
| showActions | boolean | false | — | Forces the panel Reset / Cancel / Apply row. Also shown automatically for showTime and showPresets. |
| open | boolean | — | — | Controlled panel state. Pair with open-change. |
Date constraints
| Prop | Type | Default | Options | Description |
|---|---|---|---|---|
| min | string | "" | YYYY-MM-DD | Earliest selectable date. |
| max | string | "" | YYYY-MM-DD | Latest selectable date. |
| disabledDates | string[] | [] | YYYY-MM-DD values | Unavailable days. They stay focusable in the calendar and are announced as unavailable. |
| maxRangeDays | number | — | Positive integer | Range-only inclusive maximum number of calendar days. 0 / unset means no range-length limit. |
| firstDayOfWeek | number | 1 | 0 through 6 | First day in the calendar grid; 0 is Sunday, 1 is Monday. |
| locale | string | "" | BCP 47 locale | Locale for month and weekday labels. Empty uses the browser locale. |
Field presentation
| Prop | Type | Default | Options | Description |
|---|---|---|---|---|
| label | string | "" | — | Visible field label when the label slot is empty. |
| startLabel | string | "" | — | Vertical range+time panel: From field label. Defaults to locale From. |
| endLabel | string | "" | — | Vertical range+time panel: To field label. Defaults to locale To. |
| placeholder | string | "" | — | Placeholder for the combined trigger. Empty uses the active display format (YYYY-MM-DD — YYYY-MM-DD in range mode). |
| displayFormat | string | "YYYY-MM-DD" | Day.js format tokens | Visible date-only input format. Parsing also accepts this format; emitted values stay canonical. |
| displayDateTimeFormat | string | "YYYY-MM-DD, HH:mm" | Day.js format tokens | Visible datetime input format when the typed value includes time. Parsing also accepts this format; emitted values stay canonical. |
| panelOrientation | "horizontal" | "vertical" | "horizontal" | horizontal, vertical | Horizontal pages one month (two side-by-side in range). Vertical stacks a scrollable month list. Range horizontal switches to vertical automatically below 800px. |
| panelPlacement | "top" | "bottom" | "bottom" | top, bottom | Preferred panel placement. Flips when there is insufficient space. |
| panelAlign | "start" | "end" | "start" | start, end | Cross-axis alignment of the panel relative to the trigger. |
| showClearButton | boolean | true | — | Shows an inline reset icon inside the trigger when the field has a value. |
| clearButtonAriaLabel | string | "" | — | Accessible label for the inline reset icon; defaults to bundled inputDate.clear. |
| supportingText | SupportingTextProp | "" | — | One or more helper or validation messages. Each object requires text; intent accepts neutral, danger, warning, or success, and icon accepts a Catnip icon name. See supporting-text examples. |
| size | "s" | "m" | "l" | "m" | s, m, l | Dimensions and typography. |
| intent | "neutral" | "danger" | "warning" | "success" | "neutral" | neutral, danger, warning, success | Semantic state applied to field and supporting-text styling; danger sets aria-invalid on the date control. |
| disabled | boolean | false | — | Disables the trigger, calendar, and panel actions. |
| readonly | boolean | false | — | Prevents editing and panel interaction. |
| required | boolean | false | — | Native required state and label marker. |
| hideLabel | boolean | false | — | Hides the built-in label row. |
| inputDateAriaLabel | string | "" | — | aria-label when there is no visible label. |
| ariaDescribedby | string | "" | — | Extra id values merged into aria-describedby alongside generated supporting-text ids. |
Advanced
| Prop | Type | Default | Options | Description |
|---|---|---|---|---|
| inputDateId | string | "" | — | DOM id for the input. Auto-generated when omitted. |
| name | string | "" | — | Native name for form submission. |
| inputDateAutofocus | boolean | false | — | Forwards native autofocus to the text input. |
| optional | boolean | false | — | When true and not required, shows the localized optional hint. |
| optionalText | string | "" | — | Overrides the localized optional hint (HTML: optional-text). |
| info | string | "" | — | Info tooltip beside the label; when non-empty, it wins over the label.suffix slot. |
| alignment | "left" | "center" | "right" | "left" | left, center, right | Horizontal alignment of the field label block. |
| requiredMarker | string | "*" | — | Visual marker for required fields (HTML: required-marker). |
| requiredScreenReaderText | string | "" | — | Optional screen-reader supplement; it does not replace required on the control (HTML: required-screen-reader-text). |
| testSelectors | TestSelectorsProp | See Test Selectors | — | Overrides stable data-test values. |
Values
| Mode | show-time | show-presets | Value |
|---|---|---|---|
| Single | false | — | "YYYY-MM-DD" |
| Single | true | — | "YYYY-MM-DDTHH:mm" |
| Range | false | false | { start?: "YYYY-MM-DD"; end?: "YYYY-MM-DD" } |
| Range | true | false | { start?: "YYYY-MM-DDTHH:mm"; end?: "YYYY-MM-DDTHH:mm" } |
| Range | — | true | { start?: string; end?: string; preset?: string } |
Built-in preset ids: today, yesterday, last-7-days, last-30-days, last-90-days, this-month, last-month, all-time, custom.
Display formats affect only the text shown in the input. Model values, events, constraints, and form serialization continue to use the canonical value formats above. Range endpoints in the trigger are separated with an em dash (—). The trigger fills its container and its text field is a single line: a value that doesn't fit scrolls inside the field.
Typed input and validation
| Input | Accepted text | Hint in aria-describedby |
|---|---|---|
| Trigger field | Any text; parsed with display-format / display-date-time-format and locale, then forgivingly (see below). | inputDate.formatHint with the field format. |
Time box (show-time) | Digits and : only, up to 5 characters. 930, 9:30, and 0930 expand to 09:30 on blur. | inputDate.formatHint with inputDate.timeFormat. |
| From / To fields (vertical range + time) | Any text; parsed with display-date-time-format. Emptying one clears that date, so Apply stays disabled. | inputDate.formatHint with that format. |
Forgiving dates: when the text doesn't match the display formats, the field still accepts:
.,/,-, commas, or spaces between day, month, and year, in any amount (voice input's extra spaces don't matter);- digits without separators, read in the display format's order (
27031990forDD.MM.YYYY,19900327forYYYY-MM-DD), with leading zeros; a format with a two-digit year also takes six digits (102226forM/D/YY); - missing leading zeros (
7.3.1990); - month names or abbreviations in
localeor English (27 March 1990,27 mar 1990); - an optional
HH:mmafter the date.
A four-digit year first always reads as year, month, day. A two-digit year is read only when the display format has one (
YY), from the position the format puts it in, with the same century rule as Day.js (69–99→ 1900s,00–68→ 2000s). With the year last, day and month follow the display format, or thelocale's order when the format starts with the year. A "month" above 12 is read as the day. Range text splits at the em or en dash, or at a spaced hyphen that leaves a date on each side. On blur the value is rewritten in the display format.Number pad: a single date without time whose
display-formatis all digits and separators (no month names, ordinals, or[literal]text) setsinputmode="numeric", so phones show a number pad. Some number pads (iOS) have no separators; digits without separators still parse when every part is typed with its leading zeros. Ranges andshow-timekeep the full keyboard because they need a dash, space, or colon.inputmodedoesn't limit what can be typed.Text that doesn't parse is never committed and never cleared. On blur it stays in place, gets
aria-invalid="true"and the danger border, and showsinputDate.invalidFormatwith an error icon (below the trigger as supporting text, or below the panel input). The flag clears as soon as the text parses again.Consumer
supporting-textstill renders; the format message is appended after it. Domain rules such as required dates stay consumer-owned throughintentandsupporting-text.Apply is enabled once the dates are complete. If a typed value is invalid, Apply flags every invalid input and moves focus to the first one instead of submitting.
Override
inputDate.formatHint("Format: {format}"),inputDate.invalidFormat("Use the format {format}"), andinputDate.timeFormat("HH:mm, 24-hour") throughregisterTranslate.
Events
| Name | Payload | Description |
|---|---|---|
update:modelValue | string | { start?: string; end?: string; preset?: string } | null | Committed value changed. Not emitted when the value is unchanged. |
open-change | boolean | Panel open state changed (true / false). Use @open-change. |
submit | string | { start?: string; end?: string; preset?: string } | Draft value applied from the panel action row. |
cancel | — | Cancel, or Escape while the panel has Apply, discarded the draft and closed the panel. |
clear | — | Reset or inline clear reset the value. |
close | — | The panel closed, for any reason. Not emitted when it was already closed. |
Keyboard
Field
| Key | Behaviour |
|---|---|
| Arrow Down / Alt + Arrow Down | Opens the panel and moves focus into it: the selected preset (or the first one), otherwise the active day. |
| Enter | Commits the typed value and closes the panel; with Apply in the panel, works as Apply. |
| Tab | Closes the panel and moves on, like any combobox. |
| Escape | Closes the panel. |
Focusing the field never opens the panel; a click opens it and keeps focus in the field. Each time the panel opens it shows the selected date (the range start, or today when nothing is selected), not the month browsed last time.
Panel
| Key | Behaviour |
|---|---|
| Tab / Shift + Tab | Moves between the panel's controls; focus wraps inside the panel. |
| Escape | Closes the panel and returns focus to the field. With Apply, also discards the draft like Cancel. |
Focus returns to the field after every close except a click outside. A visually hidden Close button (not in the Tab order) is added when the panel has no Cancel, so touch screen reader users can dismiss it.
Presets
| Key | Behaviour |
|---|---|
| Arrow Up / Arrow Down | Previous / next preset. |
| Home / End | First / last preset. |
| Enter / Space | Pick the focused preset. |
The list has one Tab stop: the focused, otherwise selected, preset.
Calendar
| Key | Behaviour |
|---|---|
| Arrow Left / Right | Move active day backward / forward. |
| Arrow Up / Down | Move active day by one week. |
| Home / End | Move to the start / end of the active week. |
| PageUp / PageDown | Move to the previous / next month. |
| Shift + PageUp / Shift + PageDown | Move to the same month in the previous / next year. |
| Enter / Space | Select active date. |
Arrow keys stop at min and max. Unavailable days (disabled-dates, or past max-range-days while picking the end) can be focused and are announced as unavailable, but not selected. The calendar has one Tab stop, on the active day, even with two months shown.
The month/year header button opens a year grid. In range mode it also opens a month grid: years on the left and months on the right in the horizontal dual-month panel, or years above months in the vertical stack. Selecting a year updates the month grid; selecting a month returns to the day calendars. Single-date mode still uses the year grid only. In the year grid, arrow keys move between years, PageUp / PageDown move between year ranges, and Enter / Space selects the active year. In the month grid, arrows move between months and Enter / Space selects the active month.
Accessibility tree
| Part | Semantics |
|---|---|
| Field | <input role="combobox"> with aria-haspopup="dialog", aria-expanded, aria-autocomplete="none", and aria-controls while open. |
| Panel | role="dialog" named "{label}, Choose date" (or "Choose dates" in range mode). aria-modal="true" only while focus is inside. |
| Month | <table role="grid"> named by month and year; weekday column headers carry the full weekday name; each day is a button in a role="gridcell". |
| Day names | Full date, then as applicable: "today", "unavailable", "selected", "start of selected range", "end of selected range". Today also has aria-current="date". |
| Live region | Polite. Announces "Start date {date}. Choose an end date.", "Selected range: {range}", and the month shown after the previous / next buttons. |
| Preset field | While a named preset is shown, its dates are added to the field's description; while the field has focus it shows the dates and describes "Preset: {name}". |
Override the new strings through registerTranslate: inputDate.chooseDate, inputDate.chooseDates, inputDate.close, inputDate.presetDescription, and the shared calendar keys calendar.today ("today"), calendar.selected ("selected"), calendar.rangeStart ("start of selected range"), calendar.rangeEnd ("end of selected range"), calendar.unavailable ("unavailable"), calendar.startChosen ("Start date {date}. Choose an end date."), calendar.rangeSelected ("Selected range: {range}"), and calendar.dateSelected ("Selected: {date}"). Existing inputDate.* keys for navigation (for example inputDate.previousMonth) still take precedence over their calendar.* equivalents.
Slots
| Name | Description |
|---|---|
| label | Custom label content; falls back to label prop. |
| label.action | Trailing action in the label row. |
| label.suffix | Custom label suffix; used only when info is empty. |
| startLabel | Vertical range+time panel: custom From label; falls back to startLabel prop / locale. |
| endLabel | Vertical range+time panel: custom To label; falls back to endLabel prop / locale. |
| value | Selected-value area inside the default trigger (calendar icon, clear, and chevron stay). Replaces the typeable field. |
| trigger | Full trigger override — see Trigger slot below. |
Trigger slot
When trigger is set, the default field shell is replaced. The slot is wrapped in an anchor used to position the calendar panel.
- Include a focusable control (e.g.
<button type="button">). Date appliesaria-haspopup="dialog"andaria-expanded. It doesn't setaria-controls: the panel is inside the component's shadow root, which an ID reference from the light DOM can't reach. - Click toggles the panel. Enter / Space and ArrowDown open it and move focus in; Enter / Space close it again.
- Prefer the
valueslot when you only need to customize the selected-date display. - If both are set,
triggerwins andvalueis ignored.
Forms
The name you set on <catnip-input-date> participates in form serialization. Single mode submits the committed string value. Range mode serializes the committed object as JSON for form-associated custom element submission.
Test selectors
The panel and everything in it (days, presets, time inputs, actions) render inside the component's shadow root. Query them through element.shadowRoot or a selector engine that pierces open shadow roots: Playwright locators do by default; Cypress needs includeShadowDom: true.
| Attribute | Default | Applies to |
|---|---|---|
root | catnip-input-date | Field wrapper |
control | catnip-input-date-control | Combobox text field |
value | catnip-input-date-value | Custom value slot mount |
clearButton | catnip-input-date-clear | Inline reset button |
chevron | catnip-input-date-chevron | Trailing open/close chevron |
previousButton | catnip-input-date-previous | Previous month / year-range button |
nextButton | catnip-input-date-next | Next month / year-range button |
viewToggle | catnip-input-date-view-toggle | Month/year header toggle |
panel | catnip-input-date-panel | Calendar dialog panel |
presets | catnip-input-date-presets | Range preset list |
preset | catnip-input-date-preset | Range preset row |
month | catnip-input-date-month | Month block in the panel |
grid | catnip-input-date-grid | Calendar grid |
day | catnip-input-date-day | Calendar day button |
yearGrid | catnip-input-date-year-grid | Year selection grid |
year | catnip-input-date-year | Year selection button |
monthGrid | catnip-input-date-month-grid | Range month selection grid |
monthName | catnip-input-date-month-name | Range month selection button |
time | catnip-input-date-time | Optional time section |
singleTimeInput | catnip-input-date-time-input | Single-date time input |
startTimeInput | catnip-input-date-start-time-input | Range start time input |
endTimeInput | catnip-input-date-end-time-input | Range end time input |
startLabel | catnip-input-date-start-label | Vertical range+time From label |
endLabel | catnip-input-date-end-label | Vertical range+time To label |
actionResetButton | catnip-input-date-action-reset | Panel Reset action button |
actionCancelButton | catnip-input-date-action-cancel | Panel Cancel action button |
actionSubmitButton | catnip-input-date-action-submit | Panel Apply action button |
dismissButton | catnip-input-date-dismiss | Visually hidden Close button |
liveRegion | catnip-input-date-live-region | Calendar announcement region |
supportingText | catnip-input-date-supporting-text | Supporting message |