Shadcn Combobox for React and Tailwind
Displays a searchable list of options.
Installation
bunx --bun shadcn@latest add https://kit.dev/r/combobox.jsonpnpm dlx shadcn@latest add https://kit.dev/r/combobox.jsonnpx shadcn@latest add https://kit.dev/r/combobox.jsonyarn shadcn@latest add https://kit.dev/r/combobox.json<Step>This component depends on Button, Input, and Input Group. Install them first if you haven't already.</Step>
Install the following dependencies:
bun add @ark-ui/react lucide-react tailwind-variantspnpm add @ark-ui/react lucide-react tailwind-variantsnpm install @ark-ui/react lucide-react tailwind-variantsyarn add @ark-ui/react lucide-react tailwind-variantsCopy and paste the following code into your project.
"use client";
import {
Combobox as ArkCombobox,
type ComboboxList as ArkComboboxList,
useCombobox as useArkCombobox,
useComboboxContext as useArkComboboxContext,
useComboboxItemContext as useArkComboboxItemContext,
} from "@ark-ui/react/combobox";
import { Portal } from "@ark-ui/react/portal";
import { CheckIcon, ChevronsUpDownIcon, XIcon } from "lucide-react";
import type React from "react";
import { tv, type VariantProps } from "tailwind-variants";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import type { inputVariants } from "@/components/ui/input";
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
} from "@/components/ui/input-group";
export const useCombobox = useArkCombobox;
export const useComboboxContext = useArkComboboxContext;
export const useComboboxItemContext = useArkComboboxItemContext;
export const ComboboxContext: typeof ArkCombobox.Context = ArkCombobox.Context;
export const ComboboxItemContext: typeof ArkCombobox.ItemContext =
ArkCombobox.ItemContext;
export const Combobox: ArkCombobox.RootComponent = (props) => {
const {
openOnClick = true,
lazyMount = true,
unmountOnExit = true,
...rest
} = props;
return (
<ArkCombobox.Root
data-slot="combobox"
lazyMount={lazyMount}
openOnClick={openOnClick}
unmountOnExit={unmountOnExit}
{...rest}
/>
);
};
export const ComboboxRootProvider: ArkCombobox.RootProviderComponent = (
props
) => {
const { lazyMount = true, unmountOnExit = true, ...rest } = props;
return (
<ArkCombobox.RootProvider
data-slot="combobox"
lazyMount={lazyMount}
unmountOnExit={unmountOnExit}
{...rest}
/>
);
};
export const ComboboxLabel = (
props: React.ComponentProps<typeof ArkCombobox.Label>
) => {
const { className, ...rest } = props;
return (
<ArkCombobox.Label
className={cn("font-medium text-sm", className)}
data-slot="combobox-label"
{...rest}
/>
);
};
export const ComboboxControl = (
props: React.ComponentProps<typeof ArkCombobox.Control>
) => {
const { className, ...rest } = props;
return (
<ArkCombobox.Control
className={cn(
"group/combobox-control",
"relative flex flex-wrap items-center gap-1",
className
)}
data-slot="combobox-control"
{...rest}
/>
);
};
interface ComboboxInputProps
extends Omit<React.ComponentProps<typeof ArkCombobox.Input>, "size">,
VariantProps<typeof inputVariants> {
/**
* Whether the control is disabled.
*
* @default false
*/
disabled?: boolean;
/**
* Whether to show the clear button.
*
* @default false
*/
showClear?: boolean;
/**
* Whether to show the trigger button.
*
* @default true
*/
showTrigger?: boolean;
}
export const ComboboxInput = (props: ComboboxInputProps) => {
const {
size = "md",
showTrigger = true,
showClear = false,
className,
children,
...rest
} = props;
const { inputValue } = useComboboxContext();
return (
<ComboboxControl data-size={size}>
<InputGroup className={cn(className)} size={size}>
{children}
<ArkCombobox.Input asChild>
<InputGroupInput data-slot="combobox-input" {...rest} />
</ArkCombobox.Input>
<InputGroupAddon align="inline-end">
{showTrigger ? (
<InputGroupButton
asChild
className="group-has-data-[slot=combobox-clear]/input-group:hidden"
size="icon-xs"
variant="ghost"
>
<ComboboxTrigger />
</InputGroupButton>
) : null}
{showClear && inputValue ? (
<ComboboxClear asChild>
<InputGroupButton
aria-label="Clear"
size="icon-xs"
variant="ghost"
>
<XIcon aria-hidden="true" />
</InputGroupButton>
</ComboboxClear>
) : null}
</InputGroupAddon>
</InputGroup>
</ComboboxControl>
);
};
export const ComboboxTrigger = (
props: React.ComponentProps<typeof ArkCombobox.Trigger>
) => {
const { className, children, ...rest } = props;
return (
<ArkCombobox.Trigger
className={cn("absolute inset-e-1 inset-y-0", className)}
data-slot="combobox-trigger"
{...rest}
asChild
>
{children ?? (
<Button aria-label="Toggle options" className="size-4" variant="ghost">
<ChevronsUpDownIcon aria-hidden="true" />
</Button>
)}
</ArkCombobox.Trigger>
);
};
export const ComboboxClear = (
props: React.ComponentProps<typeof ArkCombobox.ClearTrigger>
) => <ArkCombobox.ClearTrigger data-slot="combobox-clear" {...props} />;
/** Composable combobox input for custom controls (e.g. Tags Input). */
export const ComboboxFieldInput = (
props: React.ComponentProps<typeof ArkCombobox.Input>
) => <ArkCombobox.Input data-slot="combobox-field-input" {...props} />;
export const ComboboxPositioner = (
props: React.ComponentProps<typeof ArkCombobox.Positioner>
) => <ArkCombobox.Positioner data-slot="combobox-positioner" {...props} />;
export const ComboboxContent = (
props: React.ComponentProps<typeof ArkCombobox.Content>
) => {
const { className, children, ...rest } = props;
return (
<Portal>
<ComboboxPositioner>
<ArkCombobox.Content
className={cn(
"relative z-50",
"max-h-96 min-w-48",
"origin-(--transform-origin)",
"p-1",
"bg-popover",
"text-popover-foreground",
"rounded-xl border shadow-lg/5",
"overflow-y-auto",
"outline-none",
"data-[state=closed]:animate-out data-[state=open]:animate-in",
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
"data-[state=open]:zoom-in-[98%] data-[state=closed]:zoom-out-[98%]",
"data-[placement=top]:slide-in-from-bottom-2",
"data-[placement=bottom]:slide-in-from-top-2",
"data-[placement=right]:slide-in-from-start-2",
"data-[placement=left]:slide-in-from-end-2",
"motion-reduce:animate-none!",
className
)}
data-slot="combobox-content"
{...rest}
>
{children}
</ArkCombobox.Content>
</ComboboxPositioner>
</Portal>
);
};
interface ComboboxGroupProps
extends React.ComponentProps<typeof ArkCombobox.ItemGroup> {
/**
* The heading of the group
*/
heading?: string | React.ReactNode;
}
export const ComboboxGroup = (props: ComboboxGroupProps) => {
const { heading, children, ...rest } = props;
return (
<ArkCombobox.ItemGroup data-slot="combobox-group" {...rest}>
{!!heading && <ComboboxGroupLabel>{heading}</ComboboxGroupLabel>}
{children}
</ArkCombobox.ItemGroup>
);
};
export const ComboboxGroupLabel = (
props: React.ComponentProps<typeof ArkCombobox.ItemGroupLabel>
) => {
const { className, ...rest } = props;
return (
<ArkCombobox.ItemGroupLabel
className={cn(
"px-2 py-1.5 font-semibold text-muted-foreground text-xs",
className
)}
data-slot="combobox-group-label"
{...rest}
/>
);
};
export const comboboxItemVariants = tv({
base: [
"relative",
"py-1.5 ps-2",
"text-sm",
"flex w-full items-center gap-2",
"rounded-xl",
"select-none",
"cursor-default",
"outline-hidden",
"data-[state=checked]:bg-accent data-[state=checked]:text-accent-foreground",
"data-highlighted:bg-accent data-highlighted:text-accent-foreground",
"data-disabled:pointer-events-none data-disabled:opacity-64",
"[&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0",
],
defaultVariants: {
showIndicator: true,
},
variants: {
showIndicator: {
false: "pe-2",
true: "pe-8",
},
},
});
interface ComboboxItemProps
extends React.ComponentProps<typeof ArkCombobox.Item>,
VariantProps<typeof comboboxItemVariants> {}
export const ComboboxItem = (props: ComboboxItemProps) => {
const { showIndicator = true, className, children, ...rest } = props;
return (
<ArkCombobox.Item
className={cn(comboboxItemVariants({ showIndicator }), className)}
data-slot="combobox-item"
persistFocus
{...rest}
>
{children}
{showIndicator ? (
<span className="absolute inset-e-2 flex size-3.5 items-center justify-center">
<ArkCombobox.ItemIndicator data-slot="combobox-item-indicator">
<CheckIcon aria-hidden="true" />
</ArkCombobox.ItemIndicator>
</span>
) : null}
</ArkCombobox.Item>
);
};
export const ComboboxEmpty = (
props: React.ComponentProps<typeof ArkCombobox.Empty>
) => {
const { className, children, ...rest } = props;
return (
<ArkCombobox.Empty
className={cn(
"px-2 py-1.5",
"text-center text-muted-foreground text-sm",
className
)}
data-slot="combobox-empty"
{...rest}
>
{children || "No results found."}
</ArkCombobox.Empty>
);
};
export const ComboboxList = (
props: React.ComponentProps<typeof ArkComboboxList>
) => {
const { className, ...rest } = props;
return (
<ArkCombobox.List
className={cn("flex flex-col", className)}
data-slot="combobox-list"
{...rest}
/>
);
};Update the import paths to match your project setup.
Anatomy
Combobox
├── ComboboxLabel (optional)
├── ComboboxInput
│ ├── ComboboxControl (built in)
│ ├── input
│ ├── ComboboxTrigger
│ └── ComboboxClear (optional)
└── ComboboxContent
├── ComboboxEmpty
└── ComboboxList
└── ComboboxGroup
├── ComboboxGroupLabel
└── ComboboxItemComboboxContent portals the list and includes the positioner. ComboboxItem includes the check indicator. useCombobox is the machine hook for ComboboxRootProvider; useComboboxContext / ComboboxContext is in-tree.
Use Field for the visible label. For free-form suggestions, use Autocomplete.
Usage
import { useListCollection } from "@ark-ui/react/collection";
import { useFilter } from "@ark-ui/react/locale";
import {
Combobox,
ComboboxContent,
ComboboxEmpty,
ComboboxGroup,
ComboboxInput,
ComboboxItem,
ComboboxList,
} from "@/components/ui/combobox";const { contains } = useFilter({ sensitivity: "base" });
const { collection, filter } = useListCollection({
initialItems: [
{ label: "Apple", value: "apple" },
{ label: "Banana", value: "banana" },
],
filter: contains,
});
<Combobox
collection={collection}
onInputValueChange={({ inputValue }) => filter(inputValue)}
>
<ComboboxInput placeholder="Select an option" />
<ComboboxContent>
<ComboboxEmpty />
<ComboboxList>
{collection.items.map((item) => (
<ComboboxItem item={item} key={item.value}>
{item.label}
</ComboboxItem>
))}
</ComboboxList>
</ComboboxContent>
</Combobox>shadcn.io defaults openOnClick, lazyMount, and unmountOnExit to true (Ark: openOnClick false, presence false). inputBehavior is "none". Pass a collection and call filter from onInputValueChange. Selected values are always a string[].
Controlled
Control the selected value with value and onValueChange. Selected values are always a string[].
Root Provider
Use useCombobox with ComboboxRootProvider when you need the API outside the tree. Pass machine options (collection, multiple, inputBehavior, openOnClick, …) to useCombobox(), not to the provider.
States
Invalid
Disabled
Sizes
Size is set on ComboboxInput. sm is h-7, md is h-8, lg is h-9.
Small
Medium
Large
Examples
Auto highlight
Highlight the first matching item as the user types with inputBehavior="autohighlight".
Inline autocomplete
Set inputBehavior="autocomplete" so arrow keys complete the input with the highlighted item. Pair it with a startsWith filter for best results.
Group
Group related items with groupBy on the collection and collection.group() when rendering. ComboboxGroup accepts a heading for the label.
With Field
Field wires the label, helper text, and error text to the control.
Context
Read selected state with ComboboxContext or useComboboxContext.
Links
Render items as links with asChild. Use selectionBehavior="preserve" so choosing a link does not replace the input value.
Rehydrate
When defaultValue or value is set before the collection loads, call syncSelectedItems() once the items are available so the input shows the selected label.
Highlight text
Highlight the matching query in each item with Highlight.
Dynamic
Build the collection from the current input. Useful for email-style suggestions.
Creatable
Let users add a value that is not in the list. Set allowCustomValue and upsert a temporary “create” item while typing, then replace it on select.
Multiple selection
Set multiple to select more than one item. Selection behavior becomes clear, so render selected items outside the input.
Async search
Load options from an async source with useAsyncList. Filter only on reason === "input-change" so highlighting and selection do not refetch.
Custom object
Map custom objects with itemToString and itemToValue on useListCollection.
Limit results
Pass limit to useListCollection to cap how many items are rendered.
Scrollable
Cap the list height with className on ComboboxContent. The positioner also exposes --available-height if you want to fit the viewport.
With clear button
Pass showClear to ComboboxInput. The clear control is shown when the input is not empty.
With start icon
Put an InputGroupAddon as a child of ComboboxInput. Decorative icons should be aria-hidden="true".
Guides
Combobox vs Autocomplete
| Combobox | Autocomplete | |
|---|---|---|
| Custom values | Closed list unless you opt in with allowCustomValue | Allowed (allowCustomValue) |
| Input behavior | none | autocomplete |
| Trigger | Shown (showTrigger) | Hidden (showTrigger={false}) |
| Use when | Pick from a known set, with search | Free text with suggestions |
Use Select when there is no search. Use Command for a command palette. Use Tags Input when the value is a list of removable tags.
Collections
Items live in an Ark collection, not a loose items prop on the root.
const { contains } = useFilter({ sensitivity: "base" });
const { collection, filter } = useListCollection({
initialItems,
filter: contains,
});
<Combobox
collection={collection}
onInputValueChange={({ inputValue }) => filter(inputValue)}
>Call filter(inputValue) from onInputValueChange. Render collection.items (or collection.group() when grouping).
Custom objects
By default the collection expects { label, value }. Map other shapes with itemToString and itemToValue:
const { collection } = useListCollection({
initialItems: [
{ country: "Canada", code: "CA", flag: "🇨🇦" },
],
itemToString: (item) => item.country,
itemToValue: (item) => item.code,
});Type safety
Combobox is typed as Ark’s RootComponent, so onValueChange infers item types from the collection:
<Combobox
collection={collection}
onValueChange={(e) => {
// e.items is T[]
console.log(e.items);
}}
>Large datasets
Prefer limit on useListCollection so only a slice is in the DOM:
const { collection } = useListCollection({
initialItems: items,
limit: 10,
});For very large lists, pass scrollToIndexFn and virtualize the list (for example with TanStack Virtual). Keyboard navigation needs that scroll helper.
Router links
Set navigate on the root when items are links, so in-app routing runs instead of a full navigation:
<Combobox
collection={collection}
navigate={(details) => {
router.push(details.node.href);
}}
>NavigateDetails is { value: string; node: HTMLAnchorElement; href: string }.
Available size
The positioner (built into ComboboxContent) exposes CSS variables you can use on the list:
| CSS variable | Description |
|---|---|
--reference-width | Width of the input control |
--reference-height | Height of the root |
--available-width | Available width in the viewport |
--available-height | Available height in the viewport |
Example: match the input width and cap height to the viewport.
<ComboboxContent className="w-(--reference-width) max-h-[min(24rem,calc(var(--available-height)-100px))]" />Multiple selection
When multiple is true, selectionBehavior is set to clear. Render selectedItems from context above the input (chips, tags, or a summary). Do not rely on the input to show every selected value.
API Reference
shadcn.io wraps Ark UI Combobox. Defaults below are shadcn.io Combobox values. They differ from Ark for openOnClick (true here, false in Ark) and lazyMount / unmountOnExit (true here, false in Ark).
asChild merges props onto a single child element.
Combobox
Root. Renders a div. Requires a collection.
| Prop | Type | Default | Description |
|---|---|---|---|
collection | ListCollection<T> | required | Items to show. Create with useListCollection or createListCollection. |
allowCustomValue | boolean | false | Allow values that are not in the collection. |
alwaysSubmitOnEnter | boolean | false | Submit on Enter even if the list is open. Useful for single-field forms. |
asChild | boolean | false | Render the child element instead of a div. |
autoFocus | boolean | - | Focus the input on mount. |
className | string | - | Class names on the root. |
closeOnSelect | boolean | - | Close the list when an item is selected. |
composite | boolean | true | Treat as composed with other composite widgets such as tabs. |
defaultHighlightedValue | string | null | - | Uncontrolled initial highlighted value. |
defaultInputValue | string | "" | Uncontrolled initial input text. |
defaultOpen | boolean | - | Uncontrolled initial open state. |
defaultValue | string[] | [] | Uncontrolled selected values. |
disabled | boolean | - | Disable the combobox. |
disableLayer | boolean | - | Do not register as a dismissable layer. |
form | string | - | Associated form id. |
hideMode | "display-none" | "activity" | "display-none" | How to hide mounted-but-closed content. activity needs React 19+. |
highlightedValue | string | null | - | Controlled highlighted value. |
id | string | - | Unique id for the machine. |
ids | Partial<{ root: string; label: string; control: string; input: string; content: string; trigger: string; clearTrigger: string; item: (id: string, index?: number) => string; positioner: string; itemGroup: (id: string | number) => string; itemGroupLabel: (id: string | number) => string }> | - | Element ids for composition. |
immediate | boolean | - | Apply presence changes immediately instead of the next frame. |
inputBehavior | "none" | "autocomplete" | "autohighlight" | "none" | none leaves the input as typed. autocomplete fills the input from the highlighted item. autohighlight highlights the first match while typing. |
inputValue | string | - | Controlled input text. |
invalid | boolean | - | Mark as invalid. |
lazyMount | boolean | true | Mount the list on first open. |
loopFocus | boolean | true | Loop keyboard focus through items. |
multiple | boolean | - | Allow more than one selected value. Forces selectionBehavior to clear. |
name | string | - | name on the input for form submission. |
navigate | (details: NavigateDetails) => void | - | Called when a link item is chosen. { value, node, href }. |
onExitComplete | () => void | - | Called when the close animation finishes. |
onFocusOutside | (event: FocusOutsideEvent) => void | - | Called when focus moves outside. |
onHighlightChange | (details: HighlightChangeDetails<T>) => void | - | Called when the highlighted item changes. { highlightedValue, highlightedItem }. |
onInputValueChange | (details: InputValueChangeDetails) => void | - | Called when the input text changes. { inputValue, reason }. Filter the collection here. |
onInteractOutside | (event: InteractOutsideEvent) => void | - | Called on outside interaction. |
onOpenChange | (details: OpenChangeDetails) => void | - | Called when open state changes. { open, reason, value }. |
onPointerDownOutside | (event: PointerDownOutsideEvent) => void | - | Called on pointer down outside. |
onSelect | (details: SelectionDetails) => void | - | Called when an item is selected. { value, itemValue }. |
onValueChange | (details: ValueChangeDetails<T>) => void | - | Called when the selection changes. { value: string[]; items: T[] }. |
open | boolean | - | Controlled open state. |
openOnChange | boolean | ((details: InputValueChangeDetails) => boolean) | true | Open the list when the input value changes. |
openOnClick | boolean | true | Open the list on click in the input. |
openOnKeyPress | boolean | true | Open the list on arrow keys. |
placeholder | string | - | Placeholder on the root. Prefer placeholder on ComboboxInput. |
positioning | PositioningOptions | { placement: "bottom-start" } | Floating position of the list. |
present | boolean | - | Controlled presence. |
readOnly | boolean | - | Non-editable, but still interactive. |
required | boolean | - | Mark as required. |
scrollToIndexFn | (details: ScrollToIndexDetails) => void | - | Scroll a virtualized list to an index. { index, immediate, getElement }. |
selectionBehavior | "clear" | "replace" | "preserve" | "replace" | After select: clear the input, replace it with the item, or leave it. multiple forces clear. |
skipAnimationOnMount | boolean | false | Skip the initial presence animation. |
translations | IntlTranslations | - | { triggerLabel?, clearTriggerLabel? } for assistive labels. |
unmountOnExit | boolean | true | Unmount the list after the close animation. |
value | string[] | - | Controlled selected values. |
| Attribute | Description |
|---|---|
data-slot | combobox |
data-scope | combobox |
data-part | root |
data-invalid | Present when invalid |
data-readonly | Present when read-only |
ComboboxInput
Composed control: Input Group wrapping the searchable input, optional trigger, and optional clear button. Children render as start addons (icons, text). Renders inside ComboboxControl.
| Prop | Type | Default | Description |
|---|---|---|---|
size | "sm" | "md" | "lg" | "md" | Control height. sm is h-7, md is h-8, lg is h-9. |
showTrigger | boolean | true | Show the chevron that opens the list. Hidden while the clear button is visible. |
showClear | boolean | false | Show a clear button when inputValue is not empty. |
placeholder | string | - | Placeholder on the input. |
disabled | boolean | - | Disable the input. |
className | string | - | Class names on the input group. |
asChild | boolean | false | Merge onto the underlying input. |
Native input attributes (type, aria-label, autoComplete, …) pass through to the input.
| Attribute | Description |
|---|---|
data-slot | combobox-input on the input |
data-scope | combobox |
data-part | input |
data-invalid | Present when invalid |
data-state | "open" or "closed" |
data-autofocus | Present when autoFocus is set |
ComboboxControl
Optional wrapper for custom layouts. ComboboxInput already includes it.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | Merge onto a single child. |
className | string | - | Class names on the control. |
| Attribute | Description |
|---|---|
data-slot | combobox-control |
data-scope | combobox |
data-part | control |
data-state | "open" or "closed" |
data-focus | Present when focused |
data-disabled | Present when disabled |
data-invalid | Present when invalid |
ComboboxTrigger
Opens the list. Used by ComboboxInput when showTrigger is true. Always asChild; the default child is an icon Button with aria-label="Toggle options".
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | true | Always merges onto a single child. |
className | string | - | Class names on the trigger. |
focusable | boolean | - | Whether the trigger is in the tab order. |
| Attribute | Description |
|---|---|
data-slot | combobox-trigger |
data-scope | combobox |
data-part | trigger |
data-state | "open" or "closed" |
data-invalid | Present when invalid |
data-readonly | Present when read-only |
data-disabled | Present when disabled |
data-focusable | Present when focusable |
ComboboxClear
Clears the input. Used by ComboboxInput when showClear is true. Renders a button. The composed clear control uses aria-label="Clear".
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | Merge onto a single child. |
className | string | - | Class names on the clear control. |
| Attribute | Description |
|---|---|
data-slot | combobox-clear |
data-scope | combobox |
data-part | clear-trigger |
data-invalid | Present when invalid |
ComboboxFieldInput
Bare Ark input for custom layouts (for example composing with Tags Input). Prefer ComboboxInput for the standard control.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | Merge onto a single child. |
className | string | - | Class names on the input. |
Native input attributes pass through.
| Attribute | Description |
|---|---|
data-slot | combobox-field-input |
data-scope | combobox |
data-part | input |
ComboboxLabel
Optional visible label. Prefer Field (FieldLabel) so helper and error text stay associated. Renders a label.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | Merge onto a single child. |
className | string | - | Class names on the label. |
| Attribute | Description |
|---|---|
data-slot | combobox-label |
data-scope | combobox |
data-part | label |
data-disabled | Present when disabled |
data-invalid | Present when invalid |
data-readonly | Present when read-only |
data-required | Present when required |
ComboboxContent
Portaled list surface. Includes the positioner. Renders a div.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | Merge onto a single child. |
className | string | - | Class names on the content. |
| Attribute | Description |
|---|---|
data-slot | combobox-content |
data-scope | combobox |
data-part | content |
data-state | "open" or "closed" |
data-placement | Placement of the content |
data-side | Side of the trigger the content is on |
data-empty | Present when there are no items |
data-nested | Present when nested in another listbox |
data-has-nested | Present when this list has nested listboxes |
| CSS variable | Description |
|---|---|
--layer-index | Index in the dismissable layer stack |
--nested-layer-count | Number of nested comboboxes |
--transform-origin | Transform origin for open/close animation |
--reference-width | Width of the input control (on the positioner) |
--reference-height | Height of the root (on the positioner) |
--available-width | Available width in the viewport (on the positioner) |
--available-height | Available height in the viewport (on the positioner) |
ComboboxList
Wraps the options. Renders a div.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | Merge onto a single child. |
className | string | - | Class names on the list. |
| Attribute | Description |
|---|---|
data-slot | combobox-list |
data-scope | combobox |
data-part | list |
data-empty | Present when there are no items |
ComboboxItem
A single option. Always uses persistFocus so pointer leave does not clear highlight. Includes a check indicator unless showIndicator={false}. Renders a div.
| Prop | Type | Default | Description |
|---|---|---|---|
item | T | required | Collection item. |
showIndicator | boolean | true | Show the selected check. |
asChild | boolean | false | Merge onto a single child (for example an <a>). |
className | string | - | Class names on the item. |
persistFocus | boolean | true | Keep highlight when the pointer leaves. |
| Attribute | Description |
|---|---|
data-slot | combobox-item |
data-scope | combobox |
data-part | item |
data-highlighted | Present when highlighted |
data-state | "checked" or "unchecked" |
data-disabled | Present when disabled |
data-value | The item value |
The indicator is not a separate export.
| Attribute | Description |
|---|---|
data-slot | combobox-item-indicator |
data-scope | combobox |
data-part | item-indicator |
data-state | "checked" or "unchecked" |
ComboboxGroup
Groups related options. Pass heading to render ComboboxGroupLabel.
| Prop | Type | Default | Description |
|---|---|---|---|
heading | string | ReactNode | - | Group label. |
id | string | - | Group id. |
asChild | boolean | false | Merge onto a single child. |
className | string | - | Class names on the group. |
| Attribute | Description |
|---|---|
data-slot | combobox-group |
data-scope | combobox |
data-part | item-group |
data-empty | Present when the group is empty |
ComboboxGroupLabel
Label for a group. Usually created by heading on ComboboxGroup. Renders a div.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | Merge onto a single child. |
className | string | - | Class names on the label. |
| Attribute | Description |
|---|---|
data-slot | combobox-group-label |
data-scope | combobox |
data-part | item-group-label |
ComboboxEmpty
Shown when the filtered collection has no items. Default children: No results found.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | Merge onto a single child. |
className | string | - | Class names on the empty state. |
children | ReactNode | No results found. | Empty message. |
| Attribute | Description |
|---|---|
data-slot | combobox-empty |
data-scope | combobox |
data-part | empty |
ComboboxRootProvider
Root alternative that takes the API from useCombobox. Renders a div.
| Prop | Type | Default | Description |
|---|---|---|---|
value | UseComboboxReturn<T> | required | Return value of useCombobox(). |
asChild | boolean | false | Render the child element instead of a div. |
hideMode | "display-none" | "activity" | "display-none" | How to hide mounted-but-closed content. |
immediate | boolean | - | Apply presence changes immediately. |
lazyMount | boolean | true | Mount the list on first open. |
onExitComplete | () => void | - | Called when the close animation finishes. |
present | boolean | - | Controlled presence. |
skipAnimationOnMount | boolean | false | Skip the initial presence animation. |
unmountOnExit | boolean | true | Unmount after the close animation. |
Pass collection, multiple, inputBehavior, openOnClick, and other machine options to useCombobox(), not to ComboboxRootProvider. To match Combobox, pass openOnClick: true.
| Attribute | Description |
|---|---|
data-slot | combobox |
data-scope | combobox |
data-part | root |
useCombobox
Creates the combobox API for ComboboxRootProvider. Accepts the same options as Combobox except layout-only props.
const combobox = useCombobox({
collection,
onInputValueChange: (details) => filter(details.inputValue),
openOnClick: true,
});
combobox.focus();ComboboxContext / useComboboxContext
Render-prop or hook access to combobox state. Use inside Combobox or ComboboxRootProvider.
| Property | Type | Description |
|---|---|---|
focused | boolean | Whether the input is focused. |
open | boolean | Whether the list is open. |
inputValue | string | Current input text. |
highlightedValue | string | null | Value of the highlighted item. |
highlightedItem | T | null | Highlighted item. |
setHighlightValue | (value: string) => void | Highlight an item by value. |
clearHighlightValue | () => void | Clear the highlight. |
syncSelectedItems | () => void | Sync selected items after an async collection load. |
selectedItems | T[] | Selected items. |
hasSelectedItems | boolean | Whether any item is selected. |
value | string[] | Selected values. |
valueAsString | string | Selected items as a string. |
selectValue | (value: string) => void | Select a value. |
setValue | (value: string[]) => void | Set the selection. |
clearValue | (value?: string) => void | Clear one value, or all if omitted. |
focus | () => void | Focus the input. |
setInputValue | (value: string, reason?: InputValueChangeReason) => void | Set the input text. |
getItemState | (props: { item: T; persistFocus?: boolean }) => ItemState | State for one item (value, disabled, selected, highlighted). |
setOpen | (open: boolean, reason?: OpenChangeReason) => void | Open or close the list. |
collection | ListCollection<T> | Current collection. |
reposition | (options?: Partial<PositioningOptions>) => void | Update list position. |
multiple | boolean | Whether multiple selection is on. |
disabled | boolean | Whether the combobox is disabled. |
ComboboxContext children: (context) => ReactNode.
ComboboxItemContext / useComboboxItemContext
Render-prop or hook access to one item. Use inside ComboboxItem.
| Property | Type | Description |
|---|---|---|
value | string | Item value. |
disabled | boolean | Whether the item is disabled. |
selected | boolean | Whether the item is selected. |
highlighted | boolean | Whether the item is highlighted. |
ComboboxItemContext children: (context) => ReactNode.
Accessibility
Complies with the Combobox WAI-ARIA design pattern. Label the input with Field (FieldLabel) or aria-label on ComboboxInput. Keep ComboboxEmpty so a filtered list is not a blank popup.
Keyboard support
| Key | Description |
|---|---|
ArrowDown | When closed, opens the list and highlights the first option. When open, moves to the next option. |
ArrowUp | When closed, opens the list and highlights the last option. When open, moves to the previous option. |
Home | When open, moves to the first option. |
End | When open, moves to the last option. |
Enter | Selects the highlighted option and closes the list. |
Escape | Closes the list. |