Skip to main content

Combobox

Fill in a React input field with autocomplete & autosuggest functionalities. Choose from a list of suggested values with full keyboard support. Combobox follows the WAI-ARIA Combobox Pattern and builds on the ariakit Combobox.

When to use

Use Combobox for a list where the user types to filter — large static lists, async/server-side data, or any single-select where search is helpful. For small finite lists without filtering, use Select. For multi-selection, use Multi Select.

Pair with Field.* for label/description/error wiring in forms: wrap Combobox.Input in Field.Control. Field.Control flows the generated id, name, aria-describedby, aria-errormessage, and aria-invalid onto the focusable input.

Start typing to filter available domains.

import { Combobox } from "@ngrok/mantle/combobox";import { Field } from "@ngrok/mantle/field"; <Field.Item name="subdomain">	<Field.Label>Subdomain</Field.Label>	<Combobox.Root>		<Field.Control>			<Combobox.Input placeholder="Choose an ngrok subdomain..." />		</Field.Control>		<Combobox.Content>			<Combobox.Group>				<Combobox.GroupLabel>Available domains</Combobox.GroupLabel>				<Combobox.Item value="https://${random}.ngrok.app">					<Combobox.ItemValue />				</Combobox.Item>				<Combobox.Item value="https://${random}.ngrok.dev">					<Combobox.ItemValue />				</Combobox.Item>				<Combobox.Item value="https://${random}.ngrok.io">					<Combobox.ItemValue />				</Combobox.Item>			</Combobox.Group>		</Combobox.Content>	</Combobox.Root>	<Field.Description>Start typing to filter available domains.</Field.Description></Field.Item>;

Without helper or error text, a Field.Item with a Field.Label still gives the input its accessible name:

import { Combobox } from "@ngrok/mantle/combobox";import { Field } from "@ngrok/mantle/field";import { CirclesThreePlusIcon } from "@phosphor-icons/react/CirclesThreePlus"; <Field.Item name="subdomain">	<Field.Label>Subdomain</Field.Label>	<Combobox.Root>		<Field.Control>			<Combobox.Input />		</Field.Control>		<Combobox.Content>			<Combobox.Group>				<Combobox.GroupLabel>Choose an ngrok subdomain</Combobox.GroupLabel>				<Combobox.Item value="https://" disabled>					<Combobox.ItemValue />				</Combobox.Item>				<Combobox.Item value="https://${random}.ngrok.app">					<CirclesThreePlusIcon weight="duotone" className="text-accent-600" />					<Combobox.ItemValue />				</Combobox.Item>				<Combobox.Item value="https://${random}.ngrok.dev">					<Combobox.ItemValue />				</Combobox.Item>				<Combobox.Item value="https://${random}.ngrok.io">					<Combobox.ItemValue />				</Combobox.Item>			</Combobox.Group>			<Combobox.Separator />			<Combobox.Item>				Sit dolor enim eiusmod nulla nostrud officia in magna deserunt ut ex veniam cillum.			</Combobox.Item>		</Combobox.Content>	</Combobox.Root></Field.Item>;

Layering and z-index

Combobox.Content renders in place at Tailwind z-50, Mantle's float tier — it does not portal, so the dialog primitive's Escape guard can see it. Inside an overlay it paints with the overlay's content. Outside one, an open overlay (z-60) covers it.

Combobox does not render a backdrop. Modal layers such as Dialog, Sheet, and AlertDialog do render backdrops, so stacking those layers compounds the dim and blur effect on the layers underneath.

Examples

With TanStack Form

Use @tanstack/react-form with zod to keep the combobox input controlled. Wrap Combobox.Input in Field.Control so label, name, and validation ARIA are applied to the focusable input.

