Inputs

Combobox

A searchable dropdown that filters options as you type.

Installation

pnpm dlx @mielui/svelte add combobox

Usage

Bind value to the selected option. Single selection is the default. Clicking an option or pressing Enter updates value and calls onValueChange. Keyboard navigation skips disabled options. Changing an item label updates the selected label without replacing an active search.

Set name on Trigger to submit the selected value. Search text and labels are not submitted. Trigger renders an input by default, or a button with searchPlacement="menu". Its bind:element matches that element type.

An item callback runs when that option changes the selection. Selecting the current option again in single mode closes the menu without reporting a value change. Call preventDefault() in Trigger's onclick to cancel click activation. Input appearance can still open on focus or typing. Trigger's disabled prop disables both editing and its clear action.

Set type="multiple" on Root and bind a string array to value to choose several options. Selecting an item toggles it, clears the search, and keeps the menu open. The trigger lists selected labels; Escape or an outside click closes the menu. With name on Trigger, each selected value submits under that name.

<script lang="ts">
    import * as Combobox from '@mielui/svelte/components/combobox';

    let framework = $state('sveltekit');
</script>

<Combobox.Root bind:value={framework}>
    <Combobox.Trigger placeholder="Select a framework" />
    <Combobox.Content>
        <Combobox.Results>
            <Combobox.Item value="nextjs" label="Next.js" />
            <Combobox.Item value="sveltekit" label="SvelteKit" />
        </Combobox.Results>
    </Combobox.Content>
</Combobox.Root>

Examples

Basic usage

Multiple selection

Search for teams and toggle each selection without reopening the menu.

Scrollable

Long result lists stay in a height-capped menu and scroll inside it.

Glass surface

Set surface="glass" on Content for a translucent panel with blur, or surface="solid" for an opaque one. Without surface, the panel follows the theme. Glass falls back to an opaque panel when backdrop filtering is unavailable or reduced transparency is on.

Custom item content

Item renders its label by default. Pass children to show an icon or a second line instead. The label is still required: search matches against it and the trigger shows it once the item is selected.

<Combobox.Item value="fr" label="France">
  <span>France</span>
  <span class="text-foreground-muted">Europe</span>
</Combobox.Item>

Labels

Built-in text is English. Pass a ComboboxLabels object to labels on Root to translate or reword the menu search placeholder and name, the clear button, the option list name and the empty message. Every key is optional, and an omitted key keeps its English default. The trigger placeholder is a prop on Trigger.

<script lang="ts">
    import * as Combobox from '@mielui/svelte/components/combobox';
    import type { ComboboxLabels } from '@mielui/svelte/components/combobox';

    const labels: ComboboxLabels = {
        searchPlaceholder: 'Rechercher…',
        search: 'Rechercher un pays',
        clear: 'Effacer',
        options: 'Pays',
        empty: 'Aucun résultat'
    };
</script>

<Combobox.Root {labels}>
    <Combobox.Trigger placeholder="Choisir un pays" searchPlacement="menu" />
    <Combobox.Content>
        <Combobox.Results>
            <Combobox.Item value="fr" label="France" />
            <Combobox.Item value="be" label="Belgique" />
        </Combobox.Results>
    </Combobox.Content>
</Combobox.Root>

Matching typed text

The combobox filters with fuzzy matching, so a small typo still finds the option. threshold on Trigger sets how forgiving it is, from 0 for an exact match to 1 for nearly anything. The default is 0.28. Lower it for lists of codes or ids where a near miss is a wrong answer.

Opening and placement

placement on Root picks the side and alignment of the list, such as top-start. It defaults to bottom and flips when there is no room. onOpenChange runs when the person opens or closes the list, and onopen on Trigger runs just before it opens, which is the place to fetch options.

hoverable opens the list when the pointer rests on the trigger, with delay and closeDelay in milliseconds. inert is on by default and makes the rest of the page inert while a click-opened list is showing.

Panel and items

Content shares its panel with Popover. allowClickOutside={false} keeps the list open on an outside press, and dismissLayer={false} removes the invisible layer that catches the first outside press, which the input-style trigger relies on to stay typeable. lockScroll decides whether page scrolling is held while it is open; Tab is never trapped, so it moves on from the list. portal={false} renders the list in place, refElement anchors it to another element, and role and tabindex set the panel's ARIA role and tab index. surfaceClass styles the inner surface while class styles the frame.

Item is built on Button and always uses the menu row style, so Button's variant, size, and unstyled do not apply. A row is always a button and does not take href. status or loading show progress on a row, with labels setting the text for each state, and onkeydown runs before the list handles the key. Trigger takes Button's size, style, and unstyled.

API reference

Combobox.Root

