MultiSelect
Searchable multi-choice picker. Matches the Combobox / Select item-slot model and provides built-in Clear All / Select All footer controls.
Default
A plain picker — button trigger opens a popover with a search input, option list, and default footer.
Item Prefix
Use #item-prefix to render avatars, icons, or indicators next to each option label.
Grouped Options
Options can be split into named groups. Group labels render above each group's items.
Custom Footer
Replace the default Clear All / Select All footer with a custom one. The slot receives clearAll, selectAll, selectedOptions, and query.
Custom Trigger
Use #trigger to fully replace the default button trigger. The slot receives open, disabled, selectedOptions, displayValue, clearAll, and toggleOpen.
Tags Trigger
A chips-style trigger: each selected option renders as a removable Badge, with inline remove buttons. Authored through #trigger using selectedOptions and the parent's v-model.
Label, Description, Error
MultiSelect supports label, description, error, and required directly — no FormControl wrapper needed. The error suppresses the description and wires aria-invalid + aria-errormessage onto the trigger.
API Reference
Show types
import type { Component, VNode, VNodeChild } from 'vue'
import type { InputLabelingProps } from '../../composables/useInputLabeling'
export type MultiSelectVariant = 'subtle' | 'outline' | 'ghost'
export type MultiSelectSize = 'sm' | 'md' | 'lg' | 'xl'
export type PopoverSide = 'top' | 'right' | 'bottom' | 'left'
export type PopoverAlign = 'start' | 'center' | 'end'
export type MultiSelectSlotFn<TProps> = (props: TProps) => VNodeChild
export interface MultiSelectItemSlots<TProps> {
/** Replaces the prefix region of the standard row shell. */
prefix?: MultiSelectSlotFn<TProps>
/** Replaces the label region of the standard row shell. */
label?: MultiSelectSlotFn<TProps>
/** Replaces the suffix region of the standard row shell. */
suffix?: MultiSelectSlotFn<TProps>
/** Replaces the entire row; mutually exclusive with `prefix` / `label` / `suffix`. */
item?: MultiSelectSlotFn<TProps>
}
export interface MultiSelectOption {
label: string
value: string
icon?: string | Component
description?: string
disabled?: boolean
slot?: string
/** Per-item inline slot implementations for the row shell. */
slots?: MultiSelectItemSlots<MultiSelectItemSlotProps>
/** @deprecated use `slot` */
slotName?: string
/** @deprecated use `slots` — function form maps to `slots.item`, object form to `slots` */
render?:
| (() => VNode | VNode[])
| MultiSelectItemSlots<MultiSelectItemSlotProps>
[key: string]: any
}
export interface MultiSelectGroupedOption {
key?: string | number
group: string
hideLabel?: boolean
options: MultiSelectOption[]
}
export type MultiSelectOptions = Array<
MultiSelectOption | MultiSelectGroupedOption
>
export interface MultiSelectProps extends InputLabelingProps {
/** Array of selected option values. */
modelValue?: string[]
/** Options rendered in the popover. */
options?: MultiSelectOptions
/** Visual style of the trigger. */
variant?: MultiSelectVariant
/** Size of the trigger and option rows. */
size?: MultiSelectSize
/** Placeholder text shown when no value is selected. */
placeholder?: string
/** Disables the multi-select. */
disabled?: boolean
/** Controls the popover visibility. */
open?: boolean
/** Hides the in-popover search input. */
hideSearch?: boolean
/** Replaces the results with a loading state. */
loading?: boolean
/** Fallback empty-state copy. */
emptyText?: string
/** Preferred popover side. */
side?: PopoverSide
/** Preferred popover alignment. */
align?: PopoverAlign
/** Gap between trigger and content. */
offset?: number
/** Teleport target for the popover content. */
portalTo?: string | HTMLElement
/**
* Custom equality function used to resolve which options are currently
* selected for display and rendering. When omitted, the component uses
* strict equality on `option.value` against entries in `modelValue`.
*/
compareFn?: (a: MultiSelectOption, b: MultiSelectOption) => boolean
}
export interface MultiSelectTriggerSlotProps {
/** Whether the popover is open. */
open: boolean
/** Whether the multi-select is disabled. */
disabled: boolean
/** Resolved option objects for the selected values, in `modelValue` order. */
selectedOptions: MultiSelectOption[]
/** Comma-joined labels of the selected options, or `''` when nothing is selected. */
displayValue: string
/** Clears all selected values. */
clearAll: () => void
/** Toggles the popover open state. */
toggleOpen: () => void
}
export interface MultiSelectItemSlotProps {
/** Item currently being rendered. */
item: MultiSelectOption
/** Current search query — empty when the user hasn't typed since opening. */
query: string
/** Whether the item is in `modelValue`. */
selected: boolean
}
export interface MultiSelectGroupLabelSlotProps {
/** Group currently being rendered. */
group: MultiSelectGroupedOption
}
export interface MultiSelectEmptySlotProps {
/** Current search query — empty when the user hasn't typed since opening. */
query: string
}
export interface MultiSelectFooterSlotProps {
/** Clears all selected values. */
clearAll: () => void
/** Selects every enabled option across all groups. */
selectAll: () => void
/** Resolved option objects for the selected values, in `modelValue` order. */
selectedOptions: MultiSelectOption[]
/** Current search query — empty when the user hasn't typed since opening. */
query: string
}
export interface MultiSelectSlots {
/** Fully custom trigger renderer. */
trigger?: (props: MultiSelectTriggerSlotProps) => any
/** Overrides the rendered label content. Receives `{ required }`. */
label?: (props: { required: boolean }) => any
/** Overrides the rendered description content. */
description?: () => any
/** Shared content rendered before the standard row label. */
'item-prefix'?: (props: MultiSelectItemSlotProps) => any
/** Shared content rendered for the standard row label area. */
'item-label'?: (props: MultiSelectItemSlotProps) => any
/** Shared content rendered after the standard row label area. */
'item-suffix'?: (props: MultiSelectItemSlotProps) => any
/** Replaces the entire row. */
item?: (props: MultiSelectItemSlotProps) => any
/** Custom renderer for group labels. */
'group-label'?: (props: MultiSelectGroupLabelSlotProps) => any
/** Fallback content rendered when there are no results. */
empty?: (props: MultiSelectEmptySlotProps) => any
/** Replaces the default Clear All / Select All footer. */
footer?: (props: MultiSelectFooterSlotProps) => any
/** @deprecated compatibility alias for `#item-label`. */
option?: (props: { item: MultiSelectOption }) => any
[slotName: string]: ((props: any) => any) | undefined
}
export interface MultiSelectEmits {
/** Fired when the selection changes. */
'update:modelValue': [value: string[]]
/** Fired when the open state changes. */
'update:open': [value: boolean]
/** Fired when the user types in the search input. */
'update:query': [value: string]
}| Prop | Default | Type |
|---|---|---|
modelValue | [] | string[] Array of selected option values. |
options | [] | MultiSelectOptions Options rendered in the popover. |
variant | "subtle" | MultiSelectVariant Visual style of the trigger. |
size | "sm" | MultiSelectSize Size of the trigger and option rows. |
placeholder | "Select option" | string Placeholder text shown when no value is selected. |
disabled | false | boolean Disables the multi-select. |
open | false | boolean Controls the popover visibility. |
hideSearch | false | boolean Hides the in-popover search input. |
loading | false | boolean Replaces the results with a loading state. |
emptyText | "No results" | string Fallback empty-state copy. |
side | "bottom" | PopoverSide Preferred popover side. |
align | "start" | PopoverAlign Preferred popover alignment. |
offset | 4 | number Gap between trigger and content. |
portalTo | "body" | string | HTMLElement Teleport target for the popover content. |
compareFn | — | ((a: MultiSelectOption, b: MultiSelectOption) => boolean) Custom equality function used to resolve which options are currently selected for display and rendering. When omitted, the component uses strict equality on `option.value` against entries in `modelValue`. |
label | — | string Label rendered above (or beside, for binary controls) the input. |
description | — | string Helper text rendered below the input. Hidden when `error` is set. |
error | — | string | FrappeUIError Error message rendered below the input. When set, the control receives `aria-invalid="true"` and `data-state="invalid"`. May be either a string or an `Error` object whose `messages?: string[]` is rendered as stacked lines (with `Error.message` as the fallback). |
required | — | boolean Marks the field as required. Renders an asterisk next to the label and forwards `required` / `aria-required` to the underlying control. |
id | — | string HTML id of the underlying control. Auto-generated via `useId()` if omitted. |
| Slot | Payload |
|---|---|
trigger | MultiSelectTriggerSlotProps Fully custom trigger renderer. |
label | { required: boolean; } Overrides the rendered label content. Receives `{ required }`. |
description | — Overrides the rendered description content. |
item-prefix | MultiSelectItemSlotProps Shared content rendered before the standard row label. |
item-label | MultiSelectItemSlotProps Shared content rendered for the standard row label area. |
item-suffix | MultiSelectItemSlotProps Shared content rendered after the standard row label area. |
item | MultiSelectItemSlotProps Replaces the entire row. |
group-label | MultiSelectGroupLabelSlotProps Custom renderer for group labels. |
empty | MultiSelectEmptySlotProps Fallback content rendered when there are no results. |
footer | MultiSelectFooterSlotProps Replaces the default Clear All / Select All footer. |
option | Deprecated — compatibility alias for `#item-label`. |
| Event | Payload |
|---|---|
update:modelValue | unknown[] Fired when the model value changes. |
update:query | [value: string] Fired when the query changes. |
update:open | unknown[] Fired when the open state changes. |