<!-- GENERATED by scripts/build-llms.mjs from llms/forms.md — do not edit this file. -->

# `lr-date-input`

- **Import** `import '@aceshooting/lyra-ui/components/lr-date-input.js';` (stable tag alias; registers the tag)
- **Class** `LyraDateInput`, also available unregistered from `@aceshooting/lyra-ui/components/forms/date-picker/date-input.class.js`
- **Family** `components/forms/` — see `llms/index.md` for its siblings
- **Status** `experimental` since `4.0.0` — see the maturity and deprecation policy in `llms/shared.md`
- **Release history** [CHANGELOG.md](../../CHANGELOG.md); family-wide breaking-change summaries: [llms-full.txt](../../llms-full.txt)
- **Deprecations** none
- **Optional peers** none
- **Themeable via** 22 parts, 24 custom properties — see this component's own `@csspart`/`@cssprop` list below
- **Documented with** `lr-date-picker` (same section below)
- **Library-wide behavior** (events, form association, `locale`/`strings`, tokens, TS types): `llms/shared.md`

---

## `lr-date-picker` / `lr-date-input` (+ `calendar-core.ts`)

Mirrors the `<wa-date-picker>`/`<wa-date-input>` 3.11 public API under `lr-`. Both
components are **experimental since 3.8**. Values use ISO 8601: `YYYY-MM-DD` (single) or
`YYYY-MM-DD/YYYY-MM-DD` (range).

For `lr-date-input`, host `aria-describedby` targets resolve in the host's root and describe the
native combobox input through element references, before its existing error and hint descriptions.
Replacement, removal, reinsertion, newly resolved IDs, reconnect, and adoption update those
relationships. Removing `label`, `hint`, or `error-text` safely omits that copy while preserving
`null` property readback; later supplied values render normally.

`lr-date-input`'s `clearLabel`, `openLabel`, and `dialogLabel` retain their initial defaults (`''`,
`''`, and `'Choose date'`). Omitted labels localize; explicitly supplied text, built-in English
labels, and empty strings win over `.strings`. Attribute removal restores localization while
preserving `null` readback. The composed calendar applies the same range endpoint and
inclusive-length admission rules as the standalone picker.

An authored host `aria-label` names an enclosing calendar group. Individual month and selection
grids retain their distinct generated period names; changing or removing the purpose label updates
the group without changing a caller-authored host role. Removing the `value` attribute renders an
empty selection safely while preserving `null` readback; a subsequent valid value works normally.

Live constraints repair roving state without moving focus from an unrelated control. If a focused
cell becomes unavailable, focus recovers onto an enabled cell. Explicit distant bounds seed the
bounded automatic search within the permitted domain; a genuinely empty domain has no enabled roving
stop. Selected day and range-endpoint buttons retain their foreground/background pairing during
hover and press; author state-token overrides remain available. Month/year/decade state buttons
retain the common typography, padding, border reset, and minimum action size.

The `calendar-core.ts` helper `formatISO()` returns an empty string for invalid dates or years
outside `0000`–`9999`, so generated ISO anchors never advertise an unsupported signed or five-digit
year.

The ISO model is proleptic Gregorian in every locale and supports years `0000`–`9999`, including
`0000`–`0099` without JavaScript's `Date` 1900 remap. Navigation anchors remain within that domain;
moving past either boundary leaves a valid roving stop and does not change the selected value. Month/day names and visible day/week digits follow the effective locale while
formatters explicitly select the Gregorian calendar. `lr-date-input` uses locale `formatRange()`
for range presentation and normalizes locale digits plus bidi marks before parsing, so its own
Arabic/Persian display round-trips to the same ISO value.

### `lr-date-picker`

