Combobox
Presents a selection of choices to the user, filtered by a text input.
Props
Features
Section titled “Features”- 🎹 Keyboard navigation
- 🍃 Multi-selection mode
- 🧠 Smart focus management
- 💬 Flexible filtering
<script lang="ts"> import { Combobox } from "melt/builders";
const options = [ /* ... */ ] as const; type Option = (typeof options)[number];
const combobox = new Combobox<Option>();
const filtered = $derived.by(() => { if (!combobox.touched) return options; return options.filter((o) => o.toLowerCase().includes(combobox.inputValue.trim().toLowerCase()), ); });</script>
<label {...combobox.label}>Favorite Character</label><input {...combobox.input} /><button {...combobox.trigger}>open</button>
<div {...combobox.content}> {#each filtered as option (option)} <div {...combobox.getOption(option)}> {option} {#if combobox.isSelected(option)} ✓ {/if} </div> {:else} <span>No results found</span> {/each}</div>
<script lang="ts"> import { Combobox } from "melt/components"; import { Combobox as ComboboxInstance } from "melt/builders";
const options = [ /* ... */ ] as const; type Option = (typeof options)[number];
function getFiltered(combobox: ComboboxInstance<string>) { if (!combobox.touched) return options; return options.filter((o) => o.toLowerCase().includes(combobox.inputValue.trim().toLowerCase()), ); };</script>
<Combobox> {#snippet children(combobox)} <label for={combobox.ids.input}>Favorite Character</label> <input {...combobox.input} /> <button {...combobox.trigger}>open</button>
<div {...combobox.content}> {#each getFiltered(combobox) as option (option)} <div {...combobox.getOption(option)}> {option} {#if combobox.isSelected(option)} ✓ {/if} </div> {:else} <span>No results found</span> {/each} </div> {/snippet}</Combobox>
Customizing floating elements
Section titled “Customizing floating elements”Floating elements use Floating UI under the hood. To this end, we expose a floatingConfig
option, which can be used to control the underlying computePosition function, its middlewares, and the resulting styling that is applied.
API Reference
Section titled “API Reference”Constructor Props
The props that are passed when calling
new Combobox()
new Combobox()
export type ComboboxProps<T, Multiple extends boolean = false> = Omit< PopoverProps, "closeOnEscape" | "closeOnOutsideClick" | "sameWidth"> & { /** * If `true`, multiple options can be selected at the same time. * * @default false */ multiple?: MaybeGetter<Multiple | undefined>;
/** * The value for the Combobox. * * When passing a getter, it will be used as source of truth, * meaning that the value only changes when the getter returns a new value. * * Otherwise, if passing a static value, it'll serve as the default value. * * * @default false */ value?: MaybeMultiple<T, Multiple>; /** * Called when the value is supposed to change. */ onValueChange?: OnMultipleChange<T, Multiple>;
/** * The inputValue for the Combobox. * * When passing a getter, it will be used as source of truth, * meaning that the value only changes when the getter returns a new value. * * Otherwise, if passing a static value, it'll serve as the default value. * * * @default false */ inputValue?: MaybeGetter<string | undefined>; /** * Called when the value is supposed to change. */ onInputValueChange?: Setter<string>;
/** * The currently highlighted value. */ highlighted?: MaybeGetter<T | null | undefined>;
/** * Called when the highlighted value changes. */ onHighlightChange?: (highlighted: T | null) => void;
/** * Determines behavior when scrolling items into view. * Set to null to disable auto-scrolling. * * @default "nearest" * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView#block */ scrollAlignment?: MaybeGetter<"nearest" | "center" | null | undefined>;
/** * If the content should have the same width as the trigger * * @default true */ sameWidth?: MaybeGetter<boolean | undefined>;};
export type ComboboxProps<T, Multiple extends boolean = false> = Omit< PopoverProps, "closeOnEscape" | "closeOnOutsideClick" | "sameWidth"> & { /** * If `true`, multiple options can be selected at the same time. * * @default false */ multiple?: MaybeGetter<Multiple | undefined>;
/** * The value for the Combobox. * * When passing a getter, it will be used as source of truth, * meaning that the value only changes when the getter returns a new value. * * Otherwise, if passing a static value, it'll serve as the default value. * * * @default false */ value?: MaybeMultiple<T, Multiple>; /** * Called when the value is supposed to change. */ onValueChange?: OnMultipleChange<T, Multiple>;
/** * The inputValue for the Combobox. * * When passing a getter, it will be used as source of truth, * meaning that the value only changes when the getter returns a new value. * * Otherwise, if passing a static value, it'll serve as the default value. * * * @default false */ inputValue?: MaybeGetter<string | undefined>; /** * Called when the value is supposed to change. */ onInputValueChange?: Setter<string>;
/** * The currently highlighted value. */ highlighted?: MaybeGetter<T | null | undefined>;
/** * Called when the highlighted value changes. */ onHighlightChange?: (highlighted: T | null) => void;
/** * Determines behavior when scrolling items into view. * Set to null to disable auto-scrolling. * * @default "nearest" * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView#block */ scrollAlignment?: MaybeGetter<"nearest" | "center" | null | undefined>;
/** * If the content should have the same width as the trigger * * @default true */ sameWidth?: MaybeGetter<boolean | undefined>;};
Methods
The methods returned from
new Combobox()
new Combobox()
-
select
(value: T) => void(value: T) => void -
scrollIntoView
(value?: T | undefined) => void(value?: T | undefined) => void -
getOptionId
(value: T) => string(value: T) => string -
getOption
(value: T,label?: string | undefined,onSelect?: (() => void) | undefined,) => {readonly id: stringreadonly "data-melt-combobox-option": ""readonly "data-value": stringreadonly "data-label": stringreadonly "aria-hidden": true | undefinedreadonly "aria-selected": booleanreadonly "data-highlighted": "" | undefinedreadonly tabindex: -1readonly role: "option"readonly onmouseover: () => voidreadonly onclick: () => void}(value: T,label?: string | undefined,onSelect?: (() => void) | undefined,) => {readonly id: stringreadonly "data-melt-combobox-option": ""readonly "data-value": stringreadonly "data-label": stringreadonly "aria-hidden": true | undefinedreadonly "aria-selected": booleanreadonly "data-highlighted": "" | undefinedreadonly tabindex: -1readonly role: "option"readonly onmouseover: () => voidreadonly onclick: () => void}Gets the attributes for the option element. @param value The value of the option. @param label The label to display for the option. If not provided, the value will be stringified. @param onSelect An optional callback to call when the option is selected, overriding the default behavior. @returns The attributes for the option element. -
getOptionsEls
() => HTMLElement[]() => HTMLElement[] -
getOptions
() => T[]() => T[] -
highlight
(value: T) => void(value: T) => void -
highlightNext
() => void() => void -
highlightPrev
() => void() => void -
highlightFirst
() => void() => void -
highlightLast
() => void() => void
Properties
The properties returned from
new Combobox()
new Combobox()
-
multiple
MultipleMultiple -
scrollAlignment
"nearest" | "center" | null"nearest" | "center" | null -
touched
booleanboolean -
onSelectMap
Map<T, () => void>Map<T, () => void> -
ids
{trigger: stringcontent: stringoption: stringinput: string} & { popover: string }{trigger: stringcontent: stringoption: stringinput: string} & { popover: string } -
isSelected
(value: T) => boolean(value: T) => boolean -
getOptionLabel
(value: T) => string(value: T) => string -
value
SelectionStateValue<T, Multiple>SelectionStateValue<T, Multiple> -
inputValue
stringstring -
highlighted
T | nullT | null -
label
{for: stringonclick: (e: MouseEvent & { currentTarget: EventTarget & HTMLLabelElement },) => void}{for: stringonclick: (e: MouseEvent & { currentTarget: EventTarget & HTMLLabelElement },) => void} -
input
{readonly "data-melt-combobox-input": ""readonly id: stringreadonly role: "combobox"readonly "aria-expanded": booleanreadonly "aria-controls": stringreadonly "aria-owns": stringreadonly onclick: () => voidreadonly value: stringreadonly oninput: (e: Event) => voidreadonly onkeydown: (e: KeyboardEvent) => voidreadonly onfocus: (event: FocusEvent) => voidreadonly onfocusout: (event: FocusEvent) => Promise<void>readonly style: `--melt-invoker-width: ${string}; --melt-invoker-height: ${string}; --melt-invoker-x: ${string}; --melt-invoker-y: ${string}; --melt-popover-available-width: ${string}; --melt-popover-available-height: ${string}`readonly popovertarget: string}{readonly "data-melt-combobox-input": ""readonly id: stringreadonly role: "combobox"readonly "aria-expanded": booleanreadonly "aria-controls": stringreadonly "aria-owns": stringreadonly onclick: () => voidreadonly value: stringreadonly oninput: (e: Event) => voidreadonly onkeydown: (e: KeyboardEvent) => voidreadonly onfocus: (event: FocusEvent) => voidreadonly onfocusout: (event: FocusEvent) => Promise<void>readonly style: `--melt-invoker-width: ${string}; --melt-invoker-height: ${string}; --melt-invoker-x: ${string}; --melt-invoker-y: ${string}; --melt-popover-available-width: ${string}; --melt-popover-available-height: ${string}`readonly popovertarget: string} -
trigger
{onfocus: (event: FocusEvent) => voidonfocusout: (event: FocusEvent) => Promise<void>style: `--melt-invoker-width: ${string}; --melt-invoker-height: ${string}; --melt-invoker-x: ${string}; --melt-invoker-y: ${string}; --melt-popover-available-width: ${string}; --melt-popover-available-height: ${string}`"data-melt-combobox-trigger": stringid: stringonclick: () => void}{onfocus: (event: FocusEvent) => voidonfocusout: (event: FocusEvent) => Promise<void>style: `--melt-invoker-width: ${string}; --melt-invoker-height: ${string}; --melt-invoker-x: ${string}; --melt-invoker-y: ${string}; --melt-popover-available-width: ${string}; --melt-popover-available-height: ${string}`"data-melt-combobox-trigger": stringid: stringonclick: () => void} -
content
{readonly onfocus: (event: FocusEvent) => voidreadonly onfocusout: (event: FocusEvent) => Promise<void>readonly style: `--melt-invoker-width: ${string}; --melt-invoker-height: ${string}; --melt-invoker-x: ${string}; --melt-invoker-y: ${string}; --melt-popover-available-width: ${string}; --melt-popover-available-height: ${string}`readonly id: stringreadonly popover: "manual"readonly ontoggle: (e: ToggleEvent & { currentTarget: EventTarget & HTMLElement },) => voidreadonly tabindex: -1readonly inert: booleanreadonly "data-open": "" | undefined} & {readonly "data-melt-combobox-content": ""readonly role: "listbox"readonly "aria-expanded": booleanreadonly "aria-activedescendant": string | undefined}{readonly onfocus: (event: FocusEvent) => voidreadonly onfocusout: (event: FocusEvent) => Promise<void>readonly style: `--melt-invoker-width: ${string}; --melt-invoker-height: ${string}; --melt-invoker-x: ${string}; --melt-invoker-y: ${string}; --melt-popover-available-width: ${string}; --melt-popover-available-height: ${string}`readonly id: stringreadonly popover: "manual"readonly ontoggle: (e: ToggleEvent & { currentTarget: EventTarget & HTMLElement },) => voidreadonly tabindex: -1readonly inert: booleanreadonly "data-open": "" | undefined} & {readonly "data-melt-combobox-content": ""readonly role: "listbox"readonly "aria-expanded": booleanreadonly "aria-activedescendant": string | undefined} -
valueAsString
stringstring