Combobox
The WAI-ARIA 1.2 combobox pattern: a real <input role="combobox"> that keeps DOM focus the whole time you're typing, with aria-activedescendant pointing at whichever option in the role="listbox" popup is highlighted, the highlight moves, focus doesn't. popover="manual" is used deliberately here: a combobox needs its own open/close rules (open on focus or on typing, not just a single click), so outside-click and Escape dismissal are handled by hand rather than relying on popover="auto".
Usage
import { useState } from "react";
import { Combobox } from "@kernelui-lib/react";
const frameworks = [
{ value: "astro", label: "Astro" },
{ value: "next", label: "Next.js" },
{ value: "remix", label: "Remix" },
];
function FrameworkPicker() {
const [value, setValue] = useState("");
return (
<Combobox
label="Framework"
options={frameworks}
value={value}
onValueChange={setValue}
placeholder="Search frameworks…"
/>
);
}Playground
Toggle every adjustable prop and watch the component — and the code — update live.
Usage
<Combobox
label="Framework"
options={frameworks}
placeholder="Search frameworks…"
emptyMessage="No frameworks found."
/><kernel-combobox label="Framework" placeholder="Search frameworks…" empty-message="No frameworks found.">
<option value="astro">Astro</option>
<option value="next">Next.js</option>
<option value="remix">Remix</option>
<option value="sveltekit">SvelteKit</option>
<option value="nuxt">Nuxt</option>
</kernel-combobox>Grouping & custom rendering
Pass groups instead of a flat options array to section the listbox under labelled headers — filtering, keyboard navigation, and aria-activedescendant all operate across the flattened result set, so an item's position in its group doesn't change how Arrow Up/Down or Enter behave. renderOption replaces an option's default text label with anything you want (icons, a checkmark, secondary text), given the option, whether it's active or selected, and which group (if any) it belongs to.
Usage
import { Combobox } from "@kernelui-lib/react";
import type { ComboboxGroup, ComboboxOption } from "@kernelui-lib/react";
const groups: ComboboxGroup[] = [
{ id: "frontend", label: "Frontend", items: [
{ value: "astro", label: "Astro" },
{ value: "next", label: "Next.js" },
] },
{ id: "backend", label: "Backend", items: [
{ value: "express", label: "Express" },
] },
];
function renderOption(option: ComboboxOption, state: { selected: boolean }) {
return (
<span>
{option.label}
{state.selected ? " ✓" : null}
</span>
);
}
<Combobox label="Framework" groups={groups} renderOption={renderOption} />;Composable options
Use the compound API when each option needs product-owned markup, active-state styling, or grouped asynchronous results. The same input-focused keyboard and listbox behavior stays in Kernel.
Props
| Prop | Type | Default |
|---|---|---|
label | ReactNode (required) | — |
hideLabel | boolean | false |
labelOffset | boolean | true |
options | { value, label }[] | — |
groups | { id, label?, items: ComboboxOption[] }[] | — |
renderOption | (option, { active, selected, group?, index }) => ReactNode | — |
value / onValueChange | string / (value) => void | — |
defaultValue | string | "" |
placeholder | string | — |
emptyMessage | ReactNode | "No results" |
placement | "top" | "bottom" | "left" | "right" | "bottom" |
align | "start" | "center" | "end" | "center" |
offset | number (px) | 8 |
Provide either options or groups — when both are set, groups wins. Each group needs a unique id; a group without a label renders its items without a header.
Accessibility
- Grouped options are wrapped in
role="group", labelled viaaria-labelledbyby arole="presentation"header, so assistive tech announces which group an option belongs to.