Prototype Library

Filter Dropdown

Interactive dropdown filter (Lit web component) with a searchable panel, full keyboard navigation, and single or multi select. The trigger fills with the primary color when active and shows an inline clear control.

Inherited wc import '@esa/ecology/esa-filter-dropdown';

Preview

Click the trigger to open the panel, type to filter, and use Arrow/Enter/Escape to navigate. Outside-click and Escape close it. Options are set as a property in a small init script.

<esa-filter-dropdown name="status" label="Status"></esa-filter-dropdown>

<script>
  import '@esa/ecology/esa-filter-dropdown';
  customElements.whenDefined('esa-filter-dropdown').then(() => {
    document.querySelector('esa-filter-dropdown').options = [
      { label: 'Open', value: 'open' },
      { label: 'In review', value: 'review' },
      { label: 'Closed', value: 'closed' },
    ];
  });
</script>

Select modes

Single select closes on choice; multiple keeps the panel open, shows checkmarks, and renders "first +N" in the trigger.

<esa-filter-dropdown name="status" label="Status"></esa-filter-dropdown>
<esa-filter-dropdown name="dept" label="Department" multiple></esa-filter-dropdown>

Sizes

<esa-filter-dropdown label="XS" size="xs"></esa-filter-dropdown>
<esa-filter-dropdown label="Small" size="sm"></esa-filter-dropdown>
<esa-filter-dropdown label="Medium" size="md"></esa-filter-dropdown>
<esa-filter-dropdown label="Large" size="lg"></esa-filter-dropdown>

States

Disabled options are dimmed, skipped by keyboard navigation, and not selectable.

<esa-filter-dropdown name="priority" label="Priority"></esa-filter-dropdown>
<!-- options include { label: 'High', value: 'high', disabled: true } -->

API

PropTypeDefaultDescription
name string '' Filter key carried in emitted events.
label string '' Trigger label; selection appends "label: value".
options FilterOption[] [] Selectable options (set as a property, not an attribute).
multiple boolean false Allow multiple selections; trigger shows "first +N".
placeholder string '' Search-input placeholder (defaults to "Search…").
size 'xs' | 'sm' | 'md' | 'lg' 'md' Trigger height, padding, font size, and radius.
Binding these events from Angular

Every one of these is a CustomEvent, so the payload is on $event.detail — $event.target.value reads empty, with no error. The names are lowercase and Angular passes them to addEventListener verbatim, so camel-casing one means it never fires.

  • (esa-filter-change)="onEsaFilterChange($event.detail)"
  • (selection-change)="onSelectionChange($event.detail)"

Events

PropTypeDefaultDescription
selection-change { value: string | string[] } — Fires on every selection change. value is a string in single mode, an array in multiple mode.
esa-filter-change { name, filters: ActiveFilter[] } — Bubbling/composed. Carries the filter name plus the full active-filter list for a parent container to coordinate state.

Accessibility

  • Trigger is a native <button> with aria-haspopup="listbox" and aria-expanded reflecting open state.
  • Panel is a role="listbox"; each option is role="option" with aria-selected and aria-disabled.
  • Keyboard: ArrowUp/Down highlight (skipping disabled), Enter selects, Escape closes. Opening focuses the search input.
  • Visible focus ring on the trigger via --focus-ring-* tokens.