Level: molecule.
Purpose
wbl-multiselect lets a user choose several values from a supplied list and
shows that controlled selection in one field. Use it for a reusable field
outside Filter Row. It filters the supplied local list as the user types. Do
not use it for free-form option creation, asynchronous search, or a
Filter-Row-specific draft-and-apply selection; those are separate boundaries.
Anatomy
- Optional label, required marker, placeholder, description, clear action, and opening chevron.
- A combobox field trigger that becomes an in-field filter on opening, plus an attached checkbox listbox.
Textpresentation renders the selected labels as a summary.Tagpresentation renders each selected label as a closable compact Tag.
Variants and visual states
variant:textortag.size:L(56px),M(44px), orS(32px) when empty. Text keeps those field heights when it is a one-line summary.state: default, active while its listbox is open, and disabled.- Switching Text
valueLayoutchanges only the value overflow/wrapping rule. It must preserve the truncate layout's top and bottom content padding, label position, and one-line field height; wrap may add height only after a value actually occupies an additional line. - A filled Tag field wraps selected Tags with a 4px gap. The verified two-line
heights are 80px (
L), 68px (M), and 56px (S); additional wrapping must grow the field rather than clipping selected values. - The listbox stays 4px from the field in every size and placement. Clear uses
cross-S; its rendered icon size matches the chevron for its field size.
Text summary
- With
valueLayout='truncate', render one line and show no more thanmaxVisibleItemslabels followed by, ещё Nfor the remaining values. WhenmaxVisibleItems=null, all labels participate in the one-line summary and the suffix is hidden. - With
valueLayout='wrap', render all selected labels in a wrapping summary, hide, ещё N, and grow the field with the summary while retaining the same visible top and bottom insets as the fixed summary. With one rendered value line,wraphas exactly the same label position and field geometry astruncate; additional height comes only from a new value line. - The component counts hidden values from
maxVisibleItems; layout width is never used to infer the suffix count.
Tag summary
- Each selected value is a
wbl-tagwithsize="S",appearance="selected", andclosable=trueunless the whole Multiselect is disabled. - When the Tag field is open, its in-field filter follows the selected Tags in the same wrapping flow. It takes the remaining row space or moves to the next row; it never replaces or overlays a selected Tag.
- The Tag in-field filter and its placeholder use
--typography-description-pumain every size. - Closing one Tag removes only that value. Clear removes every selected value.
Behavior
itemsandselectedIdsare controlled inputs.selectedIdsis the source of truth; the component never retains an uncommitted selection.- Selecting or deselecting an option immediately emits the next immutable
selectedIdsarray. There is no Apply action or draft state. - The trigger toggles the listbox. Clear and individual Tag removal emit their changes immediately without introducing a draft state or closing the listbox.
- Opening the field focuses an in-field filter. It matches
itemsby a case-insensitive substring oflabel; filtering is local, resets when the listbox closes, and never altersselectedIdson its own. - Disabled Multiselects neither open nor emit selection, clear, or removal changes. Disabled options remain visible but cannot be toggled.
- Async loading, remote search, option creation, and Filter Row behavior are intentionally out of scope.
Accessibility
- The closed trigger has
role="combobox",aria-haspopup="listbox",aria-expanded, andaria-controlsbound to the overlay listbox. On open, the native filter input owns the combobox semantics and receives focus. - The overlay uses listbox semantics,
aria-multiselectable="true", and option semantics witharia-selected; it does not use menu semantics. - Enter, Space, and ArrowDown open the listbox. In the listbox, ArrowUp and ArrowDown move the active option; Space toggles it; Escape closes and returns focus to the trigger. In the filter input, ArrowDown moves to the first visible option and Escape closes the listbox. Disabled options are skipped by keyboard navigation.
- Clear and every visible Tag close control have a meaningful accessible name.
Figma source
- Text:
17867:14645. - _FieldValue:
17867:15072. - Tag:
21874:11775.
The Text Figma master exposes Value layout: Fixed / truncate maps to the
Angular valueLayout='truncate', while Growing / wrap maps to
valueLayout='wrap'. _FieldValue remains private Figma assembly.
The local in-field filter is runtime behavior; it has no separate published Figma variant.
Reusable Figma assembly is recorded in the Multiselect sidecar.
Local component contract
Classes and selectors
WblMultiselectComponent—wbl-multiselect(src/app/design-system/patterns/multiselect/multiselect.component.ts).
Public types
WblMultiselectVariant = 'text' | 'tag'.WblMultiselectSize = 'L' | 'M' | 'S'.WblMultiselectValueLayout = 'truncate' | 'wrap'.WblMultiselectItem = { id: string; label: string; disabled?: boolean }.
Inputs
size = input<WblMultiselectSize>('L')variant = input<WblMultiselectVariant>('text')items = input<readonly WblMultiselectItem[]>([])selectedIds = input<readonly string[]>([])opened = input(false)disabled = input(false)— keeps the field closed and non-interactive: it cannot open the dropdown, render Tag close controls, or emit selection, clear, or Tag-removal changes.label = input('Label')required = input(false)placeholder = input('Placeholder')description = input('')clearable = input(true)valueLayout = input<WblMultiselectValueLayout>('truncate')— Text only.maxVisibleItems = input<number | null>(2)— Text only;nullkeeps all labels in the one-line summary.ariaLabel = input<string | null>(null)
The local filter is enabled for both variants. It is intentionally internal:
the component filters the supplied items by label and has no query input or
remote-search API.
Outputs
selectedIdsChange = output<string[]>()openedChange = output<boolean>()
Slots and composition
- The component has no content-projection slot.
- It composes
wbl-dropdowninsemanticRole="listbox"and checkbox mode for its overlay. It useswbl-tagonly for the Tag presentation.
Local evidence
- Code:
src/app/design-system/patterns/multiselect/multiselect.component.ts. - Focused test:
src/app/design-system/patterns/multiselect/multiselect.component.spec.ts. - Public export:
export * from './patterns/multiselect/multiselect.component';insrc/app/design-system/index.ts. - Storybook:
storybook/stories/multiselect.stories.ts(Design System/Patterns/wbl-multiselect).