Show data.js

llselect demo - examples

Run npm run build, then npm run serve, and open /demo/.

1. Basic usage

1.1 Strings

Simple string-typed options.

chosen: (none)
code

1.2 Objects (custom template + compareFn)

Generic T = User object. itemToStringFn setting maps it to its display string (no subclass).

chosen: (none)
code

1.3 Clearable (x button)

clearable: true adds an x in its own trigger slot. Click it: value clears to undefined, onChange fires. Custom icon: createTriggerClearButtonContentElFn.

chosen: (none)
code

2. Positioning

Listbox uses position: fixed; tracks the trigger on scroll/resize. Flips up when there is not enough room below.

2.1 Inside a scrollable container

Open the dropdown, then scroll the gray box in either direction. Scrolling clips the trigger above or below the container edge - the popup should auto-close in both cases.

chosen: (none)
code

2.2 Transformed container (top layer)

The amber box's transform would normally displace a position: fixed popup. With the Popover API (all current browsers) the popup renders in the top layer instead - open it: it must align under the trigger. Old browsers show the documented displacement.

chosen: (none)
code
demo CSS (style.css)

3. Outside-click behavior

Open the dropdown, then click the outside button. The two modes differ in whether the outside click also triggers its own action.

3.1 pass-through (default)

Outside click closes the dropdown AND triggers the button. Count below should increment.

code

3.2 block

Outside click closes the dropdown only; the button does NOT receive the click. Count below should stay 0.

code

4. Arrow variants

Library ships no arrow by default. Pass createTriggerArrowContentElFn to add any element (SVG, <i> icon class, plain text, ...). All variants below use the same data binding; only the arrow differs.

4.1 No arrow (default)

code

4.2 Built-in SVG helpers

Both return SVGElement with fill="currentColor" and aria-hidden="true". CSS rotates them on [data-state="open"].

createChevronDownSvgEl()
createTriangleDownSvgEl()
code

4.3 Material Design Icons (mdi)

Just return an <i class="mdi mdi-...">. No lib API needed.

code

4.4 CSS-only triangle (no JS arrow)

Pure CSS pseudo-element on .llselect-trigger. data-state drives rotation.

code
demo CSS (style.css)

4.5 Arrow + clear together

Arrow and clear x live in separate trigger slots, so they compose - and both survive a custom createTriggerContentElFn.

code

5. Multiple selection (LLSelectMultiple)

Click items to toggle; the popup stays open. Items carry aria-selected, the list aria-multiselectable.

5.1 Basic (count summary)

Default trigger content - "n / m selected" / "All n selected" / placeholder.

chosen: []
code

5.2 chooseAll / unchooseAll API

External buttons call lib methods.

chosen: []
code

5.3 Tags display

triggerDisplay: 'tags' - one removable chip per chosen item; the library owns the chip, its x button and aria-label. Custom chip content / x icon: see 11.4.

chosen:
code

5.4 Checkbox items (createItemContentElFn)

createItemContentElFn renders checkbox + item text; the public isChosen() supplies the state (the fn runs per render, so the self-reference in the code is safe); the single-item rerender keeps it in sync. Subclass equivalent: 13.1.

chosen: []
code

5.5 Choose-all row (tri-state)

chooseAllRow: true pins a tri-state row acting on the VISIBLE enabled subset: type a filter, toggle the row - only the matches change. createChooseAllRowContentElFn swaps the default plain-text row (5.7) for the outlined SVG; items reuse 5.4's checkbox.

chosen: 0 items
code

5.6 Filled (Material) checkboxes

5.5 drawn with createFilledCheckboxSvgEl (tick / dash cut out of a solid box); same settings checkbox as 5.4. Choose a few items - the choose-all row shows the filled indeterminate state.

chosen: 0 items
code

5.7 Choose-all default look

No content fn: the row is just the counting text, and its numbers ARE the tri-state. The library draws no default indicator anywhere; 5.5 / 5.6 add SVG checkboxes via the hook.

code

5.8 Hide chosen rows (hideChosenRows)

A chosen item's row leaves the popup; removing its tag puts it back, and the clear button puts everything back at once. Natural with tags: the trigger shows what is chosen, the popup what is still choosable. Once everything is chosen the popup shows the no-results text.

chosen: []
code

6. Width policy: trigger and popup

Trigger width is your CSS's job; chosen text in it ellipsizes. The popup defaults to popupWidthPolicy: 'fit-content' (grows to its content, never narrower than the trigger, viewport-clamped - native select behavior); 'match-trigger' pins it to the trigger width.

6.1 No CSS width (baseline)

Demo CSS: none. Pick the long sentence - the trigger grows to fit and overflows the pane: the library constrains nothing; tidy layout is the caller's CSS job.

chosen: (none)
code

6.2 Default 'fit-content': popup outgrows the trigger

Demo CSS constrains the mount to max-width: 18rem: the trigger ellipsizes, but the default popup still opens at the widest item text's width (viewport-clamped).

chosen: (none)
code
demo CSS (style.css)

