Skip to content
Components, switch product

Multi-select list

<gui-multi-list> is a listbox to pick any number of items. It is a gui-list whose value is a list: each pick adds an item to values, or takes it out. Like gui-list, it handles the keyboard, the focus and the scrolling, renders only the items in view, and leaves drawing the items to you.

For a field the user types in to search, with the list in a popup, the options drawn for you and the picked items as pills, use gui-multi-dropdown.

Code of the example above
LanguagesField.tsx
import { useState } from 'react';
import { GuiLabel, GuiMultiList } from '@golemui/gui-components/react';
import type { GuiVisibleItem, OptionValue } from '@golemui/gui-components';
import '@golemui/gui-components/index.css';
const languages = [
'Arabic', 'Bengali', 'Catalan', 'Chinese', 'Czech', 'Danish', 'Dutch',
'English', 'Finnish', 'French', 'German', 'Greek', 'Hebrew', 'Hindi',
'Hungarian', 'Indonesian', 'Italian', 'Japanese', 'Korean', 'Norwegian',
'Polish', 'Portuguese', 'Romanian', 'Russian', 'Spanish', 'Swahili',
'Swedish', 'Thai', 'Turkish', 'Ukrainian', 'Vietnamese',
];
export function LanguagesField() {
const [values, setValues] = useState<OptionValue[]>(['Catalan']);
const [visibleItems, setVisibleItems] = useState<GuiVisibleItem[]>([]);
return (
<>
<div className="gui-field">
<GuiLabel uid="languages" label="Languages you speak" />
<div className="gui-widget">
<GuiMultiList
id="languages"
uid="languages"
label="Languages you speak"
height={200}
items={languages}
values={values}
onGuiVisibleItemsChange={(event) =>
setVisibleItems(event.detail)
}
onGuiChange={(event) => setValues(event.detail.value ?? [])}
>
{/* The list draws no items: it reports the ones to render, and you
add them as its children. One option per item, from its id and
state. A click on it adds the item or takes it out. */}
{visibleItems.map((item) => {
const classes = ['gui-list__item'];
if (item.selected) classes.push('gui-list__item-selected');
if (item.focused) classes.push('gui-list__item-focused');
return (
<div
key={item.value}
id={item.id}
role="option"
aria-selected={item.selected}
className="gui-list__item-wrapper"
style={{ height: 40 }}
>
<div className={classes.join(' ')}>
<span className="gui-list__item-check" aria-hidden="true">
<svg viewBox="0 0 16 16" width="12" height="12">
<path d="M6.4 11.6 2.8 8l1.1-1.1 2.5 2.5 5.7-5.7 1.1 1.1z" />
</svg>
</span>
<span>{String(item.template)}</span>
</div>
</div>
);
})}
</GuiMultiList>
</div>
</div>
<p role="status">Selected: {values.join(', ') || 'none'}</p>
</>
);
}

The element’s entry point is @golemui/gui-components/multi-list, which registers <gui-multi-list>, and <gui-list>, which it builds on. The examples also use gui-label for a visible label. To install the package and load the stylesheet, see Installation.

The @golemui/gui-components/react components have typed props and an on* prop for each event. React 18 or later.

LanguagesField.tsx
import { useState } from 'react';
import { GuiMultiList } from '@golemui/gui-components/react';
import '@golemui/gui-components/index.css';
const options = ['English', 'Portuguese', 'Spanish'];
export function LanguagesField() {
const [languages, setLanguages] = useState([] as string[]);
return (
<GuiMultiList
label="Languages you speak"
name="languages"
items={options}
values={languages}
onGuiChange={(event) => setLanguages(event.detail.value ?? [] as string[])}
/>
);
}

Its value is values, an array, and gui-change reports the new array in event.detail.value.

You render the options as the list’s children, exactly as for gui-list, from the items gui-visible-items-change reports: an item is selected when its value is in values. A click, Enter or Space adds the item’s value to values or takes it out, and fires gui-item-toggle with the item’s value, then gui-input and gui-change with the new values.

Item renderers shows this example in React, Angular, Vue, Lit and plain JavaScript.

Properties

Property Type Default Description
values OptionValue[] [] The selected values.
valueField attribute value-field string – For items given as objects, the key of the value.
items ListItemInput[] [] The items of the list: values, or objects whose value value-field names. The list normalizes them to { template, value } objects, the detail of gui-update-items.
itemHeight attribute item-height number – Height of each item, in pixels. Needed to virtualize the list.
height number – Height of the scrollable list, in pixels.
visibleItems read-only, no attribute GuiVisibleItem[] – The items to render: the ones in view and five more on each side, each with its index, its option id and its state. Render one option per entry, as the list's children.
uid string – The id of the element's control. The ids of its label, hint and errors derive from it. Optional: when it is not set, the element generates one from its tag name and a page-wide counter, e.g. gui-textinput-3. Generated ids depend on creation order, so a server-rendered element must set uid or its ids will not match after hydration.

