GitHub

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.

Stable wc import '@esa/ecology/esa-input-tag';

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

PropTypeDefaultDescription
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

EventTypeDefaultDescription
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" with aria-expanded, aria-haspopup="listbox", and aria-autocomplete="list".
  • The dropdown is role="listbox"; each suggestion and the "Add" row are role="option" with aria-selected.
  • Keyboard: Enter adds the typed value or the highlighted suggestion, ArrowUp/ArrowDown move through suggestions, Backspace on an empty field removes the last token, Escape closes the dropdown.
  • Each chip's remove button has an aria-label naming 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.

TokenTierResolves 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