AppMultiSelect
Pick several values from one list — each choice shows as a removable chip in the field.
<app-multi-select label="Skills" name="skills">…</app-multi-select>A multi-select lets a user choose several values from one list — skills on a profile, tags on a record, filters on a report. Every choice shows as a removable chip inside the field, so the current selection is always visible without opening the dropdown.
Its look and behaviour come from the shared @webority/theme and the underlying <wui-multiselect> element, so the Razor <app-multi-select> and the React <AppMultiSelect> render identically on every Webority surface.
Playground
Every attribute, live. Each change re-renders the real tag helper on the server, so the markup underneath is the exact call that produced the field above it. The starting selection overflows a single row on purpose, so collapse-chips is easy to compare — add or remove chips by using the control itself.
<wui-multiselect> has no max or clearable attribute (unlike <app-select>, which does support clearable). An app that needs a cap enforces it itself, e.g. surfacing error once the posted selection passes the limit.Selected as chips
values is an array of the chosen option values; onChange hands you the new array. Each selected value renders as a chip you can remove without reopening the list.
<app-option selected>; removing a chip or picking a new option updates the posted value.Show code
<app-multi-select label="Skills" name="skills" placeholder="Add skills…">
<app-option value="iec-62305" selected="true">IEC 62305</app-option>
<app-option value="earthing" selected="true">Earthing</app-option>
<app-option value="bonding">Bonding</app-option>
<!-- … -->
</app-multi-select>
Sizes
size takes sm or lg (md is the default), matching AppInput and AppSelect. The control keeps its min-height but still grows as chips wrap to a second row.
Show code
<app-multi-select label="Small" name="s" size="sm">…</app-multi-select> <app-multi-select label="Medium (default)" name="s">…</app-multi-select> <app-multi-select label="Large" name="s" size="lg">…</app-multi-select>
Collapse to a count
By default chips wrap to as many rows as needed. Set collapse-chips to keep them on a single row the width of the control — whatever doesn't fit folds into a +N counter.
Show code
<app-multi-select label="Skills" name="skills" collapse-chips>
<app-option value="lightning-protection" selected>Lightning protection</app-option>
<!-- … -->
</app-multi-select>
Searchable checklist
The dropdown is a checklist with a search box on top, so a long option list stays usable — type to filter, tick to add.
Show code
<app-multi-select label="Tags" name="tags" placeholder="Select tags…">
<app-option value="earthing">Earthing</app-option>
<app-option value="bonding">Bonding</app-option>
<!-- … -->
</app-multi-select>
Leading icon
icon adds a leading Bootstrap Icons glyph before the chips — a visual cue for what the field collects.
Show code
<app-multi-select label="Skills" name="skills" icon="tag">
<app-option value="earthing" selected>Earthing</app-option>
<!-- … -->
</app-multi-select>
Option icons
Each option can carry an icon (a Bootstrap Icons name); it shows on the checklist row and on the chip once picked.
Show code
<app-multi-select label="Notify via" name="channels">
<app-option value="email" icon="mail" selected>Email</app-option>
<app-option value="push" icon="bell" selected>Push</app-option>
<!-- … -->
</app-multi-select>
Rich rows
An option can carry a description (a second line, also matched by the search), an avatar image URL and/or initials, and disabled with a short disabled-hint. A disabled row stays in the list, readable and reachable by the arrow keys, but cannot be picked.
Show code
<app-multi-select label="Reviewers" name="reviewers">
<app-option value="asha@example.com" description="asha@example.com" initials="AR">Asha Rao</app-option>
<app-option value="ben@example.com" description="ben@example.com" initials="BD"
disabled="true" disabled-hint="Already a member">Ben Das</app-option>
<!-- … -->
</app-multi-select>
Free entry
allow-custom puts a text input after the chips. Typing filters the list; Enter or comma with no highlighted row, leaving the field, or pasting a list commits what was typed as its own chip. custom-pattern decides what a typed entry may be; a refusal keeps the text and says why under the control.
Show code
<app-multi-select label="Invite people" name="invite" allow-custom="true"
custom-pattern="[^@\s]+@[^@\s]+\.[^@\s]+"
custom-invalid-message="Enter an email address.">
<app-option value="asha@example.com" description="asha@example.com" initials="AR" selected>Asha Rao</app-option>
<!-- … -->
</app-multi-select>
custom-pattern, anchored like the HTML pattern attribute.States
Surface a validation message with error, and mark a mandatory field with required.
Show code
<app-multi-select label="Skills" name="skills" required="true"
error="Pick at least one skill.">
<app-option value="earthing">Earthing</app-option>
<!-- … -->
</app-multi-select>
When to use
Use it when
- The user can choose more than one value from a single list — skills, tags, categories.
- The selection should stay visible as chips without reopening the dropdown.
- The option list is long enough that a searchable checklist beats a row of checkboxes.
Reach for something else when
- Only one value can ever apply. → AppSelect
- There are a few independent on/off settings. → AppCheckbox
- The list is remote and the user picks exactly one. → AppAutocomplete
- You only need to display or remove tokens, not pick them. → AppChip
Best practices
Every option
The whole surface of the component, one cell per value — preselection, overflow, both booleans, every size and icon, and every combined state.
values (React) / value + selected app-option (Razor) — empty, one, many (overflow)
(no selection) — placeholder shown
<app-option value="earthing" selected> — one chip
6 selected options — wraps to a second row
collapse-chips
collapse-chips="false" (default) — wraps to as many rows as needed
collapse-chips — folds the overflow into a "+N" counter
searchable
searchable="true" (default) — search box above the checklist
searchable="false" — plain checklist, no search box
size — every size, with chips
(default) — md
size="sm"
size="lg"
icon
(no icon)
icon="tag"
per-option icon
<app-option icon="…"> — shown on the row and the chip
state
disabled="true"
required="true" — asterisk on the label
required="true" error="…"
hint="…" (no error)
help="…" — info tip beside the label
Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
| asp-for | ModelExpression | — | Model property to bind to — supplies name, id, value, label and required from the model. |
| label | string | — | Field label above the control. |
| name | string | — | Field name — posts the comma-joined selection; also the element id the label points at. |
| value | string | — | Comma-joined selection seed (React values). With asp-for the bound model value is used instead, so a validation redisplay repopulates the selection. |
| placeholder | string | Select… | Placeholder shown when nothing is selected. |
| search-placeholder | string | Search… | Placeholder inside the checklist search box. |
| searchable | bool | true | Shows the search box on top of the checklist. Ignored with allow-custom, whose inline input is the search. |
| allow-custom | bool | false | Accept typed entries that are not options: Enter, comma, blur or a pasted list commits them as chips. |
| custom-pattern | string | — | Regular expression a typed entry must match in full (anchored like the HTML pattern attribute). The declarative counterpart of React validateCustom. |
| custom-invalid-message | string | Enter a valid value. | Message shown under the control when a typed entry is refused. |
| icon | string | — | Leading icon glyph before the chips, e.g. flag. |
| size | string | — | Control size: sm / lg. Omit for the default (md). |
| collapse-chips | bool | false | Keep chips on one row; fold the overflow into a +N counter. |
| disabled | bool | false | Disables the field. |
| required | bool | false | Renders the required asterisk on the label. |
| error | string | — | Validation message; replaces the hint and marks the field invalid. |
| hint | string | — | Helper text below the field when there is no error. |
| help | string | — | Info-tip content shown beside the label. |
| help-label | string | More information | aria-label for the info-tip button (React helpLabel). |
| <app-option> | value, selected, icon, description, avatar, initials, disabled, disabled-hint | — | Child options — value attribute + label as inner content; selected seeds the initial picks. description is a second line (also searched), avatar an image URL and initials the face without it; disabled keeps the row visible but unselectable, with disabled-hint beside it. |
| class | string | — | Extra classes merged onto the .field wrapper (React fieldClassName). |
| input-class | string | — | Extra classes on the control itself (the wui-multiselect) — the React className counterpart. The plain class attribute merges onto the .field wrapper instead. |