Multi List
Multi List widgets are Input Fields that let the user toggle any number of items on and off in a scrolling list. The value bound at the widget’s path is an array of the selected item values, in the order they were toggled on. Like the List, the widget virtualizes its rows, so only the items inside the viewport are rendered regardless of how many you pass in.
Selection lives on the rows themselves: a toggled row shows a check square and carries aria-selected="true", and the list is announced as multiselectable. There are no pills. For a compact field with pills and a filter box, use the Multi Dropdown; for a single value, use the List.
The list is a single tab stop. ArrowUp and ArrowDown move the focused row, PageUp and PageDown move by one viewport, Home and End jump to the first and last enabled row, and Enter, Space or a click toggle the focused row. Disabled rows are skipped.
The simplest way to populate items is a flat array of strings or numbers: the displayed label and the stored value are the same. When you need a separate stored value (for example an id behind a human-readable name), pass an array of objects and tell the widget which keys to use via labelField and valueField.
import { gui } from '@golemui/gui-shared';
export default [ gui.inputs.multiList('toppings', { items: ['Cheese', 'Bacon', 'Mushrooms', 'Olives', 'Peppers', 'Onions'], height: 200, itemHeight: 40, label: 'Toppings', }),];{ "$schema": "https://golemui.com/schemas/form.schema.json", "form": [ { "kind": "input", "type": "multiList", "path": "toppings", "label": "Toppings", "props": { "items": [ "Cheese", "Bacon", "Mushrooms", "Olives", "Peppers", "Onions" ], "height": 200, "itemHeight": 40 } } ]}Use these props to customize your Multi List widget.
| prop | type | description |
|---|---|---|
items | array | Required. Array of strings, numbers, or plain objects. See Using Objects. |
height | number | Total height of the scrollable viewport. Defaults to 300 |
hint | string | A description to display below the list |
itemHeight | number | Fixed height of each item for virtualization logic. Defaults to 40 |
itemRenderer | string | Key of a registered renderer used to draw each item. See Item Renderers |
labelField | string | When items are objects, the key whose value is shown to the user. Defaults to label. |
valueField | string | When items are objects, the key whose value is stored in the form. Defaults to value. Required for selection to emit a primitive. |
There is no prop that caps the number of selections. Use the array validator’s maxItems instead; see Validation.
Using Objects
Section titled “Using Objects”The top-of-page demo uses an array of strings: each entry is both what the user sees and what gets stored. That is enough when label and value are identical, but lists are often backed by domain data where the human-readable name and the stored identifier differ.
For that case, pass items as plain objects and use labelField and valueField to tell the list which key holds the displayed text and which one holds the stored value. The value array holds the valueField primitives, never the objects.
import { gui } from '@golemui/gui-shared';
export default [ gui.inputs.multiList('countries', { height: 200, itemHeight: 40, labelField: 'name', valueField: 'code', items: [ { name: 'USA', code: 'US', }, { name: 'Canada', code: 'CA', }, { name: 'Mexico', code: 'MX', }, { name: 'Brazil', code: 'BR', }, { name: 'Argentina', code: 'AR', }, ], label: 'Countries', }),];{ "$schema": "https://golemui.com/schemas/form.schema.json", "form": [ { "kind": "input", "type": "multiList", "path": "countries", "label": "Countries", "props": { "height": 200, "itemHeight": 40, "labelField": "name", "valueField": "code", "items": [ { "name": "USA", "code": "US" }, { "name": "Canada", "code": "CA" }, { "name": "Mexico", "code": "MX" }, { "name": "Brazil", "code": "BR" }, { "name": "Argentina", "code": "AR" } ] } } ]}If your objects already use the default key names (label and value), you can omit labelField and valueField entirely. Otherwise both should be set so selection can extract the right primitive.
Use the property hint to provide additional context for the list.
import { gui } from '@golemui/gui-shared';
export default [ gui.inputs.multiList('channels', { hint: 'Toggle every channel you want alerts on. Enter or Space toggles the focused row.', height: 160, items: ['Email', 'SMS', 'Push', 'Slack'], label: 'Notification channels', }),];{ "$schema": "https://golemui.com/schemas/form.schema.json", "form": [ { "kind": "input", "type": "multiList", "path": "channels", "label": "Notification channels", "props": { "hint": "Toggle every channel you want alerts on. Enter or Space toggles the focused row.", "height": 160, "items": [ "Email", "SMS", "Push", "Slack" ] } } ]}Item Renderer
Section titled “Item Renderer”Use the property itemRenderer to draw each row with a registered renderer instead of the default template. The key must match one of the renderers registered on the form host; see Item Renderers for the full setup.
The default template is what draws the check square. A custom renderer replaces the whole row, so read selected from the render context to mark toggled rows yourself. This demo uses the registered complexListItemRenderer, which paints a title and description per row and highlights the selected ones. Bump itemHeight to match the renderer’s row height so virtualization stays accurate.
import { gui } from '@golemui/gui-shared';
export default [ gui.inputs.multiList('templates', { height: 240, itemHeight: 60, labelField: 'title', valueField: 'value', itemRenderer: 'complexListItemRenderer', items: [ { value: 'blog', title: 'Blog', description: 'A minimalist Markdown blog with RSS feed and dark mode.', }, { value: 'docs', title: 'Documentation Site', description: 'Versioned docs with full-text search and a live playground.', }, { value: 'dashboard', title: 'Admin Dashboard', description: 'Data tables, charts, and role-based auth out of the box.', }, { value: 'storefront', title: 'E-commerce Storefront', description: 'Product catalog, cart, and Stripe checkout wired up.', }, { value: 'landing', title: 'Marketing Landing', description: 'Hero, pricing tiers, and a contact form ready to ship.', }, { value: 'portfolio', title: 'Developer Portfolio', description: 'Showcase projects, embed demos, and host a personal blog.', }, ], label: 'Pick your starter templates', }),];{ "$schema": "https://golemui.com/schemas/form.schema.json", "form": [ { "kind": "input", "type": "multiList", "path": "templates", "label": "Pick your starter templates", "props": { "height": 240, "itemHeight": 60, "labelField": "title", "valueField": "value", "itemRenderer": "complexListItemRenderer", "items": [ { "value": "blog", "title": "Blog", "description": "A minimalist Markdown blog with RSS feed and dark mode." }, { "value": "docs", "title": "Documentation Site", "description": "Versioned docs with full-text search and a live playground." }, { "value": "dashboard", "title": "Admin Dashboard", "description": "Data tables, charts, and role-based auth out of the box." }, { "value": "storefront", "title": "E-commerce Storefront", "description": "Product catalog, cart, and Stripe checkout wired up." }, { "value": "landing", "title": "Marketing Landing", "description": "Hero, pricing tiers, and a contact form ready to ship." }, { "value": "portfolio", "title": "Developer Portfolio", "description": "Showcase projects, embed demos, and host a personal blog." } ] } } ]}Use height to set the scrollable viewport height (default 300) and itemHeight to set each row’s height (default 40). Both are in pixels; the list virtualizes and scrolls when the items exceed the viewport, and PageUp and PageDown move by height / itemHeight rows.
import { gui } from '@golemui/gui-shared';
export default [ gui.inputs.multiList('weekdays', { hint: 'A 160px viewport with 32px rows', height: 160, itemHeight: 32, items: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'], label: 'Working days', }),];{ "$schema": "https://golemui.com/schemas/form.schema.json", "form": [ { "kind": "input", "type": "multiList", "path": "weekdays", "label": "Working days", "props": { "hint": "A 160px viewport with 32px rows", "height": 160, "itemHeight": 32, "items": [ "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" ] } } ]}Validation
Section titled “Validation”The value is an array, so the widget takes the array validator: required, minItems and maxItems. The widget never refuses a toggle. A selection past maxItems is stored and the validator flags it, so the user is told why instead of clicking a row that does nothing. Override the default error copy per rule with a messages object; see Features / Validators for the full rule list.
The demo caps the selection at two. Toggle a third row and tab away to see the error.
import { gui } from '@golemui/gui-shared';
export default [ gui.inputs.multiList('frameworks', { hint: 'A third selection is stored and flagged, not blocked', height: 200, items: ['React', 'Angular', 'Vue', 'Lit', 'Svelte', 'Solid'], label: 'Frameworks (one or two)', validator: { required: true, minItems: 1, maxItems: 2, messages: { required: 'Pick at least one framework', maxItems: 'No more than two frameworks', }, }, }),];{ "$schema": "https://golemui.com/schemas/form.schema.json", "form": [ { "kind": "input", "type": "multiList", "path": "frameworks", "label": "Frameworks (one or two)", "props": { "hint": "A third selection is stored and flagged, not blocked", "height": 200, "items": [ "React", "Angular", "Vue", "Lit", "Svelte", "Solid" ] }, "validator": { "type": "array", "required": true, "minItems": 1, "maxItems": 2, "messages": { "required": "Pick at least one framework", "maxItems": "No more than two frameworks" } } } ]}Styling
Section titled “Styling”Multi Lists can be styled as explained in the Styling Guide. They share the List styles and add the check square.
CSS Variables
Section titled “CSS Variables”Following you will find a list with the CSS Variables and a quick description of what you will style.
| CSS Variable | Description |
|---|---|
--gui-bg-default | Background color for the list container |
--gui-border-default | Border color for the list container |
--gui-radius-md | Border radius for the list container and the check square |
--gui-intent-primary | Background color for selected rows |
--gui-intent-primary-hover | Background color for rows on hover |
--gui-intent-primary-text | Text and check mark color for selected and hovered rows |
--gui-shadow-focus | Box shadow of the focused row |
--gui-bg-disabled | Background color for disabled rows |
Anatomy
Section titled “Anatomy”This is the anatomy of the Multi List Widget in case you want to use your CSS styles.
<gui-multi-list-input class="gui-multi-list-widget gui-field"> <gui-label> <span class="gui-label" id="fieldUid_label"> <span class="gui-label__text">Toppings</span> </span> </gui-label>
<div class="gui-widget"> <gui-multi-list role="listbox" tabindex="0" aria-multiselectable="true" aria-labelledby="fieldUid_label" aria-activedescendant="fieldUid-item-1" > <div class="gui-list__item-wrapper" role="option" tabindex="-1" id="fieldUid-item-0" aria-selected="false" aria-disabled="false" style="height: 40px"> <div class="gui-list__item" role="option" aria-selected="false" aria-disabled="false"> <span class="gui-list__item-check" aria-hidden="true"> <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 256 256"> <path d="M229.66,77.66l-128,128a8,8,0,0,1-11.32,0l-56-56a8,8,0,0,1,11.32-11.32L96,188.69,218.34,66.34a8,8,0,0,1,11.32,11.32Z"></path> </svg> </span> Cheese </div> </div>
<div class="gui-list__item-wrapper" role="option" tabindex="-1" id="fieldUid-item-1" aria-selected="true" aria-disabled="false" style="height: 40px"> <div class="gui-list__item gui-list__item-selected" role="option" aria-selected="true" aria-disabled="false"> <span class="gui-list__item-check" aria-hidden="true"> <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 256 256"> <path d="M229.66,77.66l-128,128a8,8,0,0,1-11.32,0l-56-56a8,8,0,0,1,11.32-11.32L96,188.69,218.34,66.34a8,8,0,0,1,11.32,11.32Z"></path> </svg> </span> Bacon </div> </div>
<!-- More items --> </gui-multi-list> </div></gui-multi-list-input>