nekode
uicombobox-fieldsource: ok · refreshed
Combobox Field
A labeled combobox input built on Combobox with field layout.
npx shadcn add "https://ui.nekode.id/r/combobox-field.json"free
Interaction
dropdown
Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.
Dependencies
Bundle weight · lightui-primitives@base-ui/react
iconslucide-react
Registry dependencies
fieldcomboboxlabel
Files1 · source cached 10/8/2026, 3:32:01 AM
src/registry/ui/combobox-field.tsx
"use client";
import { Loader2Icon } from "lucide-react";
import * as React from "react";
import {
Combobox,
ComboboxContent,
ComboboxEmpty,
ComboboxInput,
ComboboxItem,
ComboboxList,
} from "@/components/ui/combobox";
import {
Field,
FieldDescription,
FieldError,
FieldLabel,
} from "@/components/ui/field";
import { cn } from "@/lib/utils";
export interface ComboboxFieldOption {
label: string;
value: string;
disabled?: boolean;
}
export interface ComboboxFieldProps {
label?: string;
description?: string;
error?: string;
placeholder?: string;
emptyText?: string;
options: ComboboxFieldOption[];
name?: string;
id?: string;
value: string;
onValueChange: (value: string | undefined) => void;
disabled?: boolean;
required?: boolean;
/**
* Show a spinner in place of the clear button and disable the field.
* Use this while options are being fetched asynchronously.
*/
loading?: boolean;
/** Show a clear ("x") button once a value is selected. Defaults to `true`. */
showClear?: boolean;
/** Auto-highlight the first matching item while filtering. */
autoHighlight?: boolean;
/** Passed to the underlying `Field` wrapper (e.g. orientation, data-invalid overrides). */
fieldClassName?: string;
labelClassName?: string;
inputClassName?: string;
orientation?: "vertical" | "horizontal" | "responsive";
}
export const ComboboxField = React.forwardRef<
HTMLInputElement,
ComboboxFieldProps
>(
(
{
label,
description,
error,
placeholder = "Select an option",
emptyText = "No results found.",
options,
name,
id,
value,
onValueChange,
disabled,
required,
loading,
showClear = true,
autoHighlight,
fieldClassName,
labelClassName,
inputClassName,
orientation = "vertical",
},
ref,
) => {
const generatedId = React.useId();
const inputId = id ?? generatedId;
const descriptionId = description ? `${inputId}-description` : undefined;
const errorId = error ? `${inputId}-error` : undefined;
const describedBy = description ? descriptionId : undefined;
const isDisabled = disabled || loading;
const selectedItem = React.useMemo(
() => options.find((item) => item.value === value) ?? null,
[options, value],
);
return (
<Field
orientation={orientation}
data-invalid={error ? true : undefined}
className={fieldClassName}
>
{label && (
<FieldLabel
htmlFor={inputId}
className={cn(
error && "text-destructive",
isDisabled && "opacity-70 cursor-not-allowed",
labelClassName,
)}
>
{label}
{required && (
<>
<span aria-hidden="true" className="text-destructive ml-0.5">
*
</span>
<span className="sr-only"> (required)</span>
</>
)}
</FieldLabel>
)}
<div className="relative flex items-center">
<Combobox
items={options}
itemToStringLabel={(item) => item.label}
itemToStringValue={(item) => item.value}
name={name}
value={selectedItem}
onValueChange={(item) =>
onValueChange?.(item ? item.value : undefined)
}
disabled={isDisabled}
autoHighlight={autoHighlight}
>
<ComboboxInput
id={inputId}
ref={ref}
placeholder={placeholder}
aria-required={required || undefined}
aria-invalid={!!error}
aria-describedby={describedBy}
aria-errormessage={errorId}
showClear={loading ? false : showClear}
className={cn("w-full", loading && "pr-9", inputClassName)}
disabled={loading || disabled}
>
{loading && (
<div
aria-hidden="true"
className="absolute right-3 flex items-center text-muted-foreground pointer-events-none"
>
<Loader2Icon className="size-4 animate-spin" />
</div>
)}
</ComboboxInput>
<ComboboxContent>
<ComboboxEmpty>{emptyText}</ComboboxEmpty>
<ComboboxList>
{(item: ComboboxFieldOption) => (
<ComboboxItem
key={item.value}
value={item}
disabled={item.disabled}
className="data-selected:bg-primary/10 data-selected:font-medium data-selected:text-primary data-selected:data-highlighted:bg-primary/15 data-selected:data-highlighted:text-primary"
>
{item.label}
</ComboboxItem>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>
</div>
{description && !error && (
<FieldDescription id={descriptionId}>{description}</FieldDescription>
)}
{error && <FieldError id={errorId}>{error}</FieldError>}
</Field>
);
},
);
ComboboxField.displayName = "ComboboxField";