6.3 'match-trigger': item text wraps

Same 18rem constraint, opt-in 'match-trigger': popup pinned to the trigger width, long item text wraps.

chosen: (none)
code
demo CSS (style.css)

6.4 Label ellipsis + title tooltip (subclass)

Demo CSS adds text-overflow: ellipsis on the items (needs 'match-trigger' - a fit-content popup never truncates); a createItemEl subclass sets el.title. The subclass IS required for this exact effect: no setting reaches the option element itself, so a content fn could only tooltip the inner text, not the whole row. (The library never auto-adds title - bring your own tooltip lib, or this.)

chosen: (none)
code
demo CSS (style.css)

7. Search (filter)

filterable: true adds a filter input inside the popup. Esc clears the filter, then closes; IME composition is respected.

7.1 Single + filterable (default filter)

Default filterFn is case-insensitive substring on the item's itemToStringFn string.

chosen: (none)
code

7.2 Multi + filterable + checkboxes

Filtering does not change the chosen set: a chosen item hidden by the current query stays chosen and reappears when cleared. Checkboxes: 5.4's settings pattern.

chosen: []
code

7.3 Custom filterFn (match name OR role)

A custom filterFn matches name OR role - the default substring only sees the rendered text.

chosen: (none)
code

7.4 Highlight the matches (createHighlightedTextEl)

The exported helper wraps each query match in <mark>. createItemContentElFn re-runs on every keystroke, so the marks follow the query; the option's aria-label stays the plain text.

chosen: (none)
code

8. Performance (10,000 items)

10,000 rows each. Lazy render builds the option DOM on open and clears it on close.

8.1 Single-select

Open and close must stay instant.

chosen: (none)
code

8.2 Multi-select

Toggling re-renders only that item's element (O(1) DOM), not the 10k list - click items rapidly.

chosen: 0 items
code

9. Disabled

Always aria-disabled, never the native disabled attribute - disabled things stay hoverable, so a tooltip can explain WHY.

9.1 itemDisabledFn + why-tooltip (createItemEl subclass)

Out-of-stock drinks: unselectable, skipped by arrows, still hoverable - hover one for the tooltip a createItemEl subclass attached. The subclass IS required here: the title sits on the option element itself (whole-row hover), and isItemEffectivelyDisabled() is protected - neither is reachable from settings.

chosen: (none)
code

9.2 setDisabled + focusableWhenDisabled

First control: setDisabled() removes it from the tab order (default). Second: focusableWhenDisabled: true keeps it Tab-reachable, so keyboard users get the tooltip too.

chosen: (none)
^ disabled + focusableWhenDisabled: true (press Tab to reach it)
code

9.3 Disabled blocks the tag and clear buttons too

A disabled control must not change value through ANY of its buttons. Toggle the disable and try the tag x buttons and the clear x: while disabled they do nothing; re-enabled they work again.

chosen: Apple, Banana
code

10. Optgroup / grouping

itemToGroupKeyFn maps each item to a group key (contiguous same-key items form one group - pre-sort your data); groupKeyToStringFn is the display / i18n customization point. Keyboard nav skips labels. Custom label content: 11.3.

10.1 Basic grouping (single)

Foods grouped by category. Open it - each category shows a label above its items.

chosen: (none)
code

10.2 Group disabled + filterable

groupDisabledFn disables the whole Dairy group (still hoverable). Type to filter - survivors regroup, empty groups disappear.

chosen:
code

11. Custom renderers (create*ContentElFn settings)

The no-subclass half of the customization model: each create*ContentElFn setting fills an element's VISIBLE content only; the element itself and its ARIA stay library-built (an option's aria-label stays the plain itemToString). When the library-built element itself must change, subclass instead: section 13.

11.1 Single icon rows

Icon + text rows via createItemContentElFn. The trigger mirrors the chosen row only because the same renderer feeds createTriggerContentElFn - there is no auto-projection.

chosen: (none)
code

11.2 Multiple icon rows

Same renderer on a multiple; the option element keeps aria-selected plus the auto aria-label.

chosen: (none)
code

11.3 Rich group label

createGroupLabelContentElFn fills the label (icon + item count); the accessible name stays the plain groupKeyToString.

code

11.4 Rich tag chips

11.1's renderer feeds createTagContentElFn too - ONE function serves rows AND chips. createTagRemoveButtonContentElFn swaps the x icon; unset, the theme's CSS glyph draws it.

chosen: TypeScript, Rust
code

11.5 Faded hint text

Primary text + a faded .hint pushed to the row's right edge (.user-row); the name highlights the filter match (createHighlightedTextEl). Visual only: the option's aria-label stays the plain itemToString name.

chosen: (none)
code

11.6 Per-item background tint

What <option style="background-color"> does on a native select (Chromium only): every row tinted from its icon color, and the chosen item's colors cover the whole trigger (onChange paints the public triggerEl). The tint is translucent, so hover / keyboard-focus stay visible. The checkmark is createCheckmarkSvgEl; rows re-render on chosen changes, so it never goes stale. All inline styles, plus one demo CSS line zeroing the option element's padding.

