Combobox

Combobox root with role="combobox", aria-expanded, and aria-haspopup="listbox". Pair with listbox/option resolve helpers (or your own list UI) for option picking.

PreviewCombobox

Usage

tsx
import { useState } from "react";
import { Combobox } from "@sometic/react/selection";

export function Example() {
    const [open, setOpen] = useState(false);
    const [value, setValue] = useState(null);
    return (
        <Combobox open={open} onOpenChange={setOpen} value={value} onValueChange={setValue}>
            {value ?? "Pick a framework"}
        </Combobox>
    );
}
tsx
import { useState } from "react";
import { Combobox } from "@sometic/react/selection";

export function Example(): JSX.Element {
    const [open, setOpen] = useState(false);
    const [value, setValue] = useState<string | null>(null);
    return (
        <Combobox open={open} onOpenChange={setOpen} value={value} onValueChange={setValue}>
            {value ?? "Pick a framework"}
        </Combobox>
    );
}
js
import {
    createComboboxController,
    resolveCombobox,
    resolveComboboxList,
    resolveComboboxOption,
} from "@sometic/dom/combobox";

const root = document.querySelector("#combobox");
const list = document.querySelector("#combobox-list");

const controller = createComboboxController({
    defaultOpen: false,
    defaultValue: null,
    onOpenChange: (open) => {
        const view = resolveCombobox({ open });
        for (const [key, value] of Object.entries(view.attributes)) {
            root.setAttribute(key, value);
        }
        list.hidden = !open;
    },
    onValueChange: (value) => {
        root.dataset.value = value ?? "";
        root.textContent = value ?? "Pick a framework";
    },
});

root.addEventListener("click", () => {
    controller.setOpen(!controller.open.get());
});

const listView = resolveComboboxList({ open: true });
for (const [key, value] of Object.entries(listView.attributes)) {
    list.setAttribute(key, value);
}

for (const option of list.querySelectorAll("[data-option]")) {
    const view = resolveComboboxOption({
        value: option.dataset.option,
        selected: false,
    });
    for (const [key, value] of Object.entries(view.attributes)) {
        option.setAttribute(key, value);
    }
    option.addEventListener("click", () => {
        controller.setValue(option.dataset.option);
        controller.setOpen(false);
    });
}

Custom element not shipped in this beta; use the DOM controller.

Custom element not shipped for Combobox (sometic-select exists for native select, not combobox). Vanilla uses @sometic/dom/combobox. React + DOM are primary; Vue has no Combobox component (Checkbox/Switch/Radio/Select only).

How it works

  1. Resolve: resolveCombobox, resolveComboboxList, resolveComboboxOption.
  2. Controller (createComboboxController): controllable value, open, and inputValue for filter UIs.
  3. React root: manages open/value props and emits combobox attributes; compose the list yourself or via DOM helpers. Click on the root toggles open when not disabled.

Anatomy

PartRole / notes
Rootrole="combobox", aria-expanded
Listrole="listbox" (compose yourself)
Optionrole="option", aria-selected

Props / attributes

React ComboboxProps

Extends HTMLAttributes<HTMLDivElement>. Remaining native div attrs are forwarded to the root (including your onClick, which runs before the built-in open toggle).

PropTypeDefaultDescription
openbooleanControlled open
defaultOpenbooleanfalseUncontrolled open
onOpenChange(open: boolean) => voidOpen changes
valuestring | nullControlled value
defaultValuestring | nullnullUncontrolled value
onValueChange(value: string | null) => voidValue changes
disabledbooleanDisables interaction
childrenReactNodeTrigger / label UI
Native attrsremaining div HTML attrsForwarded to the root

React also reflects data-value from the current value when set.

Vue

No Vue Combobox component. Use React or @sometic/dom/combobox.

Custom element

CE not shipped. Use Vanilla DOM controller or React. For a native <select>, see Select (sometic-select).

Events / callbacks

SurfaceEventPayload
ReactonOpenChangeboolean
ReactonValueChangestring | null
Vue
Custom element
DOM controlleronOpenChangeboolean
DOM controlleronValueChangestring | null

Controlled vs uncontrolled

Open and value are independently controllable. You may control one and leave the other uncontrolled.

Accessibility

  • Root uses combobox + expanded state.
  • List should use role="listbox"; options role="option" with aria-selected.
  • For typeahead, drive inputValue from createComboboxController in vanilla/DOM compositions.

Styling

Target [role="combobox"], [aria-expanded], [role="listbox"], [role="option"], [data-value].

When to use / When not

Use for searchable or custom option picking beyond native <select>.

Do not use when a native Select is enough.

FAQ

Full listbox UI in React? The React export is the combobox root. Compose list/options with DOM resolve helpers or your own markup.

Is there an sometic-combobox? No. CE not shipped.

Vue adapter? Not shipped. React + DOM primary.

Does React forward native attrs? Yes, onto the root div.

Click toggles open? Yes, unless disabled.