Input Tag
A tag / token multiselect. Type a value and press Enter to add a removable chip, or pick from an optional suggestion list. Form-associated via ElementInternals — the selected tokens submit with the enclosing form.
Preview
<esa-input-tag label="Tags" placeholder="Add a tag"></esa-input-tag> Suggestions
Pass an options array (as a property) to drive a filtered suggestion dropdown. Typed values and picked suggestions coexist — anything not in the list is added as a free-form token.
<esa-input-tag label="Species" placeholder="Search or add a species"></esa-input-tag>
<!-- el.options = [{ value, label }, ...] --> Sizes
<esa-input-tag label="Extra small" size="xs"></esa-input-tag>
<esa-input-tag label="Small" size="sm"></esa-input-tag>
<esa-input-tag label="Medium" size="md"></esa-input-tag>
<esa-input-tag label="Large" size="lg"></esa-input-tag> States
The required asterisk, hint text, and disabled state are attribute-driven. Disabled fields keep their chips but drop the remove buttons.
<esa-input-tag label="Required" required placeholder="At least one tag"></esa-input-tag>
<esa-input-tag label="With hint" hint="Press Enter to add each tag."></esa-input-tag>
<esa-input-tag label="Disabled" disabled></esa-input-tag> Form participation
The element is form-associated. Submitting logs the FormData entry for tags — a comma-joined list of token values.
<form>
<esa-input-tag label="Tags" name="tags" placeholder="Add then submit"></esa-input-tag>
<button type="submit">Submit</button>
</form> API
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | '' | Visible label rendered above the field. |
hint | string | '' | Helper text shown below the field. |
placeholder | string | 'Search or add...' | Placeholder shown when no tokens are selected. |
options | { value, label }[] | [] | Optional suggestion list. Set as a property. When provided, a chevron toggles a filtered dropdown. |
value | string[] | [] | Selected tokens. Set/read as a property; mirrored to the form as a comma-joined string. |
size | 'xs' | 'sm' | 'md' | 'lg' | 'md' | Control size (shared form scale). |
required | boolean | false | Marks the field required and renders an asterisk on the label. |
disabled | boolean | false | Disables interaction and hides the remove buttons. |
name | string | '' | Form field name used when the element participates in a form. |
Events
| Event | Type | Default | Description |
|---|---|---|---|
change | CustomEvent<{ value: string[] }> | — | Fired whenever tokens are added or removed. Composed and bubbling; detail.value is the current array of token values. |
Accessibility
- The text field carries
role="combobox"witharia-expanded,aria-haspopup="listbox", andaria-autocomplete="list". - The dropdown is
role="listbox"; each suggestion and the "Add" row arerole="option"witharia-selected. - Keyboard:
Enteradds the typed value or the highlighted suggestion,ArrowUp/ArrowDownmove through suggestions,Backspaceon an empty field removes the last token,Escapecloses the dropdown. - Each chip's remove button has an
aria-labelnaming the token it removes; outside-click closes the dropdown. - Form-associated via
ElementInternals— values submit with the enclosing form.
Theming surface
Every public token this component reads, extracted from its source at build time.
The Resolves to column walks each token's real lineage —
component → semantic → primitive → raw value — so you
can see exactly where re-pointing it lands. A spoke re-skins by overriding these in
its theme-<slug>.css; component internals are never edited. No
hook for what you need? File it with /request-lego.
| Token | Tier | Resolves to |
|---|---|---|
--focus-ring-color | component | #65ba74 via --color-border-focus → --color-grass-8 |
--focus-ring-width | component | 2px |
--form-bg | component | #fcfcfc via --color-surface → --color-gray-1 |
--form-bg-disabled | component | #f0f0f0 via --color-disabled-bg → --color-gray-3 |
--form-border-color | component | #cecece via --color-border → --color-gray-7 |
--form-border-color-focus | component | #65ba74 via --color-border-focus → --color-grass-8 |
--form-border-color-hover | component | #bbbbbb via --color-border-strong → --color-gray-8 |
--form-border-width | component | 1px |
--form-font-size-lg | component | clamp(0.875rem, 0.77rem + 0.52vw, 1.125rem) via --type-size-300 |
--form-font-size-md | component | clamp(0.75rem, 0.66rem + 0.44vw, 0.9375rem) via --type-size-200 |
--form-font-size-sm | component | clamp(0.625rem, 0.56rem + 0.32vw, 0.75rem) via --type-size-100 |
--form-font-size-xs | component | clamp(0.5rem, 0.44rem + 0.3vw, 0.625rem) via --type-size-050 |
--form-height-lg | component | 48px |
--form-height-md | component | 40px |
--form-height-sm | component | 32px |
--form-height-xs | component | 28px |
--form-help-color | component | #838383 via --color-text-muted → --color-gray-10 |
--form-label-color | component | #646464 via --color-text-secondary → --color-gray-11 |
--form-padding-x-lg | component | 1rem via --spacing-400 |
--form-padding-x-md | component | 0.75rem via --spacing-300 |
--form-padding-x-sm | component | 0.625rem via --spacing-250 |
--form-padding-x-xs | component | 0.5rem via --spacing-200 |
--form-padding-y-lg | component | 0.75rem via --spacing-300 |
--form-padding-y-md | component | 0.5rem via --spacing-200 |
--form-padding-y-sm | component | 0.375rem via --spacing-150 |
--form-padding-y-xs | component | 0.25rem via --spacing-100 |
--form-placeholder-color | component | #838383 via --color-text-muted → --color-gray-10 |
--form-radius-lg | component | 0.5rem via --radius-200 |
--form-radius-md | component | 0.5rem via --radius-200 |
--form-radius-sm | component | 0.25rem via --radius-100 |
--form-radius-xs | component | 0.25rem via --radius-100 |
--form-text-color | component | #202020 via --color-text-primary → --color-gray-12 |
--icon-size-small | component | 16px |
--color-active-overlay | semantic | rgba(0, 88, 98, 0.08) |
--color-border-light | semantic | #d9d9d9 via --color-gray-6 |
--color-danger-strong | semantic | #ce2c31 via --color-red-11 |
--color-disabled-text | semantic | #8d8d8d via --color-gray-9 |
--color-hover-overlay-strong | semantic | rgba(0, 0, 0, 0.05) |
--color-primary-strong | semantic | #2a7e3b via --color-grass-11 |
--color-surface | semantic | #fcfcfc via --color-gray-1 |
--color-surface-sunken | semantic | #f0f0f0 via --color-gray-3 |
--color-text-muted | semantic | #838383 via --color-gray-10 |
--color-text-primary | semantic | #202020 via --color-gray-12 |
--color-text-secondary | semantic | #646464 via --color-gray-11 |
--font-sans | semantic | 'DM Sans', sans-serif |
--transition-fast | semantic | 150ms ease |
--type-size-100 | semantic | clamp(0.625rem, 0.56rem + 0.32vw, 0.75rem) |
--type-size-150 | semantic | clamp(0.6875rem, 0.61rem + 0.38vw, 0.875rem) |
--type-size-200 | semantic | clamp(0.75rem, 0.66rem + 0.44vw, 0.9375rem) |
--font-weight-medium | primitive | 500 |
--radius-full | primitive | 9999px |
--shadow-200 | primitive | 0 4px 20px -4px rgba(0, 0, 0, 0.06) |
--spacing-050 | primitive | 0.125rem |
--spacing-100 | primitive | 0.25rem |
--spacing-200 | primitive | 0.5rem |
--spacing-300 | primitive | 0.75rem |
--z-dropdown | primitive | 50 |