A list shows a set of related items and lets the user select one or several of them. To pick a single value from a closed set inside a form use Select, and for hierarchical data use Tree.
By default a click selects exactly one option: autoSelect clears the rest of the selection first. Set
autoSelect="false" to make a click toggle only the option it landed on and leave the others alone.
noUnselectLast keeps the list from ending up empty: Ctrl + click on the only selected option is
ignored. Set it to false to allow deselecting everything.
Both are on by default and both are turned off automatically by multiple="checkbox", where a click is
expected to toggle a single row. Setting either one yourself pins it, so a later change of multiple
leaves your value alone.
multiple takes the mode as its value. multiple="checkbox" renders a checkbox in every option, so the
selection is readable without focus:
multiple="keyboard" selects without any checkbox: a plain click still moves the selection to one
option, and the user extends it with Ctrl + click or Shift + click.
The mode is a closed set of values:
| value | mode |
|---|---|
multiple="checkbox" | checkbox |
multiple="keyboard" | keyboard |
multiple, multiple="true", [multiple]="true" | checkbox |
no attribute, multiple="false", [multiple]="false" | single selection |
Single selection is the default, so the way to ask for it is to leave multiple off entirely.
Anything else — multiple="single" included — falls back to single selection and is reported in the
console in dev mode.
multiple is a normal input, so the mode can be bound and changed at any time:
<kbq-list-selection [multiple]="mode()" [(ngModel)]="selected"> <kbq-list-option [value]="item">{{ item }}</kbq-list-option> </kbq-list-selection>
Narrowing a list that has several options selected keeps the first selected one, drops the rest, reports the
shortened value through the form control and emits selectionChange for each option it deselected.
Note that this applies to kbq-list-selection and kbq-tree-selection only. On
Select and kbq-tree-select, multiple still cannot be changed after
initialization and throws if you try.
Subscribe to selectionChange rather than to selectionModel.changed: changing the mode replaces the
selection model, and a subscription taken on the model directly is left behind on the discarded instance.
When users often have to select every value, or to leave out just a few, put a "Select all" master checkbox
at the top of the list with the selectAll attribute — multiple="checkbox" mode only:
"Select all" is not rendered with single selection, multiple="keyboard", horizontal, an empty list or a
virtual scroller.
The list compares the values coming from the form control with the values of its options by identity. When
the options carry objects, pass a compareWith function so that a value from the model is matched to its
option:
<kbq-list-selection multiple="checkbox" [compareWith]="compareById" [(ngModel)]="selected"> @for (item of items(); track item.id) { <kbq-list-option [value]="item">{{ item.name }}</kbq-list-option> } </kbq-list-selection>
compareWith also decides when an option keeps its selection: replacing the objects behind [value] with
equal ones — an immutable update, a refetch — leaves the selection alone. Bind a stable reference: an
expression that builds a new function on every change detection pass makes the list re-match on each one.
horizontal lays the options out in a row and rebinds the navigation to the Left/Right arrows.
<kbq-list-selection horizontal aria-label="Alignment"> <kbq-list-option [value]="'left'">Left</kbq-list-option> <kbq-list-option [value]="'right'">Right</kbq-list-option> </kbq-list-selection>
The list is a single tab stop. Tabbing into it focuses the first selected option, or the first item of the
list when nothing is selected. Inside it the arrows move the active option, Home and End jump to the
ends, PageUp and PageDown move by a page, Space and Enter toggle the active option, and typing
letters jumps to the option that starts with them.
Ctrl/Cmd + A selects every option that is not disabled, in multiple selection mode only. By default a
repeated press keeps them selected; selectAllToggle makes it deselect them instead. While a
selectAll row is on screen the shortcut always toggles both ways, so it and the master
checkbox cannot disagree; in the modes that render no row it keeps its select-only default. The batch is
reported through onSelectAll, which carries the options the shortcut could act on; selectionChange stays
silent for it.
The behaviour can be replaced wholesale with the selectAllHandler input. It receives the keyboard event
and the list, and it has to be a function — anything else throws. Note that the handler replaces the
shortcut only: the master checkbox keeps running the built-in toggle, so with selectAll on you own both
paths.
Ctrl/Cmd + C copies the active option. Subscribe to onCopy to decide what lands in the clipboard:
the event carries the list, the option and the original keyboard event, and the list does nothing else.
Without a subscriber the list falls back to copying String(value) of the active option itself.
Set the draggable property on kbq-list-selection to let the user reorder options.
A single option can opt out with draggable="false" on kbq-list-option. It cannot be picked up by
either the pointer or the keyboard, but — unlike a disabled option — it still takes focus and can be
selected. The rest of the list keeps moving around it, so its own index can still shift.
The list does not open a gap while an option is being dragged: the surrounding options stay put, the dragged one keeps its place as a faded row, and a line marks the position the option would land in. No line is shown while the pointer is over the place the option already occupies — dropping it there would change nothing.
Nothing else reacts while the drag lasts. The options stop responding to hover, because what takes the
drop is the list rather than the row under the pointer, and the cursor turns to no-drop whenever the
pointer leaves every list that would accept the option.
What follows the pointer is a plate carrying the option's own text — its label, and its caption on a
line of its own — cut off with an ellipsis once it reaches its maximum width. Set dragPreview="full"
on kbq-list-selection to drag a copy of the whole row instead, checkbox, icons and action button
included.
<kbq-list-selection draggable dragPreview="full" (dropped)="dropped($event)"> <kbq-list-option [value]="item">{{ item.name }}</kbq-list-option> </kbq-list-selection>
A draggable row keeps the cursor it would have anyway — it is still a row that can be clicked and
selected, and being draggable is no reason to stop looking like one. Set dragCursor="grab" where the
whole row is meant to read as a handle:
<kbq-list-selection draggable dragCursor="grab" (dropped)="dropped($event)"> <kbq-list-option [value]="item">{{ item.name }}</kbq-list-option> </kbq-list-selection>
The list never changes the data itself — it reports the move through the dropped event and you
apply it, usually with moveItemInArray from @angular/cdk/drag-drop. Track the options by their
identity (track item.id): with a positional key such as track $index the option at a given
position is kept and rebound to a different value, and an option drops its selection when its value
changes.
An option can also be picked up by one area of the row instead of the whole of it: project an element
carrying cdkDragHandle into it, and only that element starts a drag. A handle always shows the grab
cursor — that is what it is for, so it needs no dragCursor, and the row around it keeps its own cursor
whatever the list asks for. The directive is re-exported from KbqListModule, so nothing else has to be
imported.
Options can also be moved into another list. Pass the other kbq-list-selection through connectedTo
on both lists, and apply the move with transferArrayItem. An option arrives in the target list
unselected unless the target's own value already contains it.
Options are numbered across the whole list, so a kbq-optgroup can be reordered through and an option
dragged past its boundary changes group. Dragging is not supported inside cdk-virtual-scroll-viewport,
though: the indices reported by dropped count only the rendered options, so applying the move to the
backing array silently affects the wrong item. That combination logs a warning in development mode.
Reordering is pointer-only: there is no keyboard equivalent, so it is out of reach for anyone who cannot drag.
Lists connected by id rather than by a component reference show no drop indicator — an id cannot
be resolved back to the list instance that would have to draw it.
kbq-list-selection is announced as a listbox and every kbq-list-option as an option carrying its own aria-selected. The "select all" row stays an option too — a checkbox is not a valid child of a listbox — and reports the batch state on aria-checked, including mixed for a partial selection; it carries no aria-selected of its own. With multiple the list is additionally marked aria-multiselectable, and with horizontal it reports aria-orientation="horizontal" and moves the active option with the Left/Right arrows. The list is a single tab stop; roving focus moves between the options. A disabled list or option is reported through aria-disabled. The built-in pseudo-checkbox is decorative and stays out of the accessibility tree; if you project your own kbq-pseudo-checkbox instead (externalPseudoCheckbox), mark it aria-hidden="true" too — the option's own aria-selected already carries the selected state.
A listbox needs an accessible name, so give the list one with aria-label or aria-labelledby:
<kbq-list-selection aria-label="Mailboxes"> <kbq-list-option [value]="'inbox'">Inbox</kbq-list-option> <kbq-list-option [value]="'starred'">Starred</kbq-list-option> </kbq-list-selection>
kbq-list and kbq-list-item deliberately carry no role: they are a plain container used both for semantic lists and for purely visual grouping. Add role="list" / role="listitem" yourself when the content is a real list.
kbq-list-selection an accessible name. It is announced as a listbox, and a listbox without
a name tells a screen reader nothing about what is being chosen.multiple="checkbox" when the selection has to be readable at a glance. multiple="keyboard"
keeps the rows compact but hides the state from anyone who does not know to look for the highlight.