OptionsCombobox

Typed options wrapper around the UI combobox primitive.

Recipe Source

components/ui/options-combobox.tsx
"use client";import type { Combobox as ComboboxPrimitive } from "@base-ui/react/combobox";import {	Combobox,	ComboboxContent,	ComboboxEmpty,	ComboboxItem,	ComboboxList,} from "@giddaa-housing/ui/combobox";import type { ReactNode } from "react";import {	getSelectableOptionKey,	getSelectableOptionText,	isSelectableOptionValueEqual,	type SelectableOption,} from "./selectable-option";type OptionsComboboxProps<Option extends SelectableOption = SelectableOption> =	Omit<		ComboboxPrimitive.Root.Props<Option>,		"children" | "defaultValue" | "items" | "onValueChange" | "value"	> &		Pick<			React.ComponentProps<typeof ComboboxContent>,			"side" | "align" | "alignOffset"		> & {			children?: ReactNode;			options: readonly Option[];			className?: string;			defaultValue?: Option["value"] | null;			emptyMessage?: ReactNode;			onValueChange?: (				value: Option["value"] | null,				option: Option | null,			) => void;			renderItem?: (option: Option) => ReactNode;			value?: Option["value"] | null;		};function OptionsCombobox<Option extends SelectableOption>({	defaultValue,	value,	options,	className,	side,	align,	alignOffset,	emptyMessage = "No options available",	itemToStringLabel = getSelectableOptionText,	itemToStringValue = (option) => String(option.value),	onValueChange,	renderItem,	children,	...props}: OptionsComboboxProps<Option>) {	const selectedOption =		value === undefined ? undefined : findOptionByValue(options, value);	const defaultSelectedOption =		defaultValue === undefined			? undefined			: findOptionByValue(options, defaultValue);	return (		<Combobox<Option>			items={options}			value={selectedOption}			defaultValue={defaultSelectedOption}			itemToStringLabel={itemToStringLabel}			itemToStringValue={itemToStringValue}			onValueChange={(option) => {				onValueChange?.(option?.value ?? null, option ?? null);			}}			{...props}		>			{children}			<ComboboxContent				className={className}				side={side}				align={align}				alignOffset={alignOffset}			>				<ComboboxEmpty>{emptyMessage}</ComboboxEmpty>				<ComboboxList>					{(option: Option) => (						<ComboboxItem							key={getSelectableOptionKey(option)}							value={option}							disabled={option.disabled}						>							{renderItem ? renderItem(option) : option.label}						</ComboboxItem>					)}				</ComboboxList>			</ComboboxContent>		</Combobox>	);}function findOptionByValue<Option extends SelectableOption>(	options: readonly Option[],	value: Option["value"] | null,) {	if (value == null) {		return null;	}	return (		options.find((option) =>			isSelectableOptionValueEqual(option.value, value),		) ?? null	);}export { OptionsCombobox, type OptionsComboboxProps };

Usage

import { OptionsCombobox } from "@/components/recipes/options-combobox";

<OptionsCombobox options={[{ label: "Lekki", value: "lekki" }]} />

Examples

Options Combobox

Use this when consumers have simple { label, value } options but still need combobox behavior.

Props

Prop/APITypeDefaultDescription
optionsSelectableOption[]-Options to render.
value / defaultValueoption value-Controlled or uncontrolled selected value.
renderItem(option) => ReactNode-Custom list item rendering.
emptyMessageReactNode"No options available"Empty state content.

Accessibility

  • Preserve combobox trigger/value children.
  • Provide text values for non-string labels.
  • Keep disabled options visibly and semantically disabled.

On this page