Grouped Options
Checkboxes live inside labeled fieldsets and are the source of truth, submitting with the form as a native checkbox array. With the Apply button (shown here) toggles stage in the panel and commit on Apply — filter-style; drop the button and every toggle commits instantly.
Validation v1.4.0+
Min/max selection constraints set on the wrapper and enforced on submit — always against the applied set, never a staged draft. Once an error shows, it clears live as the selection changes.
Built-in Features
Any .nds-multiselect on the page wires up on load. The inner dropmenu boots itself so the trigger, panel, and keyboard flow work without extra JS.
A footer button with data-multiselect-action="apply" switches the field to filter-style staging: toggles stage in the open panel, Apply commits them (chips, summary, change event), and closing without Apply discards. Without the button, every toggle commits instantly. Submitting mid-staging always posts and validates the applied set.
data-multiselect-options builds the option rows from JSON — a flat array, a {value: label} map, or a grouped {legend: {value: label}} object — and data-multiselect-selected pre-checks values. populate(options, selected) does the same at runtime for fetched data.
Selected values render as chips inside the form-control. Clicking a chip unchecks the option and fires a change event immediately, no need to reopen the panel.
Each option checkbox carries name="field[]" and its own value, so the browser posts the checked set as an array with zero extra wiring. data-multiselect-name on the wrapper auto-fills any checkbox missing a name.
Arrow keys walk the checkboxes, Space toggles, and Escape closes the panel and returns focus to the trigger. Removing a chip hands focus to the next chip, or back to the trigger when it was the last. A visible focus ring follows each option.
Emit checked on the persisted options — the standard checkbox restore — and the field renders their chips on init. No hidden carrier inputs, no inline JS.
The trigger button's aria-label reflects the current selection on every commit, so screen readers announce the selected values instead of the static button label. Every commit — instant toggle, Apply, chip removal, clear-all — also announces through the shared NDS live region in both English and Arabic.
Set data-required, data-min-checked, or data-max-checked on the wrapper and NDS.Forms.validateForm enforces it, anchoring the error on the field. Once an error shows, it clears live as the selection changes.
Every field exposes an instance on the DOM node with getSelected(), apply(), reset(), removeValue(), and populate(). Listen for nds:multiselect:change to react to committed selections.
Usage Guidelines
Best Practices
- Use multiselect for form fields where users pick several values from a list of ten or more options and benefit from seeing the applied set as chips they can remove individually
- Group related options inside
<fieldset>elements with a<legend>. Groups make long lists scannable and let assistive technology announce the category - For a short, flat list (three to six options) without a category axis, reach for a plain Checkbox Group instead. Multiselect's overhead (dropmenu trigger, panel, chip track) adds friction when the options fit inline
- For a single-value choice, use Radio Button or the select-mode of Dropmenu. Multiselect's chips-over-checkboxes pattern is wasted on single-pick fields
- Give each option checkbox a
name="field[]"andvalueso the browser posts the checked set natively — or setdata-multiselect-nameonce on the wrapper to auto-fill missing names. Unnamed checkboxes do not post - For long or server-driven option lists, skip the hand-written rows: declare
data-multiselect-options(grouped JSON) on the wrapper, or callpopulate()with fetched data. Both build the same canonical markup - Keep the Apply button for fields where users explore many toggles before settling (matching the Filter pattern); drop it for short lists where instant chip feedback reads better
- Set a meaningful
data-labelon each checkbox so chip text stays readable when the surrounding label changes, wraps, or contains extra markup - Keep the placeholder short (two to four words). It shares the row with chips once any are applied, so a long placeholder fights for space
- Add a
<hr class="nds-divider">between fieldsets in the panel to make the grouping visually clear - For a server-rendered form re-displaying a saved selection (after a validation error, edit screen), emit
checkedon the persisted options — the field renders their chips on init, no inline JS needed - To make the field mandatory or bound the selection size, set
data-required,data-min-checked, ordata-max-checkedon the wrapper —NDS.Forms.validateFormenforces them like any checkbox group - When the number of selections carries meaning (quota, pricing tier), listen for
nds:multiselect:changeand show a count or validation hint outside the field rather than overloading the placeholder
Data Attributes
| Attribute | Description |
|---|---|
data-multiselect-name | Set on .nds-multiselect. Auto-fills name="name[]" on any option checkbox missing a name, so the form posts the checked set as an array. Redundant when every checkbox already carries its own name |
data-chip-class | Set on .nds-multiselect. Classes applied to generated chips: color variants nds-primary / nds-neutral, sizes nds-sm / nds-md / nds-lg, plus nds-rounded. Defaults to nds-primary nds-sm. See Chips |
data-multiselect-dropmenu | Marks the wrapping .nds-form-action.nds-prefix.nds-dropmenu as the host of the options panel |
data-multiselect-chips | Marks the container that receives rendered chips. Required inside .nds-form-control |
data-multiselect-options | Set on .nds-multiselect. JSON that builds the option rows into the (otherwise empty) menu: an array (value doubles as label), a {value: label} map, or a grouped {legend: {value: label}} object rendering one fieldset per key. Omit to hand-write the rows |
data-multiselect-selected | Set on .nds-multiselect alongside data-multiselect-options. JSON array of values to pre-check — the populated equivalent of the checked attribute |
data-multiselect-action | Set on footer buttons. reset unchecks every option (staged-only while an Apply panel is open). apply commits the staged set and switches the whole field to staged mode by its presence — toggles stage in the open panel, closing without Apply reverts them, and a form submit mid-staging posts the applied set |
data-required / data-min-checked / data-max-checked | Set on .nds-multiselect. Validation rules enforced by NDS.Forms.validateForm: data-required means at least one option; min/max bound the checked count. The error anchors on the dropdown trigger and clears live once fixed |
data-label | Set on each checkbox input. Overrides the visible label text when rendering chips, useful when the visible label contains extra markup or context |
data-no-auto-close | Set on the checkbox fieldset and the Reset button so the dropmenu stays open while the user toggles options or resets |
data-state~="filled" | Stamped on .nds-multiselect by JS when at least one option is checked. Hides the placeholder via CSS. Removed when all values are cleared |
JavaScript API
The NDS.Multiselect namespace initializes all .nds-multiselect fields on load. Each instance lives on its DOM node as element.ndsMultiselect and exposes methods for programmatic control. Listen for nds:multiselect:change to react to committed selections.