Custom select
Custom select is a neobrutalist React component from Cruise UI. A select with our own list (check on the chosen option), for when native won't do. Install it with npx shadcn@latest add @cruiseui/custom-select.
Install
Terminal
npx shadcn@latest add @cruiseui/custom-selectFirst time? Installation shows the one-off components.json setup (the @cruiseui registry) and how to turn the theme on.
Example
Corners
Delivery area
import { CruiseCustomSelect } from "@/components/cruise/custom-select";
const areas = ["Ikeja", "Yaba", "Surulere", "Lekki", "Victoria Island", "Ajah"].map((label) => ({
value: label.toLowerCase().replace(/\s+/g, "-"),
label,
}));
export default function CustomSelectExample() {
return (
<div className="max-w-sm">
<CruiseCustomSelect label="Delivery area" options={areas} defaultValue="yaba" />
</div>
);
}
<div class="menu-wrap" data-menu>
<button type="button" class="brutal btn btn-secondary" id="order-btn" aria-haspopup="menu" aria-expanded="false" aria-controls="order-menu">
Order options
<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 class="menu" role="menu" id="order-menu" aria-labelledby="order-btn" hidden>
<div class="menu-item" role="menuitem" tabindex="-1">Edit order<span class="menu-hint">E</span></div>
<div class="menu-item" role="menuitem" tabindex="-1">Share receipt</div>
<div class="menu-sep" role="separator"></div>
<div class="menu-item" role="menuitem" tabindex="-1" data-danger>Cancel order</div>
</div>
</div>
<!-- Custom select: same script, role="listbox" -->
<div class="field">
<span class="field-label" id="area-label">Delivery area</span>
<div class="menu-wrap" data-menu style="display: block">
<button type="button" class="select select-trigger" aria-haspopup="listbox" aria-expanded="false" aria-controls="area-list" aria-labelledby="area-label area-value">
<span class="select-value" id="area-value">Yaba</span>
<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 class="menu" role="listbox" id="area-list" aria-labelledby="area-label" hidden>
<div class="menu-item" role="option" tabindex="-1" 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>Ikeja</div>
<div class="menu-item" role="option" tabindex="-1" aria-selected="true"><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>Yaba</div>
</div>
</div>
</div>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
Outside forms, or when options need more than text.
When not to
Forms (use the native Select). Long lists (use a Combobox).
Props
CruiseCustomSelect
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | — | — |
options | CruiseSelectOption<V>[] | — | — |
value? | V | null | — | — |
defaultValue? | V | null | — | — |
onValueChange? | ((value: V | null) => void) | — | — |
placeholder? | string | "Choose…" | — |
hint? | string | — | — |
error? | string | — | — |
disabled? | boolean | — | — |
popupClassName? | string | — | Extra classes on the list, e.g. "nepa-mode" (it renders in <body>). |
className? | string | — | — |
Accessibility
- listbox / option roles with aria-selected; arrows, Home/End and typeahead.
- Always a visible label.