import { Button } from "@ngrok/mantle/button";import { Combobox } from "@ngrok/mantle/combobox";import { Field, toErrorMessages } from "@ngrok/mantle/field";import { useForm } from "@tanstack/react-form";import { z } from "zod"; export const formSchema = z.object({	region: z.string().min(1, "Choose a region."),});function Example() {	const defaultValues = {		region: "",	};	const form = useForm({		defaultValues,		validators: {			onSubmit: formSchema,		},		onSubmit: ({ value }) => {			// Handle form submission here		},	}); 	return (		<form			onSubmit={(event) => {				event.preventDefault();				event.stopPropagation();				void form.handleSubmit();			}}		>			<form.Field name="region">				{(field) => (					<Field.Item name={field.name}>						<Field.Label>Region</Field.Label>						<Combobox.Root							value={field.state.value}							setValue={(value) => {								field.handleChange(value);							}}						>							<Field.Control>								<Combobox.Input onBlur={field.handleBlur} placeholder="Choose a region..." />							</Field.Control>							<Combobox.Content>								<Combobox.Item value="us-east-1">US East</Combobox.Item>								<Combobox.Item value="us-west-2">US West</Combobox.Item>								<Combobox.Item value="eu-west-1">EU West</Combobox.Item>							</Combobox.Content>						</Combobox.Root>						<Field.Errors messages={toErrorMessages(field.state.meta.errors)} />					</Field.Item>				)}			</form.Field>			<Button type="submit" appearance="filled" intent="neutral">				Submit			</Button>		</form>	);}

Composition

Compose the parts of a Combobox together to build your own:

Combobox.Root├── Combobox.Input└── Combobox.Content    ├── Combobox.Group    │   ├── Combobox.GroupLabel    │   └── Combobox.Item    │       └── Combobox.ItemValue    └── Combobox.Separator

Polymorphism

Combobox.Content, Combobox.Item, Combobox.Group, Combobox.GroupLabel, and Combobox.ItemValue support asChild to render as a different element or your own component while keeping the combobox behavior and styling.

import { Combobox } from "@ngrok/mantle/combobox"; <Combobox.Root>	<Combobox.Input placeholder="Search links..." />	<Combobox.Content>		<Combobox.Item value="Dashboard" asChild>			<a href="#dashboard">Dashboard</a>		</Combobox.Item>		<Combobox.Item value="Settings" asChild>			<a href="#settings">Settings</a>		</Combobox.Item>	</Combobox.Content></Combobox.Root>;

API Reference

The Combobox components are built on top of ariakit Combobox.

Combobox.Root

The outermost part of a combobox. It owns the ariakit store that holds the query text, the open state, and the selection.

Combobox.Content renders in place at Tailwind z-50, Mantle's float tier — it does not portal, so the dialog primitive's Escape guard can see it. Inside an overlay it paints with the overlay's content. Outside one, an open overlay (z-60) covers it.

All props from ariakit ComboboxProvider.

Combobox.Input

Renders a combobox input element for filtering a list of items.

All props from ariakit Combobox, plus:

PropTypeDefaultDescription
autoComplete?string"list"The autocomplete behavior of the input.
autoSelect?boolean | "always""always"How the first item is automatically selected.
validation?"error" | "success" | "warning" | false | (() => "error" | "success" | "warning" | false)Validation state that changes the border and outline.

Combobox.Content

Renders a popover that contains combobox content, e.g. items, groups, and separators.

Combobox.Content renders in place at Tailwind z-50, Mantle's float tier — it does not portal, so the dialog primitive's Escape guard can see it. Inside an overlay it paints with the overlay's content. Outside one, an open overlay (z-60) covers it.

All props from ariakit ComboboxPopover, plus:

PropTypeDefaultDescription
asChild?booleanfalseCompose onto an alternative element type or your own React component.
sameWidth?booleantrueWhether the popover should be the same width as the input.
unmountOnHide?booleantrueWhether the popover should unmount when hidden.

Combobox.Item

Renders a combobox item inside a Combobox.Content component.

All props from ariakit ComboboxItem, plus:

PropTypeDefaultDescription
asChild?booleanfalseCompose onto an alternative element type or your own React component.
focusOnHover?booleantrueWhether the item should receive focus on hover.

Combobox.ItemValue

Highlights the match between the current Combobox.Input value and parent Combobox.Item value. Should only be used as a child of Combobox.Item.

All props from ariakit ComboboxItemValue, plus:

PropTypeDefaultDescription
asChild?booleanfalseCompose onto an alternative element type or your own React component.

Combobox.Group

Renders a group for Combobox.Item elements. Optionally, compose a Combobox.GroupLabel as a child to label the group.

All props from ariakit ComboboxGroup, plus:

PropTypeDefaultDescription
asChild?booleanfalseCompose onto an alternative element type or your own React component.

Combobox.GroupLabel

Renders a label in a combobox group. Should be wrapped with Combobox.Group so the aria-labelledby is correctly set on the group element.

All props from ariakit ComboboxGroupLabel, plus:

PropTypeDefaultDescription
asChild?booleanfalseCompose onto an alternative element type or your own React component.

Combobox.Separator

Renders a separator between Combobox.Items or Combobox.Groups.

All props from Separator.