Skip to content

MultiSelect ​

A dropdown for choosing several options at once — with optional search and select-all. It binds an array of selected values with v-model, and its trigger shows a smart summary ("3 selected") instead of a long list.

MultiSelect is the most composable component here: the root provides state and exposes it through slot props, and you assemble the trigger, panel, search, and items to taste.

Anatomy ​

MultiSelect                  ← root: holds options + v-model, exposes slot props
├── MultiSelectTrigger       ← the field-like button showing the summary
└── MultiSelectContent       ← the floating panel
    ├── MultiSelectSearch    ← optional search input (needs `searchable`)
    └── MultiSelectItem      ← one per option (a checkbox row)

The root's default slot exposes everything the parts need: label (the trigger summary), empty, count, allSelected, visibleCount, toggleAll, and clear. You pass these into the parts as needed.

Usage ​

Provide an options array ({ value, label }) and bind selections with v-model. Render one MultiSelectItem per option.

BasicPick multiple options; the trigger summarizes.
<script setup lang="ts">
import { ref } from "vue";
import {
    MultiSelect,
    MultiSelectTrigger,
    MultiSelectContent,
    MultiSelectItem,
} from "@dlbcodes/ui";

const selected = ref<string[]>([]);
const options = [
    { value: "vue", label: "Vue" },
    { value: "react", label: "React" },
    { value: "svelte", label: "Svelte" },
    { value: "angular", label: "Angular" },
    { value: "solid", label: "Solid" },
];
</script>

<template>
    <div class="w-full max-w-xs">
        <MultiSelect
            v-model="selected"
            :options="options"
            v-slot="{ label, empty }"
        >
            <MultiSelectTrigger :label="label" :empty="empty" />
            <MultiSelectContent>
                <MultiSelectItem
                    v-for="o in options"
                    :key="o.value"
                    :value="o.value"
                    :label="o.label"
                />
            </MultiSelectContent>
        </MultiSelect>
        <p class="mt-2 text-sm text-text-secondary">
            Selected: {{ selected.join(", ") || "none" }}
        </p>
    </div>
</template>

Searchable with select-all ​

Add searchable and include a MultiSelectSearch to filter options. The slot's toggleAll/allSelected let you add a select-all control that operates on the currently visible (filtered) options.

SearchableFilter options and select all.
<script setup lang="ts">
import { ref } from "vue";
import {
    MultiSelect,
    MultiSelectTrigger,
    MultiSelectContent,
    MultiSelectItem,
    MultiSelectSearch,
} from "@dlbcodes/ui";

const selected = ref<string[]>(["us"]);
const options = [
    { value: "us", label: "United States" },
    { value: "uk", label: "United Kingdom" },
    { value: "pt", label: "Portugal" },
    { value: "br", label: "Brazil" },
    { value: "de", label: "Germany" },
    { value: "jp", label: "Japan" },
];
</script>

<template>
    <div class="w-full max-w-xs">
        <MultiSelect
            v-model="selected"
            :options="options"
            searchable
            placeholder="Select countries"
            v-slot="{ label, empty, allSelected, toggleAll }"
        >
            <MultiSelectTrigger :label="label" :empty="empty" />
            <MultiSelectContent>
                <MultiSelectSearch placeholder="Search countries…" />
                <button
                    type="button"
                    class="w-full px-3 py-1.5 text-left text-sm text-text-secondary hover:bg-bg-surface rounded-lg"
                    @click="toggleAll"
                >
                    {{ allSelected ? "Deselect all" : "Select all" }}
                </button>
                <MultiSelectItem
                    v-for="o in options"
                    :key="o.value"
                    :value="o.value"
                    :label="o.label"
                />
            </MultiSelectContent>
        </MultiSelect>
    </div>
</template>

Custom summary ​

By default the trigger shows "N selected". Pass a summarize function to customize the text when more than one option is selected.

Custom summaryA custom trigger summary.
<script setup lang="ts">
import { ref } from "vue";
import {
    MultiSelect,
    MultiSelectTrigger,
    MultiSelectContent,
    MultiSelectItem,
} from "@dlbcodes/ui";

const selected = ref<string[]>(["read", "write"]);
const options = [
    { value: "read", label: "Read" },
    { value: "write", label: "Write" },
    { value: "delete", label: "Delete" },
    { value: "admin", label: "Admin" },
];
const summarize = (count: number, total: number) =>
    `${count} of ${total} permissions`;
</script>

<template>
    <div class="w-full max-w-xs">
        <MultiSelect
            v-model="selected"
            :options="options"
            :summarize="summarize"
            v-slot="{ label, empty }"
        >
            <MultiSelectTrigger :label="label" :empty="empty" />
            <MultiSelectContent>
                <MultiSelectItem
                    v-for="o in options"
                    :key="o.value"
                    :value="o.value"
                    :label="o.label"
                />
            </MultiSelectContent>
        </MultiSelect>
    </div>
</template>

Props ​

MultiSelect ​

PropTypeDefaultDescription
modelValuestring[][]Selected values. Use with v-model.
options{ value: string; label: string }[]—The full option list. Required.
searchablebooleanfalseEnables filtering (include a MultiSelectSearch).
placeholderstring"Select options"Trigger text when nothing is selected.
placement"bottom-start" | "bottom-end" | "top-start" | "top-end""bottom-start"Panel placement.
offsetnumber4Gap between trigger and panel.
summarize(count, total) => string"N selected"Customizes the trigger text when multiple are selected.
classstring—Classes merged onto the root.

Slot props (from the root's default slot): label, count, empty, allSelected, visibleCount, toggleAll, clear.

MultiSelectTrigger ​

PropTypeDefaultDescription
labelstring—The summary text (pass the slot's label).
emptyboolean—Whether nothing is selected (greys the text).
variant"primary" | "contrast""primary"Field style (shares Input's variants).
size"sm" | "base" | "lg" | "xl""base"Field size (shares Input's sizes). Heights are 32, 40, 48 and 56px, the same as Button and Input.
classstring—Merged onto the trigger.

MultiSelectContent / MultiSelectSearch / MultiSelectItem ​

ComponentKey props
MultiSelectContentsize (panel width), class.
MultiSelectSearchplaceholder. Requires searchable on the root.
MultiSelectItemvalue, label, disabled. Renders a checkbox row; exposes selected via slot.

Accessibility ​

  • The trigger is built on Headless UI's Popover (focus, Escape, click-outside handled). Items are buttons with aria-pressed reflecting selection.
  • Each item shows a visual checkbox indicating its selected state.
  • When searchable, the search input filters the visible items.