Multi Select
Production-ready multi select with extensive token integration, state management, and semantic HTML.
Overview
The Multi Select component is a core building block of the Byteion design system, providing a robust, accessibility-compliant interface for its specific UI capabilities.
Why this exists
Selecting multiple values from a list is a common pattern for filters, tags, and assignments. A multi-select shows selected values as removable pills.
Mental model
A multi-select is a combobox that accumulates selections. Each selection becomes a removable tag pill inside the input.
When to use
When not to use
Basic example
The following example demonstrates standard usage, fully compliant with Byteion's token-first approach.
import { useState } from 'react';
import { MultiSelect } from '@byteion/react';
const skills = [
{ value: 'react', label: 'React' },
{ value: 'vue', label: 'Vue' },
{ value: 'svelte', label: 'Svelte' },
{ value: 'solid', label: 'SolidJS' },
];
export default function MultiSelectDemo() {
const [picked, setPicked] = useState<string[]>(['react', 'vue']);
return (
<div className="grid max-w-md gap-4 p-6">
<MultiSelect label="Skills" options={skills} value={picked} onChange={setPicked} />
<MultiSelect label="Empty" placeholder="Pick frameworks" options={skills} />
</div>
);
}
<script setup lang="ts">
import { ref } from 'vue';
import { MultiSelect } from '@byteion/vue';
const picked = ref<string[]>(['react', 'vue']);
const skills = [
{ value: 'react', label: 'React' },
{ value: 'vue', label: 'Vue' },
{ value: 'svelte', label: 'Svelte' },
{ value: 'solid', label: 'SolidJS' },
];
</script>
<template>
<div class="grid max-w-md gap-4 p-6">
<MultiSelect v-model="picked" label="Skills" :options="skills" />
<MultiSelect label="Empty" placeholder="Pick frameworks" :options="skills" />
</div>
</template>
Variants
Each variant communicates a different meaning. Choose based on priority, not aesthetics.
Single default variant.
Sizes
Size should match the importance and context of the component.
- md — md size.
States
This component reacts seamlessly to the following interaction and data states:
- default — Resting appearance before interaction.
- hover — Pointer is over the element — visual feedback via color or shadow.
- focus — Keyboard or tab focus — shows ring indicator for accessibility.
- active — Currently selected or in use.
- disabled — Non-interactive — grayed out, cursor not-allowed.
- loading — Action in progress — spinner replaces icon, button is disabled.
- invalid — Input value does not match expected format.
- error — Validation failed — red border and error message.
- success — Validation passed — green indicator.
Anatomy
Usage guidelines
- Show selected items as removable pills.
- Filter out already-selected items from the dropdown.
- Set a reasonable max selection limit.
Do and Don't
Do
- Show tags as removable pills.
- Filter out selected items.
- Set a max limit if needed.
Don't
- Do not allow duplicate selections.
- Do not hide what is already selected.
- Do not use for single-value fields.
Accessibility
Each tag pill is focusable and removable via Backspace or click.
Role:listbox combobox
ARIA Attributes
aria-expandedaria-multiselectablearia-selectedaria-activedescendant
Keyboard Navigation
- ArrowDown: Moves focus to next option
- ArrowUp: Moves focus to previous option
- Enter: Adds or removes the highlighted option
- Backspace: Removes the last selected item when input is empty
- Escape: Closes the listbox
Customization
Tag pills use badge tokens.
Compose with React cva()
Use the exportedcva() +cn() utilities to compose your own variant on top of the Byteion tokens.
import { cva, cn, type VariantProps } from '@byteion/react';
const myMultiSelect = cva('base classes…', {
variants: {
accent: { soft:'border-primary-200', loud:'border-primary-500' },
},
defaultVariants: { accent:'soft' },
});
type Props = React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof myMultiSelect>;
Compose with Vue cva()
Use the exportedcva() +cn() utilities inside standard Vue setup blocks.
<script setup lang="ts">
import { computed, useAttrs } from 'vue';
import { cva, type VariantProps } from '@byteion/vue';
interface Props { accent?:'soft' |'loud' }
const props = withDefaults(defineProps<Props>(), { accent:'soft' });
const attrs = useAttrs();
const myMultiSelect = cva('base classes…', {
variants: { accent: { soft:'border-primary-200', loud:'border-primary-500' } },
});
const cls = computed(() => myMultiSelect({ accent: props.accent, class: attrs.class as string | undefined }));
</script>
Runtime events
Byteion keeps framework APIs idiomatic: HTML uses DOM custom events, React uses callback props, and Vue uses emitted events / v-model updates.
HTML / JS events
bi:ms:addbi:ms:changebi:ms:closebi:ms:openbi:ms:removebi:multi-select:addbi:multi-select:changebi:multi-select:errorbi:multi-select:loadbi:multi-select:removebi:multi-select:search
React callbacks
onValueChange(value: string[]) => void
Vue emits
update:modelValue[v: string[]]
Remote / AJAX
Remote options
Add data-bi-source when option choices should be loaded as the user types. Selected values are kept in hidden inputs, so forms still submit progressively once options are chosen.
Attributes
data-bi-sourcedata-bi-search-paramdata-bi-min-charsdata-bi-debouncedata-bi-value-fielddata-bi-label-fielddata-bi-namedata-bi-loading-textdata-bi-empty-textdata-bi-error-text
Runtime events
bi:multi-select:searchbi:multi-select:loadbi:multi-select:errorbi:multi-select:addbi:multi-select:removebi:multi-select:change
Accessibility: loading sets aria-busy="true"; empty states use role="status"; error states use alert/status messaging where the runtime renders remote status rows.
API Reference
Requires JS module for tag management.
HTML Data Attributes
No custom data attributes required.
React Props · MultiSelectProps
Extracted directly fromreact/src/components/MultiSelect.tsx.
export interface MultiSelectProps {
options: MultiSelectOption[];
/** Controlled list of selected values. */
value?: string[];
defaultValue?: string[];
onValueChange?: (value: string[]) => void;
/** Visible label rendered above the field. */
label?: ReactNode;
helperText?: ReactNode;
error?: ReactNode;
/** Placeholder shown when nothing is selected. */
placeholder?: string;
/** Maximum number of selections. */
max?: number;
disabled?: boolean;
/** Optional id for the input element. */
id?: string;
className?: string;
}
Vue Props · MultiSelectProps
Extracted directly fromvue/src/components/MultiSelect.vue.
export interface MultiSelectProps {
modelValue?: string[];
defaultValue?: string[];
options: MultiSelectOption[];
placeholder?: string;
label?: string;
helperText?: string;
error?: string;
disabled?: boolean;
max?: number;
id?: string;
}
Developer tips
Common mistakes
Production usage tips
data-bi-* attributes instead of styling hooks.
Premium examples
Advanced patterns available in Byteion Pro:
Related components
Unlock Premium Patterns
Get access to copy-paste ready Pro blocks, SaaS templates, and advanced data-tables built with this component.
Explore Pro Components