MultiSelect
Immutable multiple selection with chips, grouping, filtering, typed templates, every Angular Forms adapter and an SSR-safe virtualized popup.
GET STARTED
Import
Use the canonical NeuralMultiSelect export from its granular entry point.
Grouped chips and filtering
Map nested data without mutation, group related results, keep unavailable options visible and customize each row through a typed template.
Display modes and selection limits
comma mode collapses long values into a localized count while selectionLimit protects domain rules. Chip mode remains the default.
At most four values · 3 currently selected.
Angular Forms
Direct Signals, Signal Forms, Reactive Forms and template-driven Forms share one immutable readonly TValue[] model. Programmatic writes never masquerade as user events.
Remote filtering and request identity
Remote mode never filters the supplied rows. Each debounced query carries a monotonic request id so stale network responses can be ignored deterministically.
Typed composition templates
Header, group, option, selected value, footer, empty and loading surfaces remain typed and independently replaceable.
States and localized feedback
Disabled, readonly, loading, empty and invalid states keep distinct semantics. Empty and loading templates can replace localized defaults.
Virtual scrolling
Fixed-height lists render only the visible overscanned range while preserving full set position metadata. Grouped lists safely fall back to complete rendering.
Semantic events
valueChange owns model synchronization. Selection, removal, clear, select-all, filter and lifecycle events describe user intent with immutable snapshots and no on-prefixed aliases.
Unstyled and typed class slots
Headless mode retains immutable state, Forms, keyboard behavior, ARIA and overlay positioning while Tailwind owns every visible surface.
ACCESSIBILITY
Multiple selection without ambiguity
The trigger, focused filter and popup share one deterministic multiple-listbox contract.
Combobox and listbox semantics
The trigger and focused filter expose expanded state, controls and active descendant. The popup uses role=listbox with aria-multiselectable=true; every row exposes selection, disabled state, position and total set size.
Keyboard model
Arrow Up/Down move through enabled options, Home/End jump to boundaries, Enter/Space toggle the active option and Escape closes. When filtering is disabled, buffered typeahead moves by localized labels.
Readonly and disabled
Readonly stays focusable and opens for inspection but blocks options, chips, clear and select-all. Disabled leaves the tab sequence and closes an open popup. Required, invalid, pending, touched and dirty states remain inspectable through ARIA and data hooks.
Virtualized accessibility
Only a small window exists in the DOM, but aria-posinset and aria-setsize describe each option inside the complete collection. Active navigation scrolls the target row into view.
PUBLIC CONTRACT
MultiSelect API
Inputs and models
| Name | Type | Default | Purpose |
|---|---|---|---|
| options | readonly TOption[] | [] | Immutable source options. |
| optionLabel | string | 'label' | Visible and accessible label path. |
| optionValue | string | 'value' | Model value path. |
| optionDisabled | string | 'disabled' | Disabled-state path. |
| optionGroup | string | '' | Optional group label path. |
| value | readonly TValue[] | [] | Two-way immutable Signal model. |
| filterValue | string | '' | Two-way filter query model. |
| display | 'chip' | 'comma' | 'chip' | Selected-value presentation. |
| dataMode | 'local' | 'remote' | 'local' | Filtering ownership. |
| filterMode | 'contains' | 'startsWith' | 'endsWith' | 'contains' | Local matching strategy. |
| filterBy / filterLocale | string | '' | Nested paths and matching locale. |
| filter | boolean | true | Shows the filter input. |
| showToggleAll | boolean | true | Shows visible-option select all. |
| clearable | boolean | true | Shows the clear action. |
| closeOnSelect | boolean | false | Closes after each toggle. |
| selectionLimit | number | 0 | Maximum selections; zero is unlimited. |
| maxSelectedLabels | number | 3 | Comma labels before count summary. |
| filterDelay | number | 150 | Debounced filter event delay in ms. |
| virtualScroll | boolean | false | Enables fixed-height windowing. |
| virtualItemSize | number | 42 | Option row height in pixels. |
| virtualScrollHeight | number | 256 | List viewport height in pixels. |
| virtualOverscan | number | 3 | Extra rows around viewport. |
| loading | boolean | false | Shows localized busy content. |
| disabled / readonly | boolean | false | Interaction policies. |
| required / invalid | boolean | false | ARIA and validation states. |
| pending / touched / dirty | boolean | false | Forms state hooks. |
| fluid | boolean | false | Fills available inline size. |
| placeholder / filterPlaceholder | string | locale | Trigger and filter labels. |
| emptyLabel / loadingLabel | string | locale | Status labels. |
| selectedItemsLabel | string | locale | Collapsed count pattern. |
| selectAllLabel / clearLabel | string | locale | Action labels. |
| dropdownLabel / removeLabel | string | locale | Accessible action patterns. |
| ariaLabel | string | '' | Combobox and listbox name. |
| multiSelectId / name | string | '' | Control identity and Forms name. |
| dropdownIconClass | string | 'nt-chevron-down' | Dropdown glyph. |
| clearIconClass / removeIconClass | string | 'nt-x' | Removal glyphs. |
| searchIconClass / checkIconClass | string | Neural Icons | Filter and selection glyphs. |
| loadingIconClass | string | 'nt-loader-3 nt-spin' | Busy glyph. |
| compareWith | function | Object.is | Custom value equality. |
| unstyled | boolean | false | Removes visual classes. |
| multiSelectClass | string | '' | Additive root class. |
| classes | NeuralMultiSelectClasses | {} | Typed slot classes. |
Outputs
| valueChange | readonly TValue[] | Generated by the value model. |
| filterValueChange | string | Generated by the filter model. |
| selectionChange | NeuralMultiSelectChange | Current/previous arrays, option and source. |
| selected / removed | NeuralMultiSelectItemEvent | One option was added or removed. |
| cleared | NeuralMultiSelectClearEvent | Explicit clear with previous values. |
| selectAllChange | NeuralMultiSelectSelectAllEvent | Visible enabled options changed together. |
| filterChange | NeuralMultiSelectFilterEvent | Debounced query and monotonic requestId. |
| opened / closed | void | Popover lifecycle. |
| touch | void | Completed interaction. |
Public methods
focus(options?: FocusOptions)Focuses the combobox trigger.
reset()Closes, clears filter and restores an empty array.
openPanel / closePanel()Controls popup visibility.
togglePanel(event?: Event)Toggles when enabled.
clear(event?: Event)Clears mutable selections.
toggleAll(event?: Event)Toggles visible enabled options.
Typed templates
neuralMultiSelectOptionContext: option, resolved, label, value, index, active, selected, disabled
neuralMultiSelectValueContext: value, labels
neuralMultiSelectGroupContext: group
neuralMultiSelectHeaderContext: No context
neuralMultiSelectFooterContext: No context
neuralMultiSelectEmptyContext: $implicit localized empty label
neuralMultiSelectLoadingContext: $implicit localized loading label
Typed class slots
roottriggervalueplaceholderchipListchipchipLabelchipRemoveclearButtondropdownButtoniconpanelheaderfilterselectAlllistgroupoptionoptionLabelcheckboxactiveOptionselectedOptiondisabledOptionemptyMessageloadingMessagefooterDESIGN TOKENS
MultiSelect tokens
Neutral tokens inherit the global primary, surface and resolved light/dark system. Consumer classes remain additive.
--neural-multi-select-widthRoot inline size.
--neural-multi-select-backgroundTrigger surface.
--neural-multi-select-borderTrigger border shorthand.
--neural-multi-select-radiusTrigger corner radius.
--neural-multi-select-colorTrigger foreground.
--neural-multi-select-panel-backgroundPopup surface.
--neural-multi-select-panel-max-heightNon-virtual list limit.