- Docs
- Select
Select
Build a DOM-first select with searchable panels, rich selected content, and multi-select summaries.
@flexilla/select gives you DOM bindings for the headless @flexilla/select-core engine. Use it when you want:
- a visible option list controlled by a trigger button
- single or multiple selection
- optional search inside the panel
- rich selected content in the trigger or multi tags
If you only want the state engine without DOM wiring, use Select Core.
Installation
Markup structure
Select does not need a wrapper. The trigger, content, and value elements only need to share the same
data-select-id. <button
data-fx-select
data-select-trigger
data-select-id="framework-select"
>
<span data-selected-value data-select-id="framework-select">
Choose a framework
</span>
</button>
<div
data-select-content
data-select-id="framework-select"
data-placement="bottom-start"
class="ui-popper"
hidden
>
<button data-select-item="astro">Astro</button>
<button data-select-item="vue">Vue</button>
<button data-select-item="laravel">Laravel</button>
</div>
<button data-select-clear data-select-id="framework-select">Clear</button> DOM attributes
| Attribute | Role |
|---|---|
data-select-trigger | Button that opens/closes the panel |
data-select-item | Selectable option (value goes in the attribute) |
data-selected-value | Container that renders the current selection |
data-select-input | Search input inside the panel (optional) |
data-select-clear | Resets all selected values |
data-select-clear-all | Same as data-select-clear |
data-select-remove | Removes a specific value (data-select-remove="value") |
data-fx-select | Auto-init selector (placed on the trigger) |
Overlay attributes
Put these on the content element to control positioning:
| Attribute | Default | Description |
|---|---|---|
data-placement | bottom-start | Popper placement |
data-offset-distance | 6 | Gap between trigger and panel |
data-prevent-close-outside | false | Keep open when clicking outside |
data-prevent-close-inside | true (with search or multiple) | Keep open when clicking inside |
data-readjust-height | true | Recalculate position on scroll |
data-min-height | 140 | Minimum panel height |
The panel also receives
--trigger-width, so you can match the trigger width: [data-select-content] {
width: var(--trigger-width);
} Basic usage
Class API
import { Select } from "@flexilla/select";
new Select('[data-select-content][data-select-id="framework-select"]'); You can also pass the element directly:
const el = document.querySelector('[data-select-content][data-select-id="framework-select"]');
new Select(el); The constructor expects a CSS selector or
HTMLElement. It reads the data-select-id from that element and resolves the rest of the select automatically. Auto init
import { Select } from "@flexilla/select";
Select.autoInit(); Looks for
[data-fx-select] elements and initializes them. Controller API
import { createSelect } from "@flexilla/select";
const select = createSelect({ multiple: true });
const { destroy } = select.connect({
element: '[data-select-content][data-select-id="framework-select"]',
}); Search
Add
data-select-input inside the content to make the list searchable. <div data-select-content data-select-id="owner-select" hidden>
<input
data-select-input
data-select-id="owner-select"
type="text"
placeholder="Search team members"
/>
<button data-select-item="alice" data-label="Alice Johnson">Alice Johnson</button>
<button data-select-item="marc" data-label="Marc Dupont">Marc Dupont</button>
<template data-select-empty>
<div>
No team member found for "<span data-select-empty-query></span>"
</div>
</template>
</div> When a search input is present:
- clicks inside the panel stay open so the input remains usable
- choosing an item in single mode still closes the panel
- use
data-select-emptyto show a message when nothing matches
Custom filter:
createSelect({
filter: (query, item) => {
return item.label?.toLowerCase().includes(query.toLowerCase()) ?? false;
},
}); Search debounce (default 100ms):
createSelect({
searchDebounce: 250, // ms
}); Set to
0 for immediate filtering on every keystroke. Selected value templates
data-selected-value can define a reusable template with template[data-selected-model]. Template bindings:
| Attribute | Description |
|---|---|
data-bind | Injects text from item data |
data-bind-src | Sets src attribute |
data-bind-alt | Sets alt attribute |
data-bind-title | Sets title attribute |
data-bind-href | Sets href attribute |
data-bind-style | Sets inline styles from item data |
data-select-label | Injects the item label |
data-select-value | Injects the raw value |
data-select-remove | Adds a remove button (multiple mode) |
<div data-selected-value data-select-id="reviewers">
<template data-selected-model>
<span class="inline-flex items-center gap-2">
<span
class="size-5 rounded-full"
data-bind-style="badgeStyle"
data-bind="initials"
></span>
<span data-bind="label"></span>
<button data-select-remove>×</button>
</span>
</template>
<span data-placeholder>No reviewer selected</span>
</div>
<li
data-select-item="alice"
data-label="Alice Johnson"
data-initials="AJ"
data-badge-style="background-color:#fde68a;color:#78350f"
>
...
</li> Multiple selection
Enable with the
multiple option: new Select('[data-select-content][data-select-id="reviewers"]', { multiple: true }); Or in markup:
<button data-fx-select data-multiple data-select-trigger data-select-id="reviewers">
Assign reviewers
</button> In multiple mode:
- clicking items toggles them on and off
- the panel stays open after selection
- removing a selected item keeps the panel open
Multi-select summaries
Instead of rendering full templates,
data-selected-value can show a summary. Modes:
chips, count, compact. <span
data-selected-value
data-select-id="reviewers"
data-select-summary-mode="count"
data-select-summary-count-singular="{count} user selected"
data-select-summary-count-plural="{count} users selected"
>
<span data-placeholder>No user selected</span>
</span> Compact mode limits visible labels:
<span
data-selected-value
data-select-id="reviewers"
data-select-summary-mode="compact"
data-select-summary-limit="2"
data-select-summary-compact-text="{labels} and {remaining} other users"
>
<span data-placeholder>No reviewer selected</span>
</span> Empty state
Rendered when search returns no visible items.
<template data-select-empty>
<div>
No results found for "<span data-select-empty-query></span>"
</div>
</template> State and methods
The controller exposes select-core methods directly:
open() close() toggle() select(value) unselect(value) clear() toggleValue(value) highlight(index) highlightNext() highlightPrev() setSearch(query) getState() subscribe(listener) const select = createSelect();
select.connect({ element: '...' });
select.subscribe((state) => {
console.log(state.selectedValues);
}); Lifecycle
Class API:
const select = new Select('...');
select.cleanup(); Controller API:
const { destroy } = select.connect({ element: '...' });
destroy(); Select vs Autocomplete
- Use Select when interaction starts from a trigger button and a visible option list.
- Use Autocomplete when interaction starts from typed input.
- Use Select Core when you want the state engine without DOM bindings.