Data input
Multi Select
Choose several options from a searchable list, shown as removable chips.
| Class name | Type | Description |
|---|---|---|
multi-selectplf:multi-select | Component | A native multiple select; the script adds a search field, chips and a list after it |
multi-select-controlplf:multi-select-control | Part | The field the script adds |
multi-select-chipsplf:multi-select-chips | Part | The row of chips and search the script adds |
multi-select-chipplf:multi-select-chip | Part | A chosen value's chip the script adds |
multi-select-chip-labelplf:multi-select-chip-label | Part | A chip's label the script adds |
multi-select-avatarplf:multi-select-avatar | Part | An option's initials the script adds |
multi-select-removeplf:multi-select-remove | Part | A chip's remove button the script adds |
multi-select-inputplf:multi-select-input | Part | The search field the script adds |
multi-select-toggleplf:multi-select-toggle | Part | The show-options button the script adds |
multi-select-clearplf:multi-select-clear | Part | The clear button the script adds |
multi-select-indicatorplf:multi-select-indicator | Part | An option's check mark the script adds |
multi-select-iconplf:multi-select-icon | Part | An icon the script adds |
multi-select-popupplf:multi-select-popup | Part | The popup the script adds |
multi-select-listplf:multi-select-list | Part | The option list the script adds |
multi-select-optionplf:multi-select-option | Part | An option the script adds |
multi-select-labelplf:multi-select-label | Part | An option's label the script adds |
multi-select-detailplf:multi-select-detail | Part | An option's second line the script adds |
multi-select-textplf:multi-select-text | Part | The label and detail column the script adds |
multi-select-emptyplf:multi-select-empty | Part | The no-match message the script adds |
multi-select-statusplf:multi-select-status | Part | The screen reader status the script adds |
multi-select-smplf:multi-select-sm | Size | Small |
multi-select-lgplf:multi-select-lg | Size | Large |
Default
Options need unique, nonempty values and no <optgroup>; read the values with FormData.getAll(name).
<form class="flex flex-col gap-4">
<div class="field">
<label class="field-label" for="multi-select-categories">Project categories <span class="field-required">(required)</span></label>
<select class="multi-select" multiple size="6" id="multi-select-categories" name="categories" required aria-describedby="multi-select-categories-help" data-plf-multi-select data-plf-placeholder="Search categories…">
<option value="product" selected>Product</option>
<option value="design" selected>Design</option>
<option value="engineering">Engineering</option>
<option value="marketing">Marketing</option>
<option value="operations">Operations</option>
<option value="legacy" disabled>Legacy (unavailable)</option>
</select>
<p class="field-help" id="multi-select-categories-help">Select at least one category. Click a selected option again to remove it.</p>
</div>
<div class="flex flex-wrap items-center gap-4">
<button class="btn btn-primary" type="submit">Save categories</button>
</div>
</form><form class="plf:flex plf:flex-col plf:gap-4">
<div class="plf:field">
<label class="plf:field-label" for="multi-select-categories">Project categories <span class="plf:field-required">(required)</span></label>
<select class="plf:multi-select" multiple size="6" id="multi-select-categories" name="categories" required aria-describedby="multi-select-categories-help" data-plf-multi-select data-plf-placeholder="Search categories…">
<option value="product" selected>Product</option>
<option value="design" selected>Design</option>
<option value="engineering">Engineering</option>
<option value="marketing">Marketing</option>
<option value="operations">Operations</option>
<option value="legacy" disabled>Legacy (unavailable)</option>
</select>
<p class="plf:field-help" id="multi-select-categories-help">Select at least one category. Click a selected option again to remove it.</p>
</div>
<div class="plf:flex plf:flex-wrap plf:items-center plf:gap-4">
<button class="plf:btn plf:btn-primary" type="submit">Save categories</button>
</div>
</form>import { Button, Field, MultiSelect } from '@pelatform/react';
export function Example() {
return (
<form className="flex flex-col gap-4">
<Field label="Project categories" required help="Select at least one category. Click a selected option again to remove it.">
<MultiSelect name="categories" placeholder="Search categories…" defaultValue={['product', 'design']}>
<option value="product">Product</option>
<option value="design">Design</option>
<option value="engineering">Engineering</option>
<option value="marketing">Marketing</option>
<option value="operations">Operations</option>
<option value="legacy" disabled>Legacy (unavailable)</option>
</MultiSelect>
</Field>
<div className="flex flex-wrap items-center gap-4">
<Button color="primary" type="submit">Save categories</Button>
</div>
</form>
);
}import { Button, Field, MultiSelect } from '@pelatform/react/prefixed';
export function Example() {
return (
<form className="plf:flex plf:flex-col plf:gap-4">
<Field label="Project categories" required help="Select at least one category. Click a selected option again to remove it.">
<MultiSelect name="categories" placeholder="Search categories…" defaultValue={['product', 'design']}>
<option value="product">Product</option>
<option value="design">Design</option>
<option value="engineering">Engineering</option>
<option value="marketing">Marketing</option>
<option value="operations">Operations</option>
<option value="legacy" disabled>Legacy (unavailable)</option>
</MultiSelect>
</Field>
<div className="plf:flex plf:flex-wrap plf:items-center plf:gap-4">
<Button color="primary" type="submit">Save categories</Button>
</div>
</form>
);
}<div class="flex flex-col gap-4">
<div class="field">
<label class="field-label" for="multi-select-size-sm">Small</label>
<select class="multi-select multi-select-sm" multiple id="multi-select-size-sm" name="compact-categories" data-plf-multi-select data-plf-placeholder="Add categories…">
<option value="product" selected>Product</option>
<option value="design">Design</option>
<option value="engineering">Engineering</option>
</select>
</div>
<div class="field">
<label class="field-label" for="multi-select-size-default">Default</label>
<select class="multi-select" multiple id="multi-select-size-default" name="default-categories" data-plf-multi-select data-plf-placeholder="Add categories…">
<option value="product" selected>Product</option>
<option value="design">Design</option>
<option value="engineering">Engineering</option>
</select>
</div>
<div class="field">
<label class="field-label" for="multi-select-size-lg">Large</label>
<select class="multi-select multi-select-lg" multiple id="multi-select-size-lg" name="large-categories" data-plf-multi-select data-plf-placeholder="Add categories…">
<option value="product" selected>Product</option>
<option value="design">Design</option>
<option value="engineering">Engineering</option>
</select>
</div>
</div><div class="plf:flex plf:flex-col plf:gap-4">
<div class="plf:field">
<label class="plf:field-label" for="multi-select-size-sm">Small</label>
<select class="plf:multi-select plf:multi-select-sm" multiple id="multi-select-size-sm" name="compact-categories" data-plf-multi-select data-plf-placeholder="Add categories…">
<option value="product" selected>Product</option>
<option value="design">Design</option>
<option value="engineering">Engineering</option>
</select>
</div>
<div class="plf:field">
<label class="plf:field-label" for="multi-select-size-default">Default</label>
<select class="plf:multi-select" multiple id="multi-select-size-default" name="default-categories" data-plf-multi-select data-plf-placeholder="Add categories…">
<option value="product" selected>Product</option>
<option value="design">Design</option>
<option value="engineering">Engineering</option>
</select>
</div>
<div class="plf:field">
<label class="plf:field-label" for="multi-select-size-lg">Large</label>
<select class="plf:multi-select plf:multi-select-lg" multiple id="multi-select-size-lg" name="large-categories" data-plf-multi-select data-plf-placeholder="Add categories…">
<option value="product" selected>Product</option>
<option value="design">Design</option>
<option value="engineering">Engineering</option>
</select>
</div>
</div>import { Field, MultiSelect } from '@pelatform/react';
export function Example() {
return (
<div className="flex flex-col gap-4">
<Field label="Small">
<MultiSelect size="sm" name="compact-categories" placeholder="Add categories…" defaultValue={['product']}>
<option value="product">Product</option>
<option value="design">Design</option>
<option value="engineering">Engineering</option>
</MultiSelect>
</Field>
<Field label="Default">
<MultiSelect name="default-categories" placeholder="Add categories…" defaultValue={['product']}>
<option value="product">Product</option>
<option value="design">Design</option>
<option value="engineering">Engineering</option>
</MultiSelect>
</Field>
<Field label="Large">
<MultiSelect size="lg" name="large-categories" placeholder="Add categories…" defaultValue={['product']}>
<option value="product">Product</option>
<option value="design">Design</option>
<option value="engineering">Engineering</option>
</MultiSelect>
</Field>
</div>
);
}import { Field, MultiSelect } from '@pelatform/react/prefixed';
export function Example() {
return (
<div className="plf:flex plf:flex-col plf:gap-4">
<Field label="Small">
<MultiSelect size="sm" name="compact-categories" placeholder="Add categories…" defaultValue={['product']}>
<option value="product">Product</option>
<option value="design">Design</option>
<option value="engineering">Engineering</option>
</MultiSelect>
</Field>
<Field label="Default">
<MultiSelect name="default-categories" placeholder="Add categories…" defaultValue={['product']}>
<option value="product">Product</option>
<option value="design">Design</option>
<option value="engineering">Engineering</option>
</MultiSelect>
</Field>
<Field label="Large">
<MultiSelect size="lg" name="large-categories" placeholder="Add categories…" defaultValue={['product']}>
<option value="product">Product</option>
<option value="design">Design</option>
<option value="engineering">Engineering</option>
</MultiSelect>
</Field>
</div>
);
}<form class="flex flex-col gap-4">
<div class="field">
<label class="field-label" for="multi-select-team">Project members</label>
<select class="multi-select" multiple size="6" id="multi-select-team" name="members" aria-describedby="multi-select-team-help" data-plf-multi-select data-plf-placeholder="Search name or email…">
<option value="olivia" data-plf-initials="OM" data-plf-detail="olivia@example.com · Owner" selected>Olivia Martin</option>
<option value="jackson" data-plf-initials="JL" data-plf-detail="jackson@example.com · Engineering" selected>Jackson Lee</option>
<option value="sofia" data-plf-initials="SD" data-plf-detail="sofia@example.com · Design">Sofia Davis</option>
<option value="noah" data-plf-initials="NW" data-plf-detail="noah@example.com · Product">Noah Wilson</option>
<option value="isabella" data-plf-initials="IB" data-plf-detail="isabella@example.com · Marketing">Isabella Brown</option>
<option value="liam" data-plf-initials="LJ" data-plf-detail="liam@example.com · Invitation pending" disabled>Liam Johnson</option>
</select>
<p class="field-help" id="multi-select-team-help">Search by name, email or team. Pending invitations cannot be selected.</p>
</div>
<div class="flex flex-wrap items-center gap-4">
<button class="btn btn-primary" type="submit">Save team</button>
</div>
</form><form class="plf:flex plf:flex-col plf:gap-4">
<div class="plf:field">
<label class="plf:field-label" for="multi-select-team">Project members</label>
<select class="plf:multi-select" multiple size="6" id="multi-select-team" name="members" aria-describedby="multi-select-team-help" data-plf-multi-select data-plf-placeholder="Search name or email…">
<option value="olivia" data-plf-initials="OM" data-plf-detail="olivia@example.com · Owner" selected>Olivia Martin</option>
<option value="jackson" data-plf-initials="JL" data-plf-detail="jackson@example.com · Engineering" selected>Jackson Lee</option>
<option value="sofia" data-plf-initials="SD" data-plf-detail="sofia@example.com · Design">Sofia Davis</option>
<option value="noah" data-plf-initials="NW" data-plf-detail="noah@example.com · Product">Noah Wilson</option>
<option value="isabella" data-plf-initials="IB" data-plf-detail="isabella@example.com · Marketing">Isabella Brown</option>
<option value="liam" data-plf-initials="LJ" data-plf-detail="liam@example.com · Invitation pending" disabled>Liam Johnson</option>
</select>
<p class="plf:field-help" id="multi-select-team-help">Search by name, email or team. Pending invitations cannot be selected.</p>
</div>
<div class="plf:flex plf:flex-wrap plf:items-center plf:gap-4">
<button class="plf:btn plf:btn-primary" type="submit">Save team</button>
</div>
</form>import { Button, Field, MultiSelect } from '@pelatform/react';
export function Example() {
return (
<form className="flex flex-col gap-4">
<Field label="Project members" help="Search by name, email or team. Pending invitations cannot be selected.">
<MultiSelect name="members" placeholder="Search name or email…" defaultValue={['olivia', 'jackson']}>
<option value="olivia" data-plf-initials="OM" data-plf-detail="olivia@example.com · Owner">Olivia Martin</option>
<option value="jackson" data-plf-initials="JL" data-plf-detail="jackson@example.com · Engineering">Jackson Lee</option>
<option value="sofia" data-plf-initials="SD" data-plf-detail="sofia@example.com · Design">Sofia Davis</option>
<option value="noah" data-plf-initials="NW" data-plf-detail="noah@example.com · Product">Noah Wilson</option>
<option value="isabella" data-plf-initials="IB" data-plf-detail="isabella@example.com · Marketing">Isabella Brown</option>
<option value="liam" data-plf-initials="LJ" data-plf-detail="liam@example.com · Invitation pending" disabled>Liam Johnson</option>
</MultiSelect>
</Field>
<div className="flex flex-wrap items-center gap-4">
<Button color="primary" type="submit">Save team</Button>
</div>
</form>
);
}import { Button, Field, MultiSelect } from '@pelatform/react/prefixed';
export function Example() {
return (
<form className="plf:flex plf:flex-col plf:gap-4">
<Field label="Project members" help="Search by name, email or team. Pending invitations cannot be selected.">
<MultiSelect name="members" placeholder="Search name or email…" defaultValue={['olivia', 'jackson']}>
<option value="olivia" data-plf-initials="OM" data-plf-detail="olivia@example.com · Owner">Olivia Martin</option>
<option value="jackson" data-plf-initials="JL" data-plf-detail="jackson@example.com · Engineering">Jackson Lee</option>
<option value="sofia" data-plf-initials="SD" data-plf-detail="sofia@example.com · Design">Sofia Davis</option>
<option value="noah" data-plf-initials="NW" data-plf-detail="noah@example.com · Product">Noah Wilson</option>
<option value="isabella" data-plf-initials="IB" data-plf-detail="isabella@example.com · Marketing">Isabella Brown</option>
<option value="liam" data-plf-initials="LJ" data-plf-detail="liam@example.com · Invitation pending" disabled>Liam Johnson</option>
</MultiSelect>
</Field>
<div className="plf:flex plf:flex-wrap plf:items-center plf:gap-4">
<Button color="primary" type="submit">Save team</Button>
</div>
</form>
);
}Options from a request
The list follows the native select, so add the options your request returns to it.
<div class="field">
<label class="field-label" for="multi-select-tags">Tags</label>
<select class="multi-select" multiple id="multi-select-tags" name="tags" data-plf-multi-select data-plf-placeholder="Loading tags…"></select>
</div>
<script>
const select = document.getElementById('multi-select-tags');
async function loadTags() {
// Your request, here a list of { id, name }.
const response = await fetch('/tags');
if (!response.ok) throw new Error(response.statusText);
const tags = await response.json();
select.append(...tags.map(tag => new Option(tag.name, tag.id)));
select.setAttribute('data-plf-placeholder', 'Search tags…');
}
loadTags().catch(() => { select.setAttribute('data-plf-placeholder', 'Tags could not be loaded'); });
</script><div class="plf:field">
<label class="plf:field-label" for="multi-select-tags">Tags</label>
<select class="plf:multi-select" multiple id="multi-select-tags" name="tags" data-plf-multi-select data-plf-placeholder="Loading tags…"></select>
</div>
<script>
const select = document.getElementById('multi-select-tags');
async function loadTags() {
// Your request, here a list of { id, name }.
const response = await fetch('/tags');
if (!response.ok) throw new Error(response.statusText);
const tags = await response.json();
select.append(...tags.map(tag => new Option(tag.name, tag.id)));
select.setAttribute('data-plf-placeholder', 'Search tags…');
}
loadTags().catch(() => { select.setAttribute('data-plf-placeholder', 'Tags could not be loaded'); });
</script>'use client';
import { useEffect, useState } from 'react';
import { Field, MultiSelect } from '@pelatform/react';
export function Example() {
const [tags, setTags] = useState<{ id: string; name: string }[]>([]);
const [placeholder, setPlaceholder] = useState('Loading tags…');
useEffect(() => {
async function loadTags() {
// Your request, here a list of { id, name }.
const response = await fetch('/tags');
if (!response.ok) throw new Error(response.statusText);
setTags(await response.json());
setPlaceholder('Search tags…');
}
loadTags().catch(() => setPlaceholder('Tags could not be loaded'));
}, []);
return (
<Field label="Tags">
<MultiSelect name="tags" placeholder={placeholder}>
{tags.map(tag => <option key={tag.id} value={tag.id}>{tag.name}</option>)}
</MultiSelect>
</Field>
);
}'use client';
import { useEffect, useState } from 'react';
import { Field, MultiSelect } from '@pelatform/react/prefixed';
export function Example() {
const [tags, setTags] = useState<{ id: string; name: string }[]>([]);
const [placeholder, setPlaceholder] = useState('Loading tags…');
useEffect(() => {
async function loadTags() {
// Your request, here a list of { id, name }.
const response = await fetch('/tags');
if (!response.ok) throw new Error(response.statusText);
setTags(await response.json());
setPlaceholder('Search tags…');
}
loadTags().catch(() => setPlaceholder('Tags could not be loaded'));
}, []);
return (
<Field label="Tags">
<MultiSelect name="tags" placeholder={placeholder}>
{tags.map(tag => <option key={tag.id} value={tag.id}>{tag.name}</option>)}
</MultiSelect>
</Field>
);
}JavaScript
Multi Select needs JavaScript. The Full script (pelatform-ui.min.js) starts every component on page load, with no code of your own; with ESM imports, call init() yourself, as in Installation. For markup you add later, see Dynamic content.
<link rel="stylesheet" href="pelatform-ui.min.css">
<script src="pelatform-ui.min.js" defer></script><link rel="stylesheet" href="core/pelatform-ui-core.min.css">
<link rel="stylesheet" href="components/button.min.css">
<link rel="stylesheet" href="components/form.min.css">
<link rel="stylesheet" href="components/multi-select.min.css">
<script src="core/pelatform-ui-core.min.js" defer></script>
<script src="components/multi-select.min.js" defer></script>
<script>document.addEventListener('DOMContentLoaded', () => PLF.init());</script><link rel="stylesheet" href="pelatform-ui.prefixed.min.css">
<script src="pelatform-ui.min.js" defer></script><link rel="stylesheet" href="core/pelatform-ui-core.prefixed.min.css">
<link rel="stylesheet" href="components/button.prefixed.min.css">
<link rel="stylesheet" href="components/form.prefixed.min.css">
<link rel="stylesheet" href="components/multi-select.prefixed.min.css">
<script src="core/pelatform-ui-core.min.js" defer></script>
<script src="components/multi-select.min.js" defer></script>
<script>document.addEventListener('DOMContentLoaded', () => PLF.init());</script>| Attribute | Purpose |
|---|---|
data-plf-multi-select |
On a multiple select: adds the search field, chips and list after it; the field takes the select’s classes, style and hidden |
data-plf-placeholder |
On the select: text in the search field; Search and select… by default |
data-plf-empty |
On the select: message when nothing matches; No options found. by default |
data-plf-label |
On an option: the label shown in the list and chip |
data-plf-detail |
On an option: a second line that is also searched |
data-plf-initials |
On an option: decorative initials before the label |
data-plf-keywords |
On an option: extra words that match the search |
| Method | Behavior |
|---|---|
PLF. |
Returns the instance, creating it for valid markup |
PLF. |
Returns the instance or null |
instance., instance. |
Open or close the list; return false when nothing changes or the event is canceled |
instance. |
Returns the chosen values in option order |
instance. |
Sets the chosen values from an array of strings; returns false for unknown or duplicate values, or when a disabled option would change |
instance. |
Adds or removes one value |
instance. |
Removes every value except those on disabled options |
instance. |
Removes the field, chips and list; the select keeps its values |
| Event | When |
|---|---|
plf:, plf: |
Before and after the list opens; the first is cancelable |
plf:, plf: |
Before and after the list closes; the first is cancelable |
plf:, plf: |
Before and after the values change; the first is cancelable |
Each event’s detail:
| Property | Events | Value |
|---|---|---|
instance |
All | The Multi Select instance |
reason |
All | api, trigger, keyboard, or native for a native input or change your code dispatches, which skips the before event |
trigger |
All | The search field, the clicked button or option, the select for native, or null from a method or a press or focus outside |
previousValue |
before-change, change |
The chosen values before the change, an array |
value |
before-change, change |
The chosen values after the change, an array |
A change also fires native input and change on the select.
Keyboard
| Key | Action |
|---|---|
| Letters, Space | Type in the search and filter the list |
| Arrow Down, Arrow Up | Open the list, then move through enabled options, wrapping at the ends |
| Enter | Opens the list; while open, adds or removes the active option and keeps the list open |
| Backspace | In an empty search, removes the last chosen value |
| Escape | Closes the list and clears the search; chosen values stay |
| Tab | Closes the list and moves on |
React
MultiSelect from @pelatform/react renders the native multiple select around its option children, with the field, chips and list the script adds; inside a Field its search field takes the field’s label, help and error.
| Prop | Values | Default |
|---|---|---|
size |
sm, lg |
The default size |
value |
An array of the chosen values, which the application changes in onValueChange |
Its own state |
defaultValue |
The values it starts with when the application does not set value |
None |
onValueChange |
(values), called with the chosen values, in option order, when they change |
None |
open |
true or false, which the application changes in onOpenChange |
Its own state |
defaultOpen |
true starts open when the application does not set open |
false |
onOpenChange |
(open, reason), called when the list opens or closes: trigger (the field, the toggle or a press outside) or keyboard (a key or the focus leaving) |
None |
placeholder |
The search field’s text | Search and select… |
emptyText |
The message when nothing matches | No options found. |
clearLabel |
The clear button’s name | Clear selection |
removeLabel |
(label) => string, a chip’s remove button’s name |
Remove {label} |
showLabel, hideLabel |
The toggle’s name while the list is closed and while it is open | Show options, Hide options |
An option takes data-plf-label, data-plf-detail, data-plf-initials and data-plf-keywords, as in HTML. The other props are a native select’s, className and ref included; the search field takes id, which the label names.
CSS variables
Override on a root or a container; on the select itself they reach the field, not the list.
| Variable | Default |
|---|---|
--plf-multi-select-height |
Minimum height: 2. (36px); 2rem (32px) with multi-select-sm, 2. (40px) with multi-select-lg; the list’s rows take it too |
--plf-multi-select-padding-block, --plf-multi-select-padding-inline |
0. (4px) and 0. (12px); 0. (2px) and 0. (8px) with multi-select-sm, 0. (4px) and 1rem (16px) with multi-select-lg |
--plf-multi-select-icon-size |
--plf-control-icon-size (16px); 14px with multi-select-sm, 18px with multi-select-lg |
--plf-multi-select-radius |
--plf-radius-lg, 0. (8px) |
--plf-multi-select-border |
--plf-color-input |
--plf-multi-select-hover-border |
--plf-multi-select-border, otherwise --plf-color-border |
--plf-multi-select-invalid-border |
--plf-color-danger |
--plf-multi-select-bg |
--plf-color-field |
--plf-multi-select-focus-width, --plf-multi-select-focus-color, --plf-multi-select-focus-offset |
The shared focus ring |
--plf-multi-select-chip-border, --plf-multi-select-chip-bg |
--plf-color-border-subtle, --plf-color-surface-muted |
--plf-multi-select-popup-width |
22rem (352px) |
--plf-multi-select-popup-max-height |
18rem (288px) |
--plf-multi-select-popup-bg, --plf-multi-select-popup-border |
--plf-color-surface-elevated, --plf-color-border-subtle |
--plf-multi-select-active-bg |
--plf-color-surface-muted |