logo dile-components dile-components

dile-select-search

Searchable select for a fixed set of options. Like dile-select, the options are declared through a <select> element in the select slot. Like dile-select-ajax-overlay, the user types in a search field and picks one of the matching options from a floating popup.

The filtering is done locally (no requests), so results appear instantly. It's a good fit for long but known lists: countries, phone prefixes, currencies, languages...

dile-select-search and dile-select-ajax-overlay share the same base class (DileSelectSearchableBase), so they look and behave the same way: same search field, keyboard navigation, read-only selected state with a clear button and form integration. Only the source of the options differs.

Installation

npm i @dile/ui

Usage

Import the component.

import '@dile/ui/components/select/select-search.js';

Use the component, placing a <select> with all the options in the select slot.

<dile-select-search name="country" label="Country" placeholder="Search a country...">
  <select slot="select">
    <option value="ES">España</option>
    <option value="PT">Portugal</option>
    <option value="FR">France</option>
  </select>
</dile-select-search>

The slotted <select> is never displayed: it's only read as the data source. Options added, removed or edited later are picked up automatically.

Note: option values must be unique, just like in a native select. When several options share the same value, the component can't tell them apart and the first one is used to display the selected text.

Properties

Property Type Default Description
label String undefined Label text for the field.
value String undefined The value of the selected option.
name String undefined Name of the field, used in forms.
placeholder String "Search to choose..." Placeholder of the search field.
emptyMessage String "No results found" Message shown when the search doesn't match any option.
showAllOnFocus Boolean false Shows every option as soon as the search field gets the focus, before typing anything. Useful for short lists.
delay Number 0 Debounce delay in milliseconds before filtering. Local filtering is fast, so it's usually not needed.
disabled Boolean false Disables the field.
errored Boolean false Shows the field in the error state.
message String undefined Message displayed below the field.
hideErrorOnInput Boolean false Clears the error state and message when the user interacts with the field.
clearSelectionLabel String "Clear selection" Accessible name of the button that clears the selected option, read by screen readers.
clearSearchLabel String "Clear search" Accessible name of the button that clears the text typed in the search field.
selectedText String undefined Text of the selected option. Assigned automatically, read it rather than setting it.
isSelected Boolean false Read-only state, true when an option is selected.

Search customization

By default an option matches when its text contains the keyword anywhere, ignoring case and accents ("espana" matches "España"). To change it, extend the component and override filterOption(option, keyword). It receives each option as { value, text, icon } and returns a boolean.

import { DileSelectSearch } from '@dile/ui/components/select/index.js';

class PrefixSelect extends DileSelectSearch {
  // Match by text or by value
  filterOption(option, keyword) {
    return super.filterOption(option, keyword) || option.value.startsWith(keyword);
  }
}
customElements.define('prefix-select', PrefixSelect);

Option icons

As in dile-select-overlay, use a data-icon attribute with a dile-iconlib family.name icon string in each <option>, or in the <select> to set a default icon for all options. Remember to import the icons you use.

Methods

Events

Slots

CSS Custom Properties

The search field uses the same custom properties as dile-select-ajax-overlay, and the popup uses the --dile-select-overlay-* properties of dile-select-overlay.

Accessibility

Same pattern as dile-select-ajax-overlay: the search field is a role="combobox", keyboard navigation (ArrowUp/ArrowDown/Home/End/Enter/Escape) is forwarded to the popup and a visually-hidden live region announces the highlighted option. Once an option is selected, the read-only field uses the label as its accessible name.

While there is text in the search field, the clear icon is a real button (reachable with Tab, activated with Enter or Space) named by clearSearchLabel. With an empty field, the search icon is only decorative and hidden from screen readers.

dile-select-search demos

Basic usage

<script type="module">
  import '@dile/ui/components/select/select-search.js';
</script>
<dile-select-search name="prefix" label="Phone prefix" placeholder="Type a prefix or a country...">
  <select slot="select">
    <option value="+34">+34 España</option>
    <option value="+351">+351 Portugal</option>
    <option value="+33">+33 France</option>
    <option value="+39">+39 Italia</option>
    <option value="+44">+44 United Kingdom</option>
    <option value="+49">+49 Deutschland</option>
    <option value="+52">+52 México</option>
    <option value="+54">+54 Argentina</option>
    <option value="+55">+55 Brasil</option>
    <option value="+1">+1 United States, Canada</option>
  </select>
</dile-select-search>

Show all options on focus

<dile-select-search showAllOnFocus label="Language" placeholder="Choose a language...">
  <select slot="select">
    <option value="es">Español</option>
    <option value="en">English</option>
    <option value="fr">Français</option>
    <option value="pt">Português</option>
  </select>
</dile-select-search>

Initial value and events

<script type="module">
  import { LitElement, html } from 'lit';

  class SelectSearchEventsDemo extends LitElement {
    static properties = {
      value: { type: String },
      text: { type: String },
    };
    render() {
      return html`
        <dile-select-search
          value="pt"
          label="Language"
          @element-changed=${e => this.value = e.detail.value}
          @dile-select-search-selected-text-changed=${e => this.text = e.detail.selectedText}
        >
          <select slot="select">
            <option value="es">Español</option>
            <option value="en">English</option>
            <option value="pt">Português</option>
          </select>
        </dile-select-search>
        <p>Value: ${this.value} / Text: ${this.text}</p>
      `;
    }
  }
  customElements.define('select-search-events-demo', SelectSearchEventsDemo);
</script>
<select-search-events-demo></select-search-events-demo>

Errored

<dile-select-search errored message="This field is required" hideErrorOnInput label="Language">
  <select slot="select">
    <option value="es">Español</option>
    <option value="en">English</option>
  </select>
</dile-select-search>