Inline month-grid calendar, not form-associated (used standalone or embedded inside
`lr-date-input`'s popover).

**Properties (28):**

- `dayContent` (JS only): `LyraDatePickerDayContent | undefined`
- `presets: LyraDateRangePreset[] = []` (JS only, new in 11.0.0) —
  `LyraDateRangePreset { label: string; start?: string; end?: string; id?: string }`, where
  `start`/`end` are ISO `YYYY-MM-DD` and `id` is an optional caller-owned
  correlation key, echoed verbatim on `appliedPreset` and never read by the picker itself.
  **Either bound may be omitted (new in 11.1.0)** to mean an OPEN bound, resolving to
  the picker's `min` / `max` respectively — that is how an "All time" preset is expressed. When the
  corresponding `min`/`max` is unset there is nothing to resolve to (a `value` of
  `YYYY-MM-DD/YYYY-MM-DD` has no unbounded spelling), so that preset's button renders **disabled**
  rather than looking live and doing nothing when pressed. Renders a `[part="presets"]` quick-range button row above the calendar, for the
  dashboard time-filter shape (Today / Last 7 days / Last 30 days / This month / All time).
  **Range mode only** — a preset names two dates, so it is ignored for a single-date picker rather
  than rendering a row that cannot do anything; unset renders nothing at all. Applying one commits
  through the same path a two-click selection uses, so the ISO serialization, the `min`/`max`
  clamping and the `input`-then-`change` pair are identical and a consumer's change handler cannot
  tell them apart. A reversed preset normalizes; a malformed one is ignored rather than clearing the
  current value, so a bad entry in a config-driven list never reads as "the user picked nothing".
  Non-array runtime assignments normalize to the empty collection, and null/non-object entries in
  an otherwise valid array are omitted from the rendered row rather than aborting the calendar. Empty and
  whitespace-only labels are also omitted, preserving named siblings. Explicit endpoints clamp to
  `min`/`max` before admission. Both endpoints must be selectable, and the inclusive length must
  satisfy `minRange`/`maxRange`; invalid outcomes render disabled and do not emit value events.
  Interior dates need not all be enabled. A same-day manual completion obeys those same inclusive
  length limits. Long preset labels wrap in narrow allocations, including unbroken text and RTL.
  The active button carries `aria-pressed="true"` and `data-active`. Deliberately the same
  `label`/`start`/`end`/`id` shape as `<lr-time-range>`'s `TimeRangePreset`, so the library has one
  preset vocabulary rather than two — the only difference is the unit (ISO dates, not numbers)
- `appliedPreset: LyraDateRangePreset | undefined` (read-only, new in 11.1.0) — the preset whose
  button produced the current `value`, or `undefined` when the range was picked by hand, cleared, or changed externally.
  `clear()` removes identity before synchronous `input`/`change` listeners run; an external value
  change removes identity silently. An accepted preset retains its exact caller-owned source
  identity through its own update. Read it
  inside your own `change`/`input` handler. It exists because a dashboard filter has to persist
  *which* preset is active rather than the pair it froze to: "Last 7 days" must still mean the last
  7 days after tomorrow's reload. That fact is not recoverable from `value` — re-deriving it by
  string-matching is the mapping table `presets` exists to delete, and it is ambiguous anyway
  (Today and This month coincide on the 1st of a month, and a hand-picked range can equal a
  preset's pair by construction). A property rather than an event detail because `input`/`change`
  here are **native** events, deliberately indistinguishable from a manual selection so existing
  handlers need no special case, and a native `Event` cannot carry a detail without changing type
- `disabled: boolean = false` (reflected)
- `disabledDates: string | string[] | Date[] = ''` (attribute `disabled-dates`)
- `disabledDaysOfWeek: string = ''` (attribute `disabled-days-of-week`)
- `disableFuture: boolean = false` and `disablePast: boolean = false` (reflected)
- `firstDayOfWeek: LyraDatePickerFirstDayOfWeek = 'auto'` (`'auto'|'sun'|'mon'|'tue'|'wed'|
'thu'|'fri'|'sat'`; attribute
  `first-day-of-week`, reflected)
- `focusedDate: string = ''` (attribute `focused-date`, reflected)
- `isDateDisabled?: (date: Date) => boolean` (JS only)
- `locale: string = ''` (reflected; malformed tags fall back to the platform locale)
- `max: string = ''` and `min: string = ''` (reflected ISO bounds)
- `maxRange: number = 0` and `minRange: number = 0` (attributes `max-range`/`min-range`, reflected;
  positive values count both range endpoints)
- `mode: 'single'|'range' = 'single'` (reflected; unknown values normalize to `single`)
- `months: 1|2 = 1` (reflected; finite values are truncated and clamped to `1..2`)
- `pageBy: 'months'|'single' = 'months'` (attribute `page-by`, reflected)
- `readonly: boolean = false` (reflected)
- `size: LyraSize = 'm'` (reflected; the shared `2xs`–`xl` ladder plus
  `small`/`medium`/`large` aliases)
- `today: string = ''` (reflected ISO override for deterministic today styling/constraints)
- `value: string = ''` (reflected)
- `valueAsDate: Date | null` and `valueAsRange: { from: Date|null; to: Date|null }` (JS-only
  accessors; setters are silent and normalize reversed ranges)
- `view: 'days'|'months'|'years'|'decades' = 'days'` (reflected)
- `weekdayFormat: 'narrow'|'short'|'long' = 'short'` (attribute `weekday-format`, reflected)
- `withOutsideDays: boolean = false` and `withWeekNumbers: boolean = false` (reflected)

Lyra retains the additive `previousLabel`/`nextLabel` accessible-label overrides and the `selection`
range getter. Their initial readback remains `'Previous month'` and `'Next month'`; omitted labels
localize, while explicit text, the built-in English labels, and empty strings win over locale and
`.strings` copy. Removing either label attribute restores localized omission while preserving
`null` property readback.

**Methods:** `clear()`, `focus(options?)`, `goToToday()`, and
`goToDate(date: string | Date)`. Valid navigation dates are clamped to `min`/`max`; invalid values
are ignored.

**Keyboard:** The day grid uses one roving Tab stop. Month, year, and decade selection views do the
same: Arrow keys move through their four-column visual grid (with horizontal movement mirrored in
RTL), Home/End move to the first/last enabled period in the current page, and Enter/Space drills
into the focused period. Moving beyond a selection-grid edge opens the adjacent period page;
disabled periods never receive the roving focus. A period is enabled only when it contains at least
one date selectable under the active bounds, past/future limits, disabled dates/weekdays, predicate,
and pending-range limits; activating an unavailable period is a no-op.

**Events:** all are non-cancelable. `input` is a bubbling/composed native `InputEvent` (including
the first endpoint of a range); `change` is a bubbling/composed native `Event` for committed
values. `lr-focus-day` carries `{ date: Date }`, and `lr-view-change` carries `{ view, date }`.

**Slots:** `header`, `previous-icon`, `next-icon`, and `footer`. A dynamic
`day-YYYY-MM-DD` slot is also accepted as a Lyra extension and takes precedence over `dayContent`.

**Custom states:** `disabled`, `range`, and `readonly`.

**CSS parts (37):** `date-picker` / permanent compatibility name `base` (tokens on the same
visible shell; both names remain supported), `day`, `day-disabled`, `day-label`, `day-outside`,
`day-placeholder`, `day-range-end`, `day-range-inner`, `day-range-preview`, `day-range-start`,
`day-selected`, `day-today`, `day-weekend`, `footer`, `grid`, `header`, `month`, `month-label`,
`months`, `nav`, `next`, `previous`, `title`, `view-cell`, `view-grid`, `view-item`,
`presets` (the quick-range row), `preset-button` (one quick-range button; carries `data-active`
while its range is the current value),
`view-item-disabled`, `view-item-selected`, `view-item-today`, `view-row`, `weekday`, `weekdays`,
`weeknumber`, and `weeknumbers`. Lyra additionally retains the existing `week` part.

**Themeable custom properties:** `--lr-cell-size` (default `2.25rem`, controls day-cell/grid-column
size; its private default follows the `size` tier — `2xs`/`xs`/`s`/`l`/`xl`; `m` keeps the
default). An inherited or direct public value remains authoritative in every tier.

**Optional peer deps:** none.

### `lr-date-input`

Text field + calendar popover, **form-associated** via the shared `FormAssociated` mixin (`name`,
`value`, `disabled`, `required` all inherited).

**Properties (44):**

- `appearance: 'filled'|'outlined'|'filled-outlined' = 'outlined'` (reflected)
- `appliedPreset: LyraDateRangePreset | undefined` (read-only, new in 12.0.0) — the `presets` entry
  whose button produced the current `value`, or `undefined` when the value was picked on the
  calendar, typed into the field, cleared, or reset. Read it inside your own `change`/`input`
  handler; it is updated before those events are relayed, so a handler observes the preset that
  caused the very commit it is handling, and it is `undefined` while the popover has never been
  opened. Mirrors the nested `lr-date-picker`'s identically-named property across this component's
  shadow boundary, which is where the readback is actually needed: the compact
  text-field-plus-popover shape is the one a dashboard filter uses, the nested picker instance is
  unreachable from outside (a CSS part cannot yield it), and the fact is not recoverable from
  `value` — re-deriving it by string-matching is the mapping table `presets` exists to delete and is
  ambiguous anyway. A property rather than an event detail because `input`/`change` here are
  **native** events that cannot carry one
- `assumeInteractionOn: string[] = ['input']` (JS only)
- `autocomplete: string = ''`
- `dayContent?: LyraDatePickerDayContent` (JS only)
- `defaultValue: string = ''` (reset value; reflected through the `value` content attribute)
- `disabled: boolean = false`
- `disabledDates: string | string[] | Date[] = ''` and `disabledDaysOfWeek: string = ''`
- `disableFuture: boolean = false` and `disablePast: boolean = false` (reflected)
- `distance: number = 0` (reflected; finite offset from the anchor)
- `firstDayOfWeek: LyraDateInputFirstDayOfWeek = 'auto'` (reflected)
- `form: HTMLFormElement | null` (JS-only FACE owner)
- `hint: string = ''`
- `isDateDisabled?: (date: Date) => boolean` (JS only)
- `label: string = ''`
- `max: string = ''` and `min: string = ''` (reflected ISO bounds)
- `maxRange: number = 0` and `minRange: number = 0` (reflected; positive values include both
  endpoints)
- `mode: 'single'|'range' = 'single'` (reflected)
- `months: 1|2 = 1` (reflected; finite values are truncated and clamped)
- `name: string = ''` (reflected)
- `open: boolean = false` (reflected)
- `pageBy: 'months'|'single' = 'months'` (reflected)
- `pill: boolean = false` (reflected)
- `placement: LyraDateInputPlacement = 'bottom-start'` (reflected; all 12 side/alignment
  placements are accepted)
- `presets: LyraDateRangePreset[] = []` (JS only, new in 11.1.0) — forwarded verbatim to the nested
  `lr-date-picker`, whose own `presets` documents the semantics (range mode only, open bounds
  resolving to `min`/`max`, unset renders nothing). Forwarded rather than reimplemented because the
  picker lives in this component's shadow root, so a consumer has no route to it. The row's parts
  are re-exported as `presets`/`preset-button`, and `appliedPreset` above reports which entry
  produced the current value
- `readonly: boolean = false` and `required: boolean = false` (reflected)
- `size: LyraSize = 'm'` (reflected; `2xs`–`xl` and aliases)
- `today: string = ''` (reflected ISO override)
- `validationTarget: HTMLElement | undefined` (JS only) — writable native-validity focus anchor.
  It defaults to the internal input after first render; assign another element to override it, or
  assign `undefined` to restore the internal input
- `validators: LyraDateInputValidator[] = []` (JS only) — each entry may be a
  `(value, input) => void | boolean | string | ValidityStateFlags` function, an object with
  `validate(value, input)` returning that same result vocabulary, or a Web Awesome-compatible
  object with `checkValidity(input)`. The mapped object returns
  `{ isValid, message, invalidKeys }`, where `invalidKeys` names `ValidityState` flags; it may also
  expose `observedAttributes` and a string or callback `message`. Changing any listed host
  attribute automatically runs validity again. `isValid: true` passes. Otherwise the listed flags
  are set (`customError` is used when the list is empty) and the returned message wins over the
  validator-level fallback.
- `value: string = ''` (JS property)
- `valueAsDate: Date | null` and `valueAsRange: { from: Date|null; to: Date|null }` (JS-only
  accessors; setters are silent and normalize reversed ranges)
- `weekdayFormat: 'narrow'|'short'|'long' = 'short'` (reflected)
- `withClear: boolean = false`, `withHint: boolean = false`, and `withLabel: boolean = false`
- `withOutsideDays: boolean = false` and `withWeekNumbers: boolean = false` (reflected)

Lyra retains additive native-wrapper and form-chrome properties: `placeholder`, `locale`,
`errorText`, `accessibleLabel` (attribute `aria-label`), `clearLabel`, `openLabel`, `dialogLabel`,
`spellcheck`, `autocapitalize`, `autoCorrect` (attribute `autocorrect`), `inputMode: string = ''`
(attribute `inputmode`), `enterKeyHint: string = ''` (attribute `enterkeyhint`), and the reflected
`customError: string | null` (attribute `custom-error`). `withLabel` and `withHint` are SSR hints:
they force those slot wrappers into the first render so server output and hydration have the same
structure even before assigned-slot state is observable. The shared Lyra FACE contract also
reflects `disabled` and accepts a `form` content-attribute owner ID in addition to the
element-valued `form` IDL.

**Methods:** `blur()`, `clear()`, `focus(options?)`,
`formStateRestoreCallback(state)`, `hide()`, `resetValidity()`, `setCustomValidity(message)`, and
`show()`. The shared form contract additionally exposes `getForm()`, `checkValidity()`, and
`reportValidity()`; Lyra's native wrapper also exposes `click()`. `show()` and `hide()` return promises that settle after their corresponding transition;
they do nothing when already settled, and respect cancellation of their request event. `clear()`
is a no-op while blank, disabled, or readonly; otherwise it emits `lr-clear`, then `input`, then
`change`. Lyra also retains native-wrapper `select()`, `setSelectionRange()`, and `setRangeText()`.
The text input is itself the popup-opening combobox owner: it exposes `role="combobox"`,
`aria-haspopup="dialog"`, and explicit `aria-controls`/`aria-expanded` alongside the expand button.
Host focus/click/show/clear calls are synchronous no-ops as soon as direct or fieldset disablement
starts, including before Lit has updated the inner native controls.

**Getters:** `input: HTMLInputElement | undefined` — the internal native `<input>`, for direct DOM
access.

**Selection properties:** `selectionStart`, `selectionEnd`, and `selectionDirection` mirror the
internal native date input.

**Events:** `input` is an `InputEvent`, `change` is an `Event`, and `focus`/`blur` are
`FocusEvent`s preserving `relatedTarget`; each is dispatched exactly once from the host and is
bubbling, composed, and non-cancelable. `lr-show`/`lr-hide` are cancelable requests emitted before state changes;
`lr-after-show`/`lr-after-hide` are non-cancelable and fire after rendering and popup animations
settle. `lr-clear` is non-cancelable. `lr-invalid` **is** cancelable: `preventDefault()` on it
suppresses the browser's native validation bubble and `reportValidity()`'s focus/scroll of this
control, without making the control valid — see "The validity alias is cancelable in 8.0.0" above.

**Slots (10):** `clear-icon`, dynamic `day-YYYY-MM-DD`, `end`, `expand-icon`, `footer`, `hint`,
`label`, `next-icon`, `previous-icon`, and `start`. Lyra additionally retains `error`, which
overrides `errorText`.

The editable input shrinks first in a constrained row; `start` and `end` adornments are each
capped at 40% and ellipsize unbroken content. Clear and calendar actions retain their fixed target.

**Custom states:** `blank`, `disabled`, `open`, and `range`; the shared form-associated mixin also
exposes its validity states.

**CSS parts (21):** `clear-button`, `date-input`, `date-picker`, `presets` and `preset-button`
(forwarded from the nested `lr-date-picker` via `exportparts`, so the quick-range row is styleable
from outside — new in 11.1.0), `end`, `expand-button`,
`expand-icon`, `form-control`, `form-control-input`, `form-control-label`, `hint`, `input`,
`input-wrapper`, `popup`, `range-separator`, `segment`, `segment-literal`, `start`, permanent
compatibility name `base` (a nested wrapper inside `date-input`), and permanent compatibility name
`label` (the inner label-content wrapper inside `form-control-label`). Lyra additionally retains
`error`.

**Form value and validation:** a complete range submits `YYYY-MM-DD/YYYY-MM-DD`. A first range
endpoint remains visible in `value` but contributes the empty string to `FormData` until the second
endpoint is selected. `min`/`max`, past/future limits, disabled dates/weekdays, the predicate,
range length, `required`, and configured validators all feed FACE validity. Reset and state restore
use the same normalization path as direct property writes.

**The required marker.** `required` with a non-empty `label` paints the library's shared marker on
`[part="form-control-label"]` — the one `::after` rule described above, not a copy of it, so
`--lr-form-control-required-content`, `--lr-form-control-required-color` and
`--lr-form-control-required-offset` retune or suppress it here exactly as they do on `lr-input`.
With no label text the part is hidden and no glyph is painted.

**Themeable custom properties:** `--lr-date-input-padding-block` (default
`--lr-form-control-padding-block`) pads the native text input; `--lr-date-input-padding-inline`
(default `--lr-form-control-padding-inline`) pads the row. `--lr-date-input-font-size` defaults to
`--lr-form-control-font-size`. The row floor `--lr-date-input-control-min-height` defaults to
`--lr-form-control-height`. All four defaults follow the shared size ladder, including
`small`/`medium`/`large` aliases and inherited `--lr-theme-form-control-height-*` overrides.
Inherited or direct public values win. `pill` changes the private radius default to
`--lr-radius-pill`; a public `--lr-date-input-radius` still wins.

Calendar and clear actions fit inside the selected row height while retaining at least 24×24
CSS-pixel targets. At the default root size, rows measure 30/40/48/56px for s/m/l/xl; 2xs and xs
grow to 26px to accommodate the 24px action plus the row borders. Custom content or font metrics
can grow an unconstrained row. A theme setting the small tier to 36px produces a 36px date row.
`--lr-date-input-control-height` remains undeclared by default and pins an exact row height when
set. If it forces a row below its target size, the action overflows instead of shrinking.

The mapped `--show-duration` and `--hide-duration` hooks independently retime the popup's enter
and exit transitions; both default to `var(--lr-transition-fast)`. Clear and calendar action
state hooks are `--lr-date-input-action-hover-color`, `--lr-date-input-action-hover-bg`, and
`--lr-date-input-action-hover-radius` (defaults: text, transparent, and the input radius), plus
`--lr-date-input-action-active-color`, `--lr-date-input-action-active-bg`, and
`--lr-date-input-action-active-radius` for the pressed state. They inherit from theme ancestors;
direct values on `lr-date-input` win without retuning library-wide tokens.

`--lr-positioning-strategy` (16.0.0) — the calendar popup reads this same cascading
`absolute`/`fixed` override documented on `<lr-popover>` when it is (re)positioned, falling back to
its own `fixed` default when nothing is set. There is no per-instance `positioning-strategy`
property on `<lr-date-input>`; set the custom property on `:root`, a theme, or one clipping
ancestor to change every unset date input beneath it.

**Optional peer deps:** none.

```html
<lr-date-input
  id="di"
  label="Start date"
  with-clear
  name="start"
></lr-date-input>
<script type="module">
  const di = document.getElementById("di");
  di.value = "2026-07-10";
  di.addEventListener("change", () => console.log(di.value)); // ISO string
</script>
```

`calendar-core.ts` exports the pure date math both components share (**internal — not re-exported
from the package root**, but worth knowing when reasoning about behavior): `parseISO`, `formatISO`,
`isSameDay`, `addMonths`, `addMonthsClampingDay(date, n)` (like `addMonths` but clamps the result to
the target month's last day instead of overflowing into the month after — e.g. Jan 31 + 1 month
lands on Feb 28/29, not Mar 3; backs `lr-date-picker`'s PageUp/PageDown), `clampDate`,
`monthMatrix(year, month, firstDayOfWeek)`, `weekdayLabels(firstDayOfWeek, format, locale)`,
`monthTitle(year, month, locale)`, `resolveFirstDayOfWeek(value, locale?)`,
`normalizeCalendarMode(value)`, `normalizeCalendarMonths(value)`, `normalizeWeekdayFormat(value)`,
and `dateTimeFormat(locale, options)`.

**Known gotchas:**

- `first-day-of-week="auto"` now derives from `locale` when the runtime's `Intl.Locale` exposes
  week-info (`weekInfo`/`getWeekInfo()`, still shifting between engines) — `resolveFirstDayOfWeek()`
  only hardcodes Sunday as the fallback when that isn't available or `locale` is unset. A
  French/German-locale user with a supporting runtime now gets the Monday-first grid their OS would
  show; on an older runtime it still falls back to Sunday-first silently. **If you need a guaranteed
  locale-correct week start regardless of runtime**, pass an explicit
  `first-day-of-week="mon"` (accepted values: `sun`/`mon`/`tue`/`wed`/`thu`/`fri`/`sat`).
- Runtime attribute/property inputs are normalized before calendar math or `Intl` formatting:
  unsupported modes and weekday formats use their documented defaults, month counts cannot exceed
  the two-grid API, malformed locale tags use the platform locale, and invalid `Date` objects do
  not replace the current view.
- (date-input only) — opening the calendar does not move focus into it: `Alt+ArrowDown` leaves focus
  on the text input, and the expand button keeps focus when clicked. The focused opener is
  remembered. Escape and a finalized calendar selection close the popup and return focus to that
  same connected element; a direct `.open = false` also restores it when focus would otherwise
  remain inside the now-hidden popup. Outside-pointer dismissal deliberately leaves focus at the
  clicked target.
- (date-input only) — typing an unparseable string and blurring/committing sets `badInput` via
  `internals.setValidity()` and reverts the displayed text to the last valid commit. The visible
  `<input part="input">` mirrors `required` through native `required`/`aria-required` and exposes
  touched outer validity through `aria-invalid`; this includes required, bad-input, and range
  failures owned by the form-associated host. A host `aria-label` is reactively forwarded to the
  input, including later changes/removal, and takes precedence over the component's
  label/placeholder fallback. Native validation attempts reveal `aria-invalid`; `form.reset()`
  clears that touched presentation. A parseable typed date outside an active bound is committed
  instead and reports the precise `rangeUnderflow`/`rangeOverflow` state.
- (date-input only) — declarative, IDL, reset, and restored values are sanitized to exact ISO dates:
  calendar-invalid or malformed strings become `''` (and therefore `valueMissing` when required),
  while valid dates outside `min`/`max` or `disable-past`/`disable-future` remain submitted and expose
  `rangeUnderflow`/`rangeOverflow`. Changes to these constraints, `mode`, and `readonly` recompute
  validity synchronously; range mode validates both endpoints.
- The grid keyboard pattern (Arrow/PageUp/PageDown/Home/End navigation with correct focus
  sequencing) is implemented correctly and safe to rely on, as is the selected/range-day text color
  (`--lr-date-picker-selected-color`, defaulting to `--lr-color-on-brand`, not a hardcoded literal).
  Override selected background and foreground together to preserve contrast.

**Additional API surface:**

- `--lr-date-picker-month-gap` — Gap between visible months. Default: `var(--lr-space-l)`.
- `--lr-date-picker-header-gap` — Month-header child gap. Default: `var(--lr-space-s)`.
- `--lr-date-picker-radius` — Calendar and control corner radius. Default: `var(--lr-radius)`.
- `--lr-date-picker-nav-hover-bg` — Hover background of the previous/next month-navigation buttons.
  Default: `var(--lr-color-brand-quiet)`. An inline `var()` fallback rather than a `:host`
  declaration, and the rule wraps its selector in `:where()` so a consumer's own
  `::part(previous):hover` still wins without `!important`.
- `--lr-date-picker-nav-active-bg` — Pressed navigation background; defaults to the hover color
  mixed by `--lr-color-mix-active`.
- `--lr-date-picker-preset-hover-bg`, `--lr-date-picker-preset-active-bg`, and
  `--lr-date-picker-preset-selected-bg` (new in 11.0.0) — hover, pressed, and
  currently-selected paint for a `presets` quick-range button. Defaults are
  `var(--lr-color-brand-quiet)`, that hover colour mixed by `--lr-color-mix-active`, and
  `var(--lr-color-brand)` respectively.
- `--lr-date-picker-preset-selected-border` (default `var(--lr-color-brand)`) and
  `--lr-date-picker-preset-selected-color` (default `var(--lr-color-on-brand)`) independently
  theme a selected preset's border and foreground; the selected background token controls only its
  background.
- `--lr-date-picker-title-hover-color`, `--lr-date-picker-title-active-color`,
  `--lr-date-picker-title-active-bg`, and `--lr-date-picker-title-active-radius` — Month-title
  hover/press paint and pressed shape; defaults to brand, brand, brand-quiet, and
  `var(--lr-date-picker-radius)` respectively.
- `--lr-date-picker-day-hover-bg` and `--lr-date-picker-day-active-bg` — Day hover/press
  backgrounds; the pressed default mixes the hover hook by `--lr-color-mix-active`.
- `--lr-date-picker-day-outside-color`, `--lr-date-picker-today-outline`,
  `--lr-date-picker-disabled-color`, and `--lr-date-picker-disabled-opacity` — adjacent-month,
  today, and disabled-day paint; defaults preserve the quiet-text, brand, and shared disabled
  tokens.
- `--lr-date-picker-range-bg`, `--lr-date-picker-range-preview-bg`, and
  `--lr-date-picker-range-color` — range-interior, pending-preview, and adjacent-month range text
  paint. The preview defaults to the range background hook.
- `--lr-date-picker-selected-bg` and `--lr-date-picker-selected-color` — selected day and range
  endpoint paint; defaults to brand/on-brand.
- `--lr-date-picker-view-hover-bg`, `--lr-date-picker-view-active-bg`,
  `--lr-date-picker-view-selected-bg`, `--lr-date-picker-view-selected-color`,
  `--lr-date-picker-view-today-outline`, and `--lr-date-picker-view-disabled-opacity` — the
  corresponding month/year/decade selection-view states, independently themeable from day cells.
- `--lr-date-input-placeholder-color` — Placeholder text color. Default: `var(--lr-color-text-quiet)`.
- `--lr-date-input-gap` — Gap between input-row children. Default: `var(--lr-space-xs)`.
- `--lr-date-input-radius` — Input-row corner radius. Default: `var(--lr-radius)`.
- `--lr-date-input-focus-border-color` — Focused row border color. Default: `var(--lr-color-brand)`.
- `--lr-date-input-fill` — Resting input-row background, public since 16.0.0 (the radius beside it
  always was). Default: `var(--lr-color-surface)`, or `var(--lr-color-surface-raised)` under the
  `filled`/`filled-outlined` treatments; a value set here wins over every treatment.
- `--lr-date-input-border-color` — Resting input-row border color. Default:
  `var(--lr-color-border)`, or `transparent` under `filled`.
- `--lr-form-control-focus-shadow` — The shared field halo, painted as a `box-shadow` while the row
  holds focus. Default: `none`. Additive — the focused brand border is never replaced by it.

---
