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: 14.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 headers. Custom header content: 11.3.

10.1 Basic grouping (single)

Foods grouped by category. Open it - each category shows a header 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 14.

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 header

createGroupLabelContentElFn fills the header (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. 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.

13.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

13.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

13.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

14. 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()).

14.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)

14.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)