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

# `lr-token-input`

- **Import** `import '@aceshooting/lyra-ui/components/lr-token-input.js';` (stable tag alias; registers the tag)
- **Class** `LyraTokenInput`, also available unregistered from `@aceshooting/lyra-ui/components/forms/token-input/token-input.class.js`
- **Family** `components/forms/` — see `llms/index.md` for its siblings
- **Status** `stable` 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** 12 parts, 25 custom properties — see this component's own `@csspart`/`@cssprop` list below
- **Library-wide behavior** (events, form association, `locale`/`strings`, tokens, TS types): `llms/shared.md`

---

## `lr-token-input`

An editable form-associated token list. Enter, comma, Tab, or blur commits a token; Tab is not
prevented, so focus still advances normally. Backspace removes the last token. `value` is a
readonly owned `readonly string[]` snapshot and repeated values are submitted under `name`; mutate
a new array and reassign it to change the list.

Composing keyboard events (`isComposing` or legacy key code 229) remain with the native draft or
inline editor without adding/removing tokens, committing an edit or closing the editor. Normal
Enter, delimiter, Tab, Backspace and Escape behavior is unchanged after composition.

Host `aria-describedby` references resolve in the host's root and precede local hint/error guidance
on the native draft input. Live source replacement, removal, reinsertion, host reconnection and
document adoption update the relationship. Removing `label`, `hint` or `error-text` safely removes
that copy while preserving native attribute-removal property readback, including `null`; explicit
empty and later replacement strings remain supported.

Editable token labels vertically center their text within the existing pointer target while
retaining narrow-content ellipsis, wrapping and alignment with the remove action. The ordinary
noneditable token layout remains the default.