Provides combobox state.

  • children
    Snippet<[]>

    Content rendered inside.

  • open Bindable
    boolean

    Whether it is open. Bind it to control the state from outside.

    Default false

  • onOpenChange
    ((open: boolean) => void)

    Called with the new state whenever it opens or closes.

  • placement
    Placement

    Preferred side and alignment relative to the trigger, such as bottom-start. It flips when there is no room.

    Default 'bottom'

  • hoverable
    boolean

    Opens on hover as well as on click.

    Default false

  • delay
    number

    Milliseconds the pointer rests on the trigger before it opens.

    Default 0

  • closeDelay
    number

    Milliseconds after the pointer leaves before it closes.

    Default 150

  • inert
    boolean

    Make document content outside an open non-hover popover inert. Defaults to true.

    Default true

  • labels
    ComboboxLabels

    Overrides the built-in text and accessible names. Every key is optional and English is the fallback.

  • type
    "single" | "multiple"

    single keeps one value. multiple allows several and makes value an array.

  • value Bindable
    string | string[]

    Selected value, or an array in multiple mode. Bindable.

    Default undefined

  • onValueChange
    ((value: string) => void) | ((value: string[]) => void)

    Called with the new value when it changes.

Combobox.Content

Contains combobox controls.

  • children Required
    Snippet<[]>

    Content rendered inside.

  • class
    (string & (ClassValue | null))

    Classes added to the element.

  • tabindex
    number

    Tab index of the panel.

    Default -1

  • role
    "dialog" | "alertdialog" | "menu" | "listbox" | "none"

    ARIA role of the panel.

    Default 'none'

  • surface
    "solid" | "glass"

    Surface treatment. Omit it to inherit --mielui-surface from the theme.

  • surfaceClass
    string

    Classes for the inset surface (where children live) — padding, layout, background overrides. The class prop styles the outer Panel frame.

  • allowClickOutside
    boolean

    Closes when the pointer is pressed outside it. Set false to keep it open until it is dismissed another way.

    Default true

  • dismissLayer
    boolean

    Render the full-viewport dismiss layer under the panel while open. Defaults to true. Set to false for triggers that must stay clickable while open (e.g. an input-style combobox trigger); outside pointer dismissal still applies via allowClickOutside.

    Default true

  • portal
    boolean

    Moves the panel to the end of the document so ancestors cannot clip it. Set false to render it in place.

    Default true

  • refElement
    VirtualElement

    Positions the panel against this element or virtual element instead of the trigger. The panel then opens to the right of it, as a submenu does.

  • lockScroll
    boolean

    Lock document scrolling while the panel is open. Defaults to true.

    Default true

Combobox.Item

Defines a selectable item.

  • value Required
    string | (string & string[])

    Value this option selects.

  • label Required
    string

    Text used for search and for the trigger, when it differs from the option's content.

  • callback
    (() => void)

    Called when the item is chosen with the pointer or the keyboard.

  • children
    Snippet<[]>

    Content rendered inside.

  • onkeydown
    ((event: KeyboardEvent) => void)

    Called on key down, before the button handles the key itself.

  • onclick
    ((event: MouseEvent) => void)

    Called when the button is activated. Loading and disabled buttons do not call it.

  • status
    ButtonStatus

    Controlled visual state. Loading remains focusable and refuses activation.

  • disabled
    boolean

    Prevents activation. A disabled link loses its destination and is skipped by Tab.

    Default false

  • element Bindable
    HTMLButtonElement | HTMLAnchorElement

    Bindable reference to the rendered DOM element. Type is the union of the two possible element types -- narrow at the use site:

  • loading
    boolean

    Convenience alias for status="loading".

  • labels
    ButtonLabels

    Overrides the built-in text and accessible names. Every key is optional and English is the fallback.

Combobox.Label

Labels the combobox.

  • children Required
    Snippet<[]>

    Content rendered inside.

  • class
    string

    Classes added to the element.

Combobox.Results

Lists matching items.

  • children
    Snippet<[]>

    Content rendered inside.

Combobox.Trigger

Opens the combobox.

  • class
    string

    Classes added to the element.

  • style
    string

    Inline styles for the element.

  • onclick
    ((event: MouseEvent) => void)

    Called when it is clicked or activated.

  • variant
    ButtonVariant

    Button style of the trigger.

    Default 'outline'

  • size
    "sm" | "md" | "lg" | "icon"

    Control height of the trigger.

    Default 'md'

  • unstyled
    boolean

    Forwarded to Button: render with class alone, skipping variant/size.

  • icon
    boolean

    Shows the chevron after the label.

  • onopen
    (() => void)

    Called immediately before this trigger opens its popover.

  • aria-labelledby
    string

    Id of the element that names the control, such as a visible label.

  • trailing
    Snippet<[]>

    Content at the end of the trigger.

  • placeholder
    string

    Text shown while there is no value.

    Default 'Select…'

  • searchPlacement
    "trigger" | "menu"

    trigger types into the trigger. menu puts a search field inside the menu.

    Default 'trigger'

  • threshold
    number

    How loosely typed text may match, from 0 for exact to 1 for anything.

    Default 0.28

  • appearance
    "button" | "input"

    button looks like a select. input looks like a text field.

    Default 'button'

  • element Bindable
    HTMLButtonElement | HTMLInputElement

    Bindable reference to the DOM element.