GitHub

Select

Form-associated searchable select with single/multi-select, chips, and full keyboard navigation. A Lit Web Component migrated from the Angular esa-select.

Stable wc import '@esa/ecology/esa-select';

Preview

<esa-select label="Fruit" placeholder="Choose a fruit" help-text="Type to filter"></esa-select>

Sizes

<esa-select label="Extra small" size="xs"></esa-select>
<esa-select label="Small" size="sm"></esa-select>
<esa-select label="Medium" size="md"></esa-select>
<esa-select label="Large" size="lg"></esa-select>

States

Multi-select renders chips with chip-mode. Error, required, disabled, and read-only states are all driven by attributes.

<esa-select label="Toppings" multiple chip-mode placeholder="Add toppings"></esa-select>
<esa-select label="Required" required></esa-select>
<esa-select label="Error" error-text="Please make a selection"></esa-select>
<esa-select label="Disabled" disabled></esa-select>

API

PropTypeDefaultDescription
label string '' Field label above the control.
options { label, value, disabled? }[] [] Selectable options. Set as a property (not an attribute).
size 'xs' | 'sm' | 'md' | 'lg' 'md' Control size (reflected attribute).
placeholder string 'Select...' Placeholder shown when nothing is selected.
help-text string '' Helper text below the control.
error-text string '' Error message; sets the error state when present.
required boolean false Marks the field required (renders a * after the label).
disabled boolean false Disables interaction (reflected attribute).
multiple boolean false Allow multiple selections.
searchable boolean true Type-to-filter the option list. When false the input is read-only.
chip-mode boolean false In multi-select, render selected values as removable chips.
value string | string[] Property accessor. Single = string; multiple = string array.

Events

EventTypeDefaultDescription
change CustomEvent<{ value: string | string[] }> Fired on selection change. Composed and bubbles. Form value is the string (single) or comma-joined values (multiple).

Accessibility

  • Input has role="combobox" with aria-expanded, aria-haspopup="listbox", and aria-autocomplete="list".
  • Dropdown is role="listbox"; options are role="option" with aria-selected / aria-disabled.
  • Keyboard: ArrowUp/Down navigate, Enter selects, Escape/Tab close. Outside-click closes.
  • Chip remove buttons carry an aria-label ("Remove {label}").
  • Form-associated via ElementInternals — value submits 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-error component #e5484d via --color-danger--color-red-9
--form-border-color-focus component #65ba74 via --color-border-focus--color-grass-8
--form-border-width component 1px
--form-error-color component #ce2c31 via --color-danger-strong--color-red-11
--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-label-font-size component var(--_field-font-size)
--form-label-font-weight component 500 via --font-weight-medium
--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-medium component 20px
--color-active-overlay semantic rgba(0, 88, 98, 0.08)
--color-danger-border semantic #fdbdbe via --color-red-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
--font-sans semantic 'DM Sans', sans-serif
--transition-fast semantic 150ms ease
--type-size-150 semantic clamp(0.6875rem, 0.61rem + 0.38vw, 0.875rem)
--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