**Properties:** live, non-reflecting `value`, reflected `defaultValue` (attribute `value`, encoded
as a JSON string array), `customError` (`custom-error`), `label`, `hint`, `errorText`
(`error-text`), `placeholder`, `name`,
`required`, `disabled`, `accessibleLabel` (attribute `aria-label` — forwarded to the input wrapper
and draft text input; precedence is presence-based, so `aria-label=""` remains an explicit empty
override and suppresses visible-label linkage), `spellcheck: boolean = true`, `autocapitalize: string = ''`, and `autocorrect` (read: `boolean = true`; write: `boolean | string`, attribute values
`on`/`off`) — all three native text-entry hints are forwarded to both the draft input and the inline
token editor. The former camel-case `autoCorrect` property is removed; use the native-shaped
lowercase IDL. `size: '2xs' | 'xs' | 's' | 'm' | 'l' | 'xl' = 'm'` (reflected —
same scale as `lr-input`'s `size`, scaling the input-wrapper's row height and text size across six
tiers, and both `2xs`/`xs`/`s`/`m`/`l`/`xl` and `small`/`medium`/`large` are accepted; the remove
button's hit area stays fixed at `40px` across all sizes), `pill` (reflected, default `false` —
rounds the token row's corners by changing the private radius default to `--lr-radius-pill`; the
chips share the public `--lr-token-input-radius` hook with the row, and an inherited or direct
value remains authoritative),
`allowDuplicates`
(`allow-duplicates`, default `false`), `editable` (reflected, default `false` — see below), and
`delimiter: string | null` (default `','` — see below).
**Slots:** `label`, `hint`, `error`, `start` (adornment before the tokens), `end` (adornment after
the draft input) — both wrapped in a `hidden`-toggling span, mirroring `lr-combobox`'s identical
`start`/`end`.
**Events:** native `InputEvent` `input`, `lr-input`, native `Event` `change`, then `lr-change` for
each list mutation; native events have no detail and both aliases carry a frozen
`{ value: readonly string[] }` snapshot.
Native `FocusEvent` `focus`/`blur` are relayed once from the draft and inline editor, preserving
`relatedTarget`. `lr-add`
(`detail: { value, values }`, where `value` is the final added token and `values` is the frozen,
readonly, complete ordered and deduplicated set of tokens added by that commit — cancelable as of
10.0.0; `preventDefault()` keeps the tokens out of `value` and leaves the typed draft text in the
input unchanged so the user can correct it, rather than clearing it),
`lr-remove`
(`detail: { value, index }` — cancelable; `preventDefault()` keeps the token in `value`
unchanged), and `lr-token-edit`
(`detail: { value, previousValue, index }` — an existing token is about to be edited in place —
cancelable as of 10.0.0; `preventDefault()` keeps the token in `value` unchanged and leaves the
inline editor open with the user's edited text intact, rather than closing and discarding it).
All three mutators now share one veto contract; previously only `lr-remove` could be vetoed.
`lr-invalid` (no detail) is emitted once as a bubbling/composed alias when native validity fails.
**CSS parts:** `form-control`, `form-control-label`, `input-wrapper`, `token`, `token-label` (the
token's text, doubling as the roving-focus edit trigger — rendered only while `editable`),
`token-editor` (the inline text field that replaces a token's text while it is open for editing —
rendered only while `editable` and only for the token being edited), `remove` (the
per-token remove button, floored at the shared `--lr-icon-button-size` tap size around a compact
glyph), `input`, `start`, `end`, `hint`, `error`. `focus()`, `blur()`, `click()`, and `select()` forward to the
internal draft text input. `selectionStart`, `selectionEnd`, and `selectionDirection` are readable/
writable native-selection passthroughs; `setSelectionRange(start, end, direction?)` and
`setRangeText(replacement, start?, end?, selectMode?)` expose the matching native methods.
`setRangeText()` synchronizes the pending draft without emitting `input`/`change`, so the next
delimiter, Enter, or blur commit consumes the edited text. `getForm()` returns the browser-resolved
owning form. `setCustomValidity(message)` carries a
rejection no client-side constraint can express
("that tag is reserved"): a non-empty message raises `customError` and blocks submission, `''`
restores the control's own computed validity so a `required` control with no tokens goes back to
`valueMissing`. It survives every token add, removal and edit, and a `form.reset()`.

`defaultValue` is the current reset target. A live `value` write marks the token list dirty, so a
later default/attribute mutation does not overwrite it; `form.reset()` restores the latest default
and makes the value pristine again. Session restoration uses repeated entries in a `FormData`
state, is independent of the current `name`, accepts early delivery, rejects malformed/file state
to an empty list, and emits no user events.

**`editable` — editing a token in place.** Off by default, in which case the token row renders
exactly as it does without the feature and stays non-focusable. Turn it on and each token becomes a
roving tab stop (one Tab stop for the whole row): click, Enter, Space, or F2 opens an inline
editor on that token; ArrowLeft/ArrowRight move between tokens (swapped under RTL, since they mean
previous/next _visually_), Home/End jump to the first/last. Inside the editor, Enter commits and
returns focus to the token, Escape cancels (and is consumed rather than left to bubble, so an
enclosing dialog or popover does not also close), and blurring commits _without_ pulling focus
back — a blur means the user already aimed focus elsewhere. A changed inline edit commits and emits
its native/alias input-change sequence before the public native/alias blur sequence. Both the draft
and inline editor relay one native bubbling/composed host `focus` or `blur` event while their source
event stays internal.
`lr-token-edit` fires only for an edit
that actually changed something: a reverted, unchanged, emptied, or (under the default
`allowDuplicates = false`) duplicate-colliding edit is discarded silently, mirroring how a
duplicate draft is skipped rather than rejecting the whole entry. Own or fieldset-cascaded
disablement removes every token label's tabindex, renders `aria-disabled="true"`, retires any
internal focus/editor state, and suppresses enabled hover/active paint. Re-enabling renders
`aria-disabled="false"` and restores exactly one roving token stop.
Host `focus()` and `click()` are also synchronous no-ops under own or fieldset-cascaded disablement,
including the same task that sets `disabled` before Lit has updated the still-rendered native draft.
`blur()` remains available to release existing focus.
When a focused token label, editor, or remove action disappears through its own removal or a
controlled `value`/pristine `defaultValue` shrink, DOM focus moves to the nearest surviving
equivalent surface at the clamped index. If no token remains it moves to the draft input; a newer
explicit focus destination outside the component is never reclaimed.

**`delimiter` is nullable, and only a single character acts as a commit key.** It does two separate
jobs: it splits a committed draft into several tokens, and — _only when it is exactly one
character_ — it is the keystroke that commits the draft. A multi-character delimiter still splits a
pasted or committed draft, but no keystroke can ever match it, so nothing commits on typing.
Setting it to `null` disables both, so a token may contain the delimiter verbatim. **`delimiter="null"`
does not work** — that is the four-character string `null`. Use `delimiter="none"`, `delimiter=""`
(both of which the attribute converter maps to `null`), or a property binding
(`.delimiter=${null}`). Removing the attribute restores the `,` default.

**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 under "The required-field marker"
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`. It marks the control, not an individual token. With no label
text the part is hidden and no glyph is painted.

**Themeable custom properties:** `--lr-token-input-padding` (the input-wrapper padding, scaled by
`size`), `--lr-token-input-font-size` (the input-wrapper and token font size, scaled by `size`),
`--lr-token-input-control-min-height` (the input-wrapper's block-size floor, scaled by `size`),
`--lr-token-input-control-height` (exact input-wrapper height — undeclared by default, leaving the
`--lr-token-input-control-min-height` floor only; set it to a length to both floor and cap the row,
e.g. to pixel-match a sibling field in the same toolbar row). An uncapped row grows as tokens wrap.
A capped row explicitly clips inline overflow and becomes a block-axis scrollport, preserving every
wrapped token and 40px-floored remove/edit action instead of clipping them; keyboard focus scrolls
the destination token into view. `--lr-token-input-input-inline-size`
(the editable input's `flex-basis` inside the wrapped token row; undeclared by default, falling back
inline to `--lr-size-8rem`), `--lr-token-input-min-input-inline-size` (default `--lr-size-4rem`, the
floor that input keeps once tokens have consumed the row), and `--lr-token-input-editor-inline-size`
(default `--lr-size-6rem`, the inline size of the inline token editor opened by `editable`).

**Additional API surface:**

- `--lr-token-input-token-padding` — Per-token chip padding, scaled by `size`.
- `--lr-token-input-gap` — Gap between form/row children. Default: `var(--lr-space-xs)`.
- `--lr-token-input-token-gap` — Gap inside token chips. Default: `var(--lr-space-2xs)`.
- `--lr-token-input-radius` — Row/token corner radius. Default: `var(--lr-radius)`.
- `--lr-token-input-token-bg` — Token chip background. Default: `var(--lr-color-brand-quiet)`.
- `--lr-token-input-action-hover-bg` — Backwards-compatible aggregate edit/remove hover
  background. Default: `var(--lr-color-brand-quiet)`.
- `--lr-token-input-edit-hover-bg` / `--lr-token-input-edit-pressed-bg` — Editable token-label
  hover and pressed backgrounds. The hover hook falls back to
  `--lr-token-input-action-hover-bg`; the pressed hook defaults to its active-state mix.
- `--lr-token-input-remove-hover-bg` / `--lr-token-input-remove-pressed-bg` — Remove-action hover
  and pressed backgrounds, with the same aggregate-hover and active-state fallbacks.
- `--lr-token-input-focus-border-color` — Focused row border color. Default: `var(--lr-color-brand)`.
- `--lr-token-input-fill` — Resting input-row background, public since 16.0.0. Default:
  `var(--lr-color-surface)`.
- `--lr-token-input-border-color` — Resting input-row border color. Default:
  `var(--lr-color-border)`. The invalid and focused states keep their own hooks and still win
  over it.
- `--lr-form-control-focus-shadow` — The shared field halo, painted as a `box-shadow` while the
  row holds focus. Default: `none`. Additive — the focus outline and the focused border are
  never replaced by it.
- `--lr-token-input-invalid-border-color` — Invalid row border color. Default: `var(--lr-color-danger)`.
