Combobox
Presents a selection of choices to the user, filtered by a text input.
Props
Features
- 🎹 Keyboard navigation
- 🍃 Multi-selection mode
- 🧠 Smart focus management
- 💬 Flexible filtering
Usage
<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 for={combobox.ids.input}>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
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
Constructor Props
The props that are passed when calling
new Combobox()
new Combobox()
export type ComboboxProps<T extends string, 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 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 extends string, 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 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,onSelect?: (() => void) | undefined,) => {readonly id: stringreadonly "data-melt-combobox-option": ""readonly "data-value": DataReturn<T>readonly "aria-hidden": true | undefinedreadonly "aria-selected": booleanreadonly "data-highlighted": "" | undefinedreadonly role: "option"readonly onmouseover: () => voidreadonly onclick: () => void}(value: T,onSelect?: (() => void) | undefined,) => {readonly id: stringreadonly "data-melt-combobox-option": ""readonly "data-value": DataReturn<T>readonly "aria-hidden": true | undefinedreadonly "aria-selected": booleanreadonly "data-highlighted": "" | undefinedreadonly role: "option"readonly onmouseover: () => voidreadonly onclick: () => void}Gets the attributes for the option element. @param value The value of the option. @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 -
inputValue
stringstring -
touched
booleanboolean -
onSelectMap
Map<T, () => void>Map<T, () => void> -
ids
{trigger: stringcontent: stringoption: stringinput: string} & { invoker: string; popover: string }{trigger: stringcontent: stringoption: stringinput: string} & { invoker: string; popover: string } -
isSelected
(value: T) => boolean(value: T) => boolean -
value
SelectionStateValue<T, Multiple>SelectionStateValue<T, Multiple> -
highlighted
T | nullT | null -
valueAsString
stringstring -
input
{readonly "data-melt-combobox-input": ""readonly id: stringreadonly role: "combobox"readonly "aria-expanded": booleanreadonly "aria-controls": stringreadonly "aria-owns": stringreadonly onclick: undefinedreadonly value: stringreadonly oninput: (e: Event) => voidreadonly onkeydown: (e: KeyboardEvent) => voidreadonly onfocusout: () => Promise<void>readonly popovertarget: string}{readonly "data-melt-combobox-input": ""readonly id: stringreadonly role: "combobox"readonly "aria-expanded": booleanreadonly "aria-controls": stringreadonly "aria-owns": stringreadonly onclick: undefinedreadonly value: stringreadonly oninput: (e: Event) => voidreadonly onkeydown: (e: KeyboardEvent) => voidreadonly onfocusout: () => Promise<void>readonly popovertarget: string} -
trigger
{onfocusout: () => Promise<void>"data-melt-combobox-trigger": stringid: stringonclick: () => void}{onfocusout: () => Promise<void>"data-melt-combobox-trigger": stringid: stringonclick: () => void} -
content
{readonly onfocusout: () => Promise<void>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 onfocusout: () => Promise<void>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}