Skip to content

Select <l-select>

Build a select two ways. For a simple single select, style a native <select> with class="l-select" — zero-JS and the recommended default. For in-popover search, multi-select with chips, or async options, reach for the <l-select> custom element.

HTML tag<select>
Native
Progressive
Plain
Shadow-DOM
Native element
html
<select class="l-select">
  <option>France</option>
  <option>Germany</option>
</select>

Basic

Add class="l-select" to a native <select> — its <option>s are styled automatically. Built on the Customizable Select API (appearance: base-select), degrading to a native <select> where unsupported — so always keep meaningful text in each <option>.

Code
html
<select
  class="l-select"
  aria-label="Plan"
>
  <option value="free">Free</option>
  <option value="pro">Pro</option>
  <option value="team">Team</option>
</select>

Rich options

Put markup inside each <option> — wrap a .l-select-item-title over a .l-select-item-description in .l-select-item-text (add a .l-select-item-media image or icon before it). A <button><selectedcontent></button> trigger mirrors the chosen option.

Code
html
<select
  class="l-select"
  aria-label="Merge method"
>
  <button>
    <selectedcontent></selectedcontent>
  </button>

  <option
    value="squash"
    selected
  >
    <span class="l-select-item-text">
      <span class="l-select-item-title">Squash and merge</span>
      <span class="l-select-item-description">
        The 1 commit from this branch will be added to the base branch.
      </span>
    </span>
  </option>

  <option value="rebase">
    <span class="l-select-item-text">
      <span class="l-select-item-title">Rebase and merge</span>
      <span class="l-select-item-description">
        The 1 commit from this branch will be rebased and added to the base branch.
      </span>
    </span>
  </option>
</select>

Enhanced: <l-select>

When the native tier isn't enough, the <l-select> custom element adds in-popover search, multi-select with removable chips, and async options. It needs its JS module (import 'luxen-ui/select'). Options are authored as a native <datalist> of <option> — the same surface as <l-combobox> — with <option selected> for pre-selection.

HTML tag<l-select>
Native
Progressive
Plain
Shadow-DOM
Shadow-DOM Custom Element

Searchable

Add searchable for a filter box inside the popover — useful for long lists. Matching is accent/case-insensitive.

Code
html
<div class="w-72">
  <l-select
    searchable
    label="Country"
    name="country"
    placeholder="Select a country…"
  >
    <datalist>
      <option value="us">United States</option>
      <option value="fr">France</option>
      <option value="de">Germany</option>
      <option value="es">Spain</option>
      <option value="it">Italy</option>
      <option value="jp">Japan</option>
      <option value="br">Brazil</option>
    </datalist>
  </l-select>
</div>

Multiple

Add multiple to select several values. The trigger shows a removable <l-tag> chip per value and the form submits one entry per value under name.

Code
html
<div class="w-72">
  <l-select
    multiple
    label="Tags"
    name="tags"
    placeholder="Select tags…"
  >
    <datalist>
      <option
        value="design"
        selected
      >
        Design
      </option>
      <option value="dev">Development</option>
      <option value="qa">QA</option>
      <option value="ops">Ops</option>
    </datalist>
  </l-select>
</div>

Rich options

The same .l-select-item-* classes as the native tier work inside each <option>. Set a label (or .l-select-item-title) so filtering and the trigger display use the title.

Code
html
<div class="w-72">
  <l-select
    label="Country"
    placeholder="Select a country…"
  >
    <datalist>
      <option
        value="us"
        label="United States"
      >
        <span class="l-select-item-media"><l-icon name="circle-flags:us"></l-icon></span>
        <span class="l-select-item-text">
          <span class="l-select-item-title">United States</span>
          <span class="l-select-item-description">North America</span>
        </span>
      </option>
      <option
        value="fr"
        label="France"
      >
        <span class="l-select-item-media"><l-icon name="circle-flags:fr"></l-icon></span>
        <span class="l-select-item-text">
          <span class="l-select-item-title">France</span>
          <span class="l-select-item-description">Europe</span>
        </span>
      </option>
      <option
        value="jp"
        label="Japan"
      >
        <span class="l-select-item-media"><l-icon name="circle-flags:jp"></l-icon></span>
        <span class="l-select-item-text">
          <span class="l-select-item-title">Japan</span>
          <span class="l-select-item-description">Asia</span>
        </span>
      </option>
    </datalist>
  </l-select>
