Combobox

A keyboard-accessible autocomplete combobox built on Base UI: type-ahead filtering, async options, and full ARIA, styled entirely with CSS classes.

Real use · searchable state picker

Docs search (no clear button)

Sizes

Using Combobox

Silica ships one design system through three paths. They share a class vocabulary but are not interchangeable — pick the one that matches what you are building.

Plain HTML — CSS classes

The Tailwind v4 plugin. Works in any framework or none — Rails, Django, PHP, Go, a static file. This path ships no JavaScript, so Combobox is styled but inert: its behavior comes from combobox. For that behavior with no framework, use the node-tree path below.

/* in your CSS */
@import "tailwindcss";
@plugin "@wizeworks/silicaui";

Root class combobox-clear, with 5 modifiers:

  • .combobox-clear
  • .combobox-control
  • .combobox-empty
  • .combobox-input
  • .combobox-item
  • .combobox-trigger

React

Typed components over those same classes, with behavior and accessibility from Base UI.

npm i @wizeworks/silicaui-react
import { Combobox } from "@wizeworks/silicaui-react";

Props

ComboboxItemProps
PropTypeRequiredDescription
valueunknownyes—
childrenReact.ReactNodeno—
indicatorbooleannoShow the leading selected-check (default true).
ComboboxProps
PropTypeRequiredDescription
itemsreadonly unknown[]yesThe full option set (strings or { value, label }); Base UI filters it.
valueunknownnoControlled selected value.
defaultValueunknownnoUncontrolled initial value.
onValueChange(value: unknown, eventDetails?: unknown) => voidnoFires with the newly-selected value.
namestringno—
disabledbooleanno—
requiredbooleanno—
placeholderstringno—
emptyMessageReact.ReactNodenoShown in the popup when nothing matches the query.
colorComboboxColornoAccent for the input border + focus ring (shares Input colors).
sizeComboboxSizenoInput height; matches same-size Inputs.
clearablebooleannoShow the clear (×) button (default true).
sideComboboxSideno—
alignComboboxAlignno—
sideOffsetnumberno—
classNamestringnoClass for the text input.
popupClassNamestringnoClass for the popup surface.
popupPropsReact.ComponentProps<typeof BaseCombobox.Popup> & { "data-theme"?: string; }noExtra props spread onto the popup surface — a portaled popup renders at document.body, OUTSIDE any [data-theme] island it was opened from, so a host with a scoped theme (e.g. a builder chrome) should pass popupProps={{ "data-theme": "…" }} to re-establish the theme tokens on the popup's own root (mirrors Select's popupProps).
itemToStringLabel(item: unknown) => stringnoFor object items, map an item to its display string (default: .label).
renderItem(item: unknown, index: number) => React.ReactNodenoOverride how each filtered item renders.
filter(item: unknown, query: string) => booleannoDecide whether an item matches what was typed. By default an item matches when its label contains the query.
"aria-label"stringno—
"aria-labelledby"stringno—
idstringno—
anchorBasePositionerProps["anchor"]noWhat to position against. Defaults to the component's own trigger.
positionMethodBasePositionerProps["positionMethod"]noabsolute (default) or fixed. Use fixed to escape a clipping ancestor.
alignOffsetBasePositionerProps["alignOffset"]noOffset along the alignment axis, in px. Pairs with align.
collisionBoundaryBasePositionerProps["collisionBoundary"]noThe element the popup must stay inside. Defaults to the viewport — set it to a scroll container so the popup collides with the panel, not the window.
collisionPaddingBasePositionerProps["collisionPadding"]noInset from the collision boundary, in px.
collisionAvoidanceBasePositionerProps["collisionAvoidance"]noWhich collision strategy runs per axis (flip, shift, none).
stickyBasePositionerProps["sticky"]noKeep the popup glued to the anchor while it scrolls out of view.
arrowPaddingBasePositionerProps["arrowPadding"]noMinimum distance from the popup's corner to the arrow, in px.
disableAnchorTrackingBasePositionerProps["disableAnchorTracking"]noStop re-measuring when the anchor moves. Cheaper, but goes stale.

Source of the demo above

The exact file that renders the examples above, so it can never drift from them. It imports a few of this site’s own layout helpers (../lib/…), so read it for the component calls rather than copying it whole.

Show source
import { useState } from "react";
import { Combobox } from "@wizeworks/silicaui-react";
import { Section, Row } from "../lib/Section";

const US_STATES = [
    "Alabama", "Alaska", "Arizona", "Arkansas", "California", "Colorado",
    "Connecticut", "Delaware", "Florida", "Georgia", "Hawaii", "Idaho",
    "Illinois", "Indiana", "Iowa", "Kansas", "Kentucky", "Louisiana",
];

const DOC_SUGGESTIONS = [
    "Getting started", "Installation", "Theming", "Color tokens",
    "Dark mode", "Components", "Button", "Select", "Combobox",
    "Form validation", "Accessibility", "Migration guide",
];

export function ComboboxDemo() {
    const [usState, setUsState] = useState<string | null>(null);
    const [docQuery, setDocQuery] = useState("");

    return (
        <>
            <Section title="Real use · searchable state picker">
                <Combobox
                    items={US_STATES}
                    value={usState}
                    onValueChange={(v) => setUsState(v as string | null)}
                    placeholder="Search states…"
                    color="primary"
                />
            </Section>

            <Section title="Docs search (no clear button)">
                <Combobox
                    items={DOC_SUGGESTIONS}
                    value={docQuery || null}
                    onValueChange={(v) => setDocQuery((v as string) ?? "")}
                    placeholder="Search docs…"
                    clearable={false}
                />
            </Section>

            <Section title="Sizes">
                <Row>
                    {(["sm", "md", "lg"] as const).map((size) => (
                        <Combobox key={size} items={["A", "B", "C"]} size={size} placeholder={size} />
                    ))}
                </Row>
            </Section>
        </>
    );
}

Vanilla — node tree to HTML

combobox — Combobox/Autocomplete/MultiSelect are all SINGLE, self- contained React components with an items prop (like Select, not a Root/Trigger/Content compound tree), so their vanilla macros follow the same shape: build the whole input+popup+options structure from props.items (unauthored structural sugar, same precedent as Carousel's invented track/dots). Build a node tree, project it with toHtml(), and let the zero-dependency runtime hydrate it. This is the path for generated or user-authored documents — a site builder, CMS output, a static export.

npm i @wizeworks/silicaui-html @wizeworks/silicaui-behaviors
import { atom, toHtml } from "@wizeworks/silicaui-html";

const node = atom("Combobox", "combobox-clear", { /* props */ });
const markup = toHtml(node);

Styling on this path is the class string in the second argument — the same classes as the Plain HTML section above. There is no color prop here; write "combobox-clear combobox-clear-primary" rather than passing a colour as a prop.

Takes children
no
Hydrated by
combobox

Load @wizeworks/silicaui-behaviors on the page or this markup renders correctly and does nothing.

The full API for Combobox, on every path, is available through the Silica MCP server.