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.
<select><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
<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
<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.
<l-select>Searchable
Add searchable for a filter box inside the popover — useful for long lists. Matching is accent/case-insensitive.
Code
<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
<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
<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.
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 arerole="option"in arole="listbox"(witharia-multiselectablewhenmultiple)WCAG4.1.2RGAA11.1
Keyboard interactions
API reference
The native tier ships its styles via luxen-ui/css/select; the reference below is for the <l-select> custom element.
Importing
import 'luxen-ui/select';@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.