</div>

Clearable

Add with-clear for a button that resets the value.

Custom filter

Options are matched case- and accent-insensitively (every space-separated keyword must appear). Override the filter property — (item, query) => boolean — for startsWith, fuzzy, or remote-driven filtering.

js
const select = document.querySelector('l-select');

// Match from the start of the label instead of anywhere in it.
select.filter = (item, query) => item.label.toLowerCase().startsWith(query.toLowerCase());

Accessibility

The native tier inherits the platform's <select> semantics — pair it with a <label>. The criteria below cover the <l-select> custom element.

Criteria

Role

The trigger exposes aria-haspopup="listbox" / aria-expanded / aria-controls; options are role="option" in a role="listbox" (with aria-multiselectable when multiple)

WCAG4.1.2
RGAA11.1
Accessible name

Set label (or wrap with <l-form-field>)

WCAG1.3.1
RGAA11.1
Focus management

Opening moves focus into the search box (or the listbox when not searchable); closing returns it to the trigger

WCAG2.4.3
RGAA12.8
Target size

The trigger and chip remove buttons keep a minimum 24×24px hit target

WCAG2.5.8
RGAA13.10

Keyboard interactions

Enter / Space / ArrowDown
Opens the listbox (focus moves to the search box when searchable, else the list)
ArrowDown / ArrowUp
Moves the active option
Enter
Selects the active option (single) or toggles it (multiple)
Escape
Closes the listbox and returns focus to the trigger
Backspace / Delete
Removes a focused chip (multiple)

API reference

The native tier ships its styles via luxen-ui/css/select; the reference below is for the <l-select> custom element.

Importing

js
import 'luxen-ui/select';
css
@import 'luxen-ui/css/select';

Attributes & Properties

placeholderstringProperty
Placeholder shown in the trigger when nothing is selected.
labelstringProperty
Accessible label for the trigger.
sizeSelectSizedefault:'md'Property
Control size.
searchablebooleandefault:falseProperty
Show a filter box inside the popover (opt-in, for long lists).
with-clearbooleandefault:falseProperty
Show a button to clear the value.
placementPlacementdefault:'bottom-start'Property
Panel placement relative to the trigger.
multiplebooleandefault:falseProperty
Allow selecting multiple values — renders chips and submits one entry per value.
filterSelectFilterdefault:defaultFilterProperty
Override the option filter. (item, query) => boolean.
valuestring | string[]Property
The selected value (single mode) or array of values (multiple mode).
validationTargetProperty

Events

changeEvent
Fired when the selection changes. Bubbles. Not cancelable. Properties: value: string | string[].
inputEvent
Fired as the user types in the search box. Bubbles. Not cancelable. Properties: value: string (the query).
showcancelableEvent
Fired before the listbox opens. Cancelable.
hidecancelableEvent
Fired before the listbox closes. Cancelable.

CSS parts

basePart
The host wrapper.
triggerPart
The button that opens the listbox.
valuePart
The selection display inside the trigger.
chevronPart
The trigger chevron.
clearPart
The clear button.
panelPart
The floating popover panel.
searchPart
The search input.
listboxPart
The options container.
optionPart
Each option row.
emptyPart
The "no results" message.

CSS custom properties

--heightCustom property
Control height. Defaults to the form-control height.
--border-radiusCustom property
Trigger + panel radius.
--backgroundCustom property
Panel background.