Combobox
Combobox is a neobrutalist React component from Cruise UI. A filterable select: type to filter, grouped options, async loading and multi-select chips. Install it with npx shadcn@latest add @cruiseui/combobox.
Install
Terminal
npx shadcn@latest add @cruiseui/comboboxFirst time? Installation shows the one-off components.json setup (the @cruiseui registry) and how to turn the theme on.
Example
Corners
Try typing “lag” or “river”.
Lagos
Ibadan
"use client";
import { CruiseCombobox, type CruiseComboGroup } from "@/components/cruise/combobox";
const opt = (label: string) => ({ value: label.toLowerCase().replace(/\s+/g, "-"), label });
const states: CruiseComboGroup[] = [
{ label: "South South", items: ["Akwa Ibom", "Bayelsa", "Cross River", "Delta", "Edo", "Rivers"].map(opt) },
{ label: "South West", items: ["Ekiti", "Lagos", "Ogun", "Ondo", "Osun", "Oyo"].map(opt) },
];
const cities = ["Lagos", "Ibadan", "Abuja", "Port Harcourt", "Kano", "Enugu"].map(opt);
export default function ComboboxExample() {
return (
<div className="flex max-w-md flex-col gap-6">
<CruiseCombobox label="State" items={states} placeholder="Search states" hint="Try typing “lag” or “river”." />
<CruiseCombobox multiple label="Pick your cities" items={cities} defaultValue={[cities[0], cities[1]]} />
</div>
);
}
<div class="field combobox" data-combobox>
<label class="field-label" for="state-input">State</label>
<div class="combobox-sheet">
<div class="combobox-field">
<svg class="combobox-search" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="m20 20-4-4"/></svg>
<input id="state-input" class="combobox-input" role="combobox" aria-expanded="false" aria-controls="state-list" aria-autocomplete="list" autocomplete="off" placeholder="Search states">
<button type="button" class="combobox-btn" data-clear aria-label="Clear" hidden>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" aria-hidden="true"><path d="M18 6 6 18M6 6l12 12"/></svg></button>
<button type="button" class="combobox-btn" data-toggle aria-label="Show state options" tabindex="-1">
<svg class="menu-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg></button>
</div>
<div class="menu combobox-popup" hidden>
<div class="combobox-status" role="status"></div>
<div role="listbox" id="state-list" aria-label="States">
<div role="group" aria-labelledby="zone-ss">
<div class="menu-group-label" id="zone-ss">South South</div>
<div class="menu-item" role="option" id="state-delta" aria-selected="false"><svg class="menu-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg><span class="combobox-label">Delta</span></div>
<div class="menu-item" role="option" id="state-rivers" aria-selected="false"><svg class="menu-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg><span class="combobox-label">Rivers</span></div>
</div>
<div role="group" aria-labelledby="zone-sw">
<div class="menu-group-label" id="zone-sw">South West</div>
<div class="menu-item" role="option" id="state-lagos" aria-selected="false"><svg class="menu-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg><span class="combobox-label">Lagos</span></div>
<div class="menu-item" role="option" id="state-oyo" aria-selected="false"><svg class="menu-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg><span class="combobox-label">Oyo</span></div>
</div>
</div>
</div>
</div>
<div class="drawer-backdrop" hidden></div>
</div>
<!-- Multi-select: add data-multiple to .combobox; chips are added before the input.
Error: data-invalid on .combobox-field, aria-invalid + aria-describedby on the input, then
<p class="field-error" id="...">Choose a delivery state.</p>. Loading: put
<span class="spinner" aria-hidden="true"></span> Searching… in .combobox-status. -->Needs theme.css. Interactive parts (menus, tabs, drawers) also need the script in the spec for that component.
When to use it
When to use
Long lists, or when people know what they're looking for (their state, their bank).
When not to
Under ~8 options (use a native Select).
Props
CruiseCombobox
| Prop | Type | Default | Description |
|---|---|---|---|
label? | string | — | Visible label. Leave out inside a CruiseFormField (it brings the label). |
items | CruiseComboOption[] | CruiseComboGroup[] | — | Options, or groups of options with a heading each. |
placeholder? | string | — | — |
hint? | string | — | — |
error? | string | — | Error message: --danger border and text under the field. |
disabled? | boolean | — | — |
searchIcon? | boolean | — | Search icon at the start of the field. |
loading? | boolean | — | Async lists: shows the spinner + "Searching…". |
onInputChange? | ((query: string) => void) | — | Called as people type (e.g. to fetch matches). |
remote? | boolean | — | The list is already filtered (e.g. by a server), so don't filter it again. |
popupClassName? | string | — | Extra classes on the list / sheet, e.g. "nepa-mode" (they render in <body>). |
className? | string | — | — |
multiple? | boolean | — | — |
value? | CruiseComboOption | null | CruiseComboOption[] | — | — |
defaultValue? | CruiseComboOption | null | CruiseComboOption[] | — | — |
onValueChange? | ((value: CruiseComboOption | null) => void) | ((value: CruiseComboOption[]) => void) | — | — |
Accessibility
- ARIA combobox pattern: aria-expanded, aria-controls, aria-activedescendant; listbox / option / aria-selected.
- Works with the keyboard only; "No matches" and "Searching…" are announced.
- Below 640px it opens as a bottom sheet with the search focused.