Form control properties

Every field has these, from the base class of the form controls.

Property Type Default Description
value OptionValue – The value of the selected item.
name string – The name the value is submitted under. Without it the element stays out of its form. Reflected, like a native control's: the form reads a single value's name from the attribute.
label string – The visible label, which also names the control for assistive technology. Without one the control has no accessible name; the date, time, range, tags and file fields warn about it in development builds.
hint string – Help text shown under the label and announced as the control's description.
errors string[] [] Error messages to show, typically from your own validation. They show as soon as they are set, unless touched is false.
touched boolean – Whether the user has interacted with the control. Leave it unset unless you validate on interaction: false holds errors back until it becomes true.
required boolean false The control needs a value: it is marked as required and, with a name, blocks submission.
readOnly attribute readonly boolean – The value can be read and focused but not changed.
disabled boolean – Disabled by its own attribute or by a disabled <fieldset> around it. Setting the property sets the attribute, like a native control's.
form read-only, no attribute HTMLFormElement | null – The <form> the element belongs to, if any.
validity read-only, no attribute ValidityState – The element's validity, like a native control's.
validationMessage read-only, no attribute string – The message the browser shows for an invalid value.

Methods

Method Description
checkValidity(): boolean –
reportValidity(): boolean –

Events

Every event bubbles and is composed, so you can listen on any ancestor.

Event Description
gui-item-toggle The user toggled an item. detail.value is its value.
gui-input The user toggled an item. detail.value is the new values.
gui-change The user toggled an item. detail.value is the new values.
gui-visible-items-change The items to render changed: the user scrolled, items changed, or an item was selected or became the active one. detail is the new visibleItems.
gui-blur Focus left the control.
gui-focus-change The focused item changed. detail.index is its index, or -1.
gui-range-change The rendered items changed while scrolling. detail has the startIndex and endIndex.
gui-update-items The items were normalized. detail is the list of items.

The example above starts with a value, from the values attribute: a JSON array. Everything on the List page applies too: items as objects with value-field, disabled items, height and item-height, filtering, and the read-only and disabled states.

The stylesheet draws a check box for an element with the gui-list__item-check class inside an option’s text, and shows the icon in it while the option is selected. Put any icon in it, such as the inline SVG of the example above; it takes the text color.

With a name, <gui-multi-list> submits one entry per selected item, all under that name, and nothing while none is selected. Read them with FormData.getAll(). required blocks the submit until the user picks at least one. Submit the form below, then pick a couple of topics and submit again.

Code of the example above
MultiListNativeForm.tsx
import { useState, type SubmitEvent } from 'react';
import {
GuiButton,
GuiLabel,
GuiMultiList,
} from '@golemui/gui-components/react';
import type { GuiVisibleItem, OptionValue } from '@golemui/gui-components';
import '@golemui/gui-components/index.css';
const topics = [
'Accessibility',
'Design systems',
'Performance',
'Web components',
];
export function MultiListNativeForm() {
const [values, setValues] = useState<OptionValue[]>([]);
const [visibleItems, setVisibleItems] = useState<GuiVisibleItem[]>([]);
const [result, setResult] = useState('');
const onSubmit = (event: SubmitEvent<HTMLFormElement>) => {
event.preventDefault();
// getAll: the list submits one entry per selected item.
const topics = new FormData(event.currentTarget).getAll('topics');
setResult(`Submitted ${JSON.stringify({ topics })}`);
};
return (
<form id="topics-form" onSubmit={onSubmit}>
<div className="gui-grid">
<div className="gui-grid__cell">
<div className="gui-field">
<GuiLabel uid="topics" label="Topics to follow" required />
<div className="gui-widget">
<GuiMultiList
id="topics"
uid="topics"
label="Topics to follow"
name="topics"
height={160}
required
items={topics}
values={values}
onGuiVisibleItemsChange={(event) =>
setVisibleItems(event.detail)
}
onGuiChange={(event) => setValues(event.detail.value ?? [])}
>
{visibleItems.map((item) => {
const classes = ['gui-list__item'];
if (item.selected) classes.push('gui-list__item-selected');
if (item.focused) classes.push('gui-list__item-focused');
return (
<div
key={item.value}
id={item.id}
role="option"
aria-selected={item.selected}
className="gui-list__item-wrapper"
style={{ height: 40 }}
>
<div className={classes.join(' ')}>
{String(item.template)}
</div>
</div>
);
})}
</GuiMultiList>
</div>
</div>
</div>
<div className="gui-grid__cell">
<GuiButton type="submit" label="Follow" />
</div>
</div>
<p id="topics-result" role="status">
{result}
</p>
</form>
);
}