chosen: (none)
code
demo CSS (style.css)

12. Label integration (labelEl)

Native <label for> cannot target a div, so the labelEl setting emulates both halves: it feeds the accessible name (via aria-labelledby) and clicking it focuses the trigger - focus only, like a native select label.

^ click the label above: the trigger receives focus; inspect it to see aria-labelledby referencing the label
code

13. Subclassing

Settings customize CONTENT; subclassing customizes the library-built elements (e.g. the role="option" element itself) and can read protected state. Performance is identical (one shared prototype method vs one closure per instance is noise) - pick by capability. The required-subclass cases live with their features: 6.4 (title on the whole row), 9.1 (protected isItemEffectivelyDisabled()).

13.1 Checkbox items, subclass-style (= 5.4)

The same result as 5.4 via a createItemEl override - not required, kept as the reference example of the subclass surface.

chosen: []
code
demo CSS (style.css)

13.2 Tree multiple select: a whole variant in TypeScript

Branch rows expand / collapse via the caret, wear a folder icon that follows the state, and show a tri-state checkbox derived from their leaves; activating a branch toggles its whole subtree. The model value is the chosen leaves. Built on the extension points: typed subclass settings (defaultExpandDepth), a cached getVisibleItems override, createItemEl / createItemContentEl, onItemActivated. The toggle shows the usage, the TypeScript subclass (subclass/tree-select.ts), and its demo CSS.

chosen: []
code (TypeScript)
usage (main.js)
the subclass (subclass/tree-select.ts)
demo CSS (style.css)

14. Popup header / footer (pinned slots)

Content that stays put while the list scrolls. Built once, in the constructor; the returned node is yours to update. No ARIA role, and never part of the listbox.

14.1 Tags in the trigger, the count and a Clear all button in the header

The trigger shows chips, so the header takes over the count with triggerCountSummary. The match count is rewritten from onFilterQueryChange. The Clear all button is a slot control. A click keeps keyboard input on the combobox. Tab reaches it while the popup is open.

code
demo CSS (style.css)

14.2 Column headings above two-column rows

Rows show a name and a role; the header repeats the same layout in bold. It sits between the filter input and the list, so the headings stay while the rows scroll.

chosen: (none)
code

14.3 A footer link to a management page

A pinned link under the list. A click keeps keyboard input on the combobox; Tab reaches the link while the popup is open, and Esc on it returns to the trigger.

code

14.4 Select all and Restore defaults pinned in the header, for comparison with 15.1

The same two commands as 15.1 with the same row look, only pinned: they never scroll away. Each is a real button wearing the theme's item class. Tab reaches them, but they sit outside the arrow-key ring and are not announced as options; the rows in 15.1 are the keyboard-native form.

chosen: []
code
demo CSS (style.css)

15. Action rows: commands inside the list

Rows the library renders as options above or below the items, so a keyboard user reaches them with the arrow keys. They scroll with the list. Each row is a plain object: its text, whether it is disabled, and what activation does. The library never chooses or closes for it.

15.1 Restore defaults, above the items

The row reads the live selection: textFn and disabledFn run again after every change. It follows the choose-all row, so ArrowDown reaches it before any item.

chosen: []
code

15.2 Add a country, above the items

A single select with one row above the items. Activating it asks for a name, adds it, picks it and closes; the row itself never closes the popup.

chosen: (none)
code

15.3 Sort the list, above the items

The row flips the order with setItems and shows the current one. A filter query re-applies to the new order, and the chosen item stays chosen.

chosen: (none)
code

15.4 Select all and Restore defaults pinned at the top of the list

The rows of 15.1 with popupListLeadingRowsPinned: true: the choose-all row and the command stay in view while the items scroll under them, and keep their place in the arrow-key ring.

chosen: []
code

16. i18n (language packs) + RTL

All chrome strings live in the uiTranslationPack setting; packs from @llselect/core/i18n fill it whole, per-key overrides via spread. An explicit placeholder setting wins over the pack's default.

16.1 Pack switcher (incl. RTL)

Switch the pack - setUiTranslationPack re-renders in place, chosen values survive. Placeholders, filter input, clear x, tag remove buttons and the count summary all translate. ar / he also set dir="rtl" on the mounts (direction is inherited - there is no RTL setting): arrows and x buttons mirror LEFT, chips flow right-to-left.

^ LLSelectSingle - localized placeholder, chosen text in the trigger
^ multi, triggerDisplay: 'tags' - translated remove x per chip
^ multi, default 'count' - translated count summary
chosen:
code

16.2 RTL + checkbox rows (auto-mirroring)

dir="rtl" on the mount is ALL the wiring - there is no RTL API. Flex rows and logical margins mirror by themselves: the checkboxes land on the right, the ar pack's counting text reads right-to-left.

chosen: 0 items
code

16.3 Every language pack

One instance per entry in uiTranslationPackByLocale - the grid grows with the library. Each trigger shows its pack's own placeholder; RTL packs set dir="rtl".

code