How the elements submit, validate and reset is in Native forms.

<gui-multi-select-trigger> is the field of gui-multi-dropdown: the selected options as removable pills, and a search input with the combobox role. It holds no value, options or filtering of its own; the dropdown that composes it with a gui-multi-list in a popup owns them, and listens to its gui-pill-remove event. It is a building block rather than an element to use on its own: to let users pick several items, use gui-multi-dropdown or gui-multi-list, or gui-tags for free text.

Properties

Property Type Default Description
touched boolean – Whether that field was touched.
required boolean false Whether that field is required.
disabled boolean false Disables the trigger.
readOnly attribute readonly boolean false Shows the selection without letting the user change it.
errors string[] [] Errors of the field the trigger belongs to.
placeholder string – Text shown while nothing is selected.
icon string – Icon class name shown inside the trigger.
autocomplete string – The autocomplete hint of the search field.
removeAriaLabel attribute remove-aria-label string – Accessible name of each remove button. An empty value keeps the default.
removeIcon attribute remove-icon string – Icon class name of the remove buttons.
compactAriaLabel attribute compact-aria-label string – Accessible name of the count shown when the options do not fit. {count} is the number selected. An empty value keeps the default.
toolbarAriaLabel attribute toolbar-aria-label string – Accessible name of the selected options. An empty value removes it.
uid string – The id of the element's control. The ids of its label, hint and errors derive from it. Optional: when it is not set, the element generates one from its tag name and a page-wide counter, e.g. gui-textinput-3. Generated ids depend on creation order, so a server-rendered element must set uid or its ids will not match after hydration.

Events

Every event bubbles and is composed, so you can listen on any ancestor.

Event Description
gui-pill-remove The user removed a selected option from its pill. detail.key is the pill's key.
gui-dropdown-toggle The count bubble opened or closed its dropdown. detail.open is the new state.

CSS custom properties

Property Default Description
--gui-pill-height – Height of each pill.
--gui-pill-font-size – Font size of the pill text.
--gui-pill-action-size – Size of the icons inside a pill.
--gui-pill-action-hit – Clickable area of the buttons inside a pill.

Like gui-list, <gui-multi-list> has a shadow root that scrolls the items and holds them in place. Your options are its children in the light DOM, so plain CSS styles them:

gui-multi-list
<gui-multi-list
id="languages"
uid="languages"
role="listbox"
tabindex="0"
aria-label="Languages you speak"
aria-multiselectable="true"
aria-activedescendant="languages-item-2"
>
<!-- Shadow root: the scrolling box, which shows the items through its slots. -->
<div
id="languages-item-1"
role="option"
class="gui-list__item-wrapper"
aria-selected="false"
>
<div class="gui-list__item">
<span class="gui-list__item-check" aria-hidden="true"><svg>…</svg></span>
<span>Chinese</span>
</div>
</div>
<div
id="languages-item-2"
role="option"
class="gui-list__item-wrapper"
aria-selected="true"
>
<div class="gui-list__item gui-list__item-selected gui-list__item-focused">
<span class="gui-list__item-check" aria-hidden="true"><svg>…</svg></span>
<span>English</span>
</div>
</div>
<!-- One option per item in range, then the errors, as in gui-list. -->
</gui-multi-list>

The classes are those of gui-list, and one more:

ClassPart
gui-list__item-checkThe check box of an option. Its icon shows while the option’s text has gui-list__item-selected.

The shadow root has no parts to style and no tokens of its own: style the gui-multi-list element and your options from your stylesheet.

The list reads the same tokens as gui-list, and the check box reads two more:

TokenWhat it styles
--gui-radius-mdThe check box’s corner radius.
--gui-space-2The space between the check box and the text.

To restyle beyond the tokens, see Overriding styles; for dark mode and the Clay theme, see Themes and dark mode.

  • Listbox. The element itself is the listbox, with role="listbox" and aria-multiselectable="true", and takes the focus. The active item is announced through aria-activedescendant.
  • Name. label names the listbox through aria-label, and hint describes it through aria-description. Without a label the list has no accessible name.
  • Errors and states. As on gui-list: aria-invalid and aria-errormessage while errors show, and aria-required, aria-readonly and aria-disabled.
  • Options. Set aria-selected on each option you render from values, and hide the check icon from screen readers: aria-selected already says the option is picked.
KeyAction
TabMoves focus to the list, on the first selected item, and away from it.
↓ ↑Moves to the next or previous item, skipping disabled ones.
Page Down Page UpMoves by the number of items that fit in the list.
Home EndMoves to the first or last item that isn’t disabled.
Enter SpaceSelects the active item, or unselects it, and fires gui-change.

What every element shares, such as translating the browser messages, is in Accessibility.