A combo box combines a text input with a listbox, allowing users to filter a list of options to items matching a query
Import
import { ComboBox } from "heroui-solid";Usage
import { ComboBox, Input, Label, ListBox } from "heroui-solid"
export function Default() {
return (
<ComboBox class="w-[256px]">
<Label>Favorite Animal</Label>
<ComboBox.InputGroup>
<Input placeholder="Search animals..." />
<ComboBox.Trigger />
</ComboBox.InputGroup>
<ComboBox.Popover>
<ListBox>
<ListBox.Item id="aardvark" textValue="Aardvark">
Aardvark
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="dog" textValue="Dog">
Dog
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="kangaroo" textValue="Kangaroo">
Kangaroo
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="panda" textValue="Panda">
Panda
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="snake" textValue="Snake">
Snake
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</ComboBox.Popover>
</ComboBox>
)
}Anatomy
Import the ComboBox component and access all parts using dot notation.
import { ComboBox, Input, Label, Description, Header, ListBox, Separator } from "heroui-solid";
export default () => (
<ComboBox>
<Label />
<ComboBox.InputGroup>
<Input />
<ComboBox.Trigger />
</ComboBox.InputGroup>
<Description />
<ComboBox.Popover>
<ListBox>
<ListBox.Item>
<Label />
<Description />
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Section>
<Header />
<ListBox.Item>
<Label />
</ListBox.Item>
</ListBox.Section>
</ListBox>
</ComboBox.Popover>
</ComboBox>
);With Description
import { ComboBox, Description, Input, Label, ListBox } from "heroui-solid"
export function WithDescription() {
return (
<ComboBox class="w-[256px]">
<Label>Favorite Animal</Label>
<ComboBox.InputGroup>
<Input placeholder="Search animals..." />
<ComboBox.Trigger />
</ComboBox.InputGroup>
<ComboBox.Popover>
<ListBox>
<ListBox.Item id="aardvark" textValue="Aardvark">
Aardvark
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="dog" textValue="Dog">
Dog
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="kangaroo" textValue="Kangaroo">
Kangaroo
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="panda" textValue="Panda">
Panda
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="snake" textValue="Snake">
Snake
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</ComboBox.Popover>
<Description>Search and select your favorite animal</Description>
</ComboBox>
)
}With Sections
import {
ComboBox,
Header,
Input,
Label,
ListBox,
Separator
} from "heroui-solid"
export function WithSections() {
return (
<ComboBox class="w-[256px]">
<Label>Country</Label>
<ComboBox.InputGroup>
<Input placeholder="Search countries..." />
<ComboBox.Trigger />
</ComboBox.InputGroup>
<ComboBox.Popover>
<ListBox>
<ListBox.Section>
<Header>North America</Header>
<ListBox.Item id="usa" textValue="United States">
United States
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="canada" textValue="Canada">
Canada
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="mexico" textValue="Mexico">
Mexico
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox.Section>
<Separator />
<ListBox.Section>
<Header>Europe</Header>
<ListBox.Item id="uk" textValue="United Kingdom">
United Kingdom
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="france" textValue="France">
France
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="germany" textValue="Germany">
Germany
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="spain" textValue="Spain">
Spain
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="italy" textValue="Italy">
Italy
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox.Section>
<Separator />
<ListBox.Section>
<Header>Asia</Header>
<ListBox.Item id="japan" textValue="Japan">
Japan
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="china" textValue="China">
China
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="india" textValue="India">
India
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="south-korea" textValue="South Korea">
South Korea
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox.Section>
</ListBox>
</ComboBox.Popover>
</ComboBox>
)
}With Disabled Options
import { ComboBox, Input, Label, ListBox } from "heroui-solid"
export function WithDisabledOptions() {
return (
<ComboBox class="w-[256px]" disabledKeys={["cat", "kangaroo"]}>
<Label>Animal</Label>
<ComboBox.InputGroup>
<Input placeholder="Search animals..." />
<ComboBox.Trigger />
</ComboBox.InputGroup>
<ComboBox.Popover>
<ListBox>
<ListBox.Item id="dog" textValue="Dog">
Dog
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="bird" textValue="Bird">
Bird
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="kangaroo" textValue="Kangaroo">
Kangaroo
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="elephant" textValue="Elephant">
Elephant
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="tiger" textValue="Tiger">
Tiger
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</ComboBox.Popover>
</ComboBox>
)
}Custom Indicator
import { ChevronsExpandVertical } from "gravity-icons-solid"
import { ComboBox, Input, Label, ListBox } from "heroui-solid"
export function CustomIndicator() {
return (
<ComboBox class="w-[256px]">
<Label>Favorite Animal</Label>
<ComboBox.InputGroup>
<Input placeholder="Search animals..." />
<ComboBox.Trigger class="size-3">
<ChevronsExpandVertical />
</ComboBox.Trigger>
</ComboBox.InputGroup>
<ComboBox.Popover>
<ListBox>
<ListBox.Item id="aardvark" textValue="Aardvark">
Aardvark
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="dog" textValue="Dog">
Dog
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="kangaroo" textValue="Kangaroo">
Kangaroo
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="panda" textValue="Panda">
Panda
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="snake" textValue="Snake">
Snake
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</ComboBox.Popover>
</ComboBox>
)
}Required
import {
Button,
ComboBox,
FieldError,
Form,
Input,
Label,
ListBox
} from "heroui-solid"
export function Required() {
const onSubmit = (e: SubmitEvent) => {
e.preventDefault()
alert("Form submitted successfully!")
}
return (
<Form class="flex w-[256px] flex-col gap-4" onSubmit={onSubmit}>
<ComboBox isRequired class="w-full" name="animal">
<Label>Favorite Animal</Label>
<ComboBox.InputGroup>
<Input placeholder="Search animals..." />
<ComboBox.Trigger />
</ComboBox.InputGroup>
<ComboBox.Popover>
<ListBox>
<ListBox.Item id="aardvark" textValue="Aardvark">
Aardvark
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="dog" textValue="Dog">
Dog
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="kangaroo" textValue="Kangaroo">
Kangaroo
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="panda" textValue="Panda">
Panda
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="snake" textValue="Snake">
Snake
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</ComboBox.Popover>
<FieldError />
</ComboBox>
<Button type="submit">Submit</Button>
</Form>
)
}Custom Value
import {
Avatar,
AvatarFallback,
AvatarImage,
ComboBox,
Description,
Input,
Label,
ListBox
} from "heroui-solid"
import { For } from "solid-js"
export function CustomValue() {
const users = [
{
avatarUrl:
"https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg",
email: "bob@heroui.com",
fallback: "B",
id: "1",
name: "Bob"
},
{
avatarUrl:
"https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg",
email: "fred@heroui.com",
fallback: "F",
id: "2",
name: "Fred"
},
{
avatarUrl:
"https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg",
email: "martha@heroui.com",
fallback: "M",
id: "3",
name: "Martha"
},
{
avatarUrl:
"https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/red.jpg",
email: "john@heroui.com",
fallback: "J",
id: "4",
name: "John"
},
{
avatarUrl:
"https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/orange.jpg",
email: "jane@heroui.com",
fallback: "J",
id: "5",
name: "Jane"
}
]
return (
<ComboBox class="w-[256px]">
<Label>User</Label>
<ComboBox.InputGroup>
<Input placeholder="Search users..." />
<ComboBox.Trigger />
</ComboBox.InputGroup>
<ComboBox.Popover>
<ListBox>
<For each={users}>
{(user) => (
<ListBox.Item id={user.id} textValue={user.name}>
<Avatar size="sm">
<AvatarImage src={user.avatarUrl} />
<AvatarFallback>{user.fallback}</AvatarFallback>
</Avatar>
<div class="flex flex-col">
<Label>{user.name}</Label>
<Description>{user.email}</Description>
</div>
<ListBox.ItemIndicator />
</ListBox.Item>
)}
</For>
</ListBox>
</ComboBox.Popover>
</ComboBox>
)
}Controlled
Selected: Cat
import { ComboBox, Input, Label, ListBox } from "heroui-solid"
import { createSignal, For } from "solid-js"
export function Controlled() {
const animals = [
{
id: "cat",
name: "Cat"
},
{
id: "dog",
name: "Dog"
},
{
id: "bird",
name: "Bird"
},
{
id: "fish",
name: "Fish"
},
{
id: "hamster",
name: "Hamster"
}
]
const [selectedKey, setSelectedKey] = createSignal<string | null>("cat")
const selectedAnimal = () => animals.find((a) => a.id === selectedKey())
return (
<div class="space-y-2">
<ComboBox
class="w-[256px]"
selectedKey={selectedKey()}
onSelectionChange={(key) => setSelectedKey(key)}
>
<Label>Animal (controlled)</Label>
<ComboBox.InputGroup>
<Input placeholder="Search animals..." />
<ComboBox.Trigger />
</ComboBox.InputGroup>
<ComboBox.Popover>
<ListBox>
<For each={animals}>
{(animal) => (
<ListBox.Item id={animal.id} textValue={animal.name}>
{animal.name}
<ListBox.ItemIndicator />
</ListBox.Item>
)}
</For>
</ListBox>
</ComboBox.Popover>
</ComboBox>
<p class="text-sm text-muted">
Selected: {selectedAnimal()?.name || "None"}
</p>
</div>
)
}Controlled Input Value
Input value: (empty)
import { ComboBox, Input, Label, ListBox } from "heroui-solid"
import { createSignal } from "solid-js"
export function ControlledInputValue() {
const [inputValue, setInputValue] = createSignal("")
return (
<div class="space-y-2">
<ComboBox
class="w-[256px]"
inputValue={inputValue()}
onInputChange={setInputValue}
>
<Label>Search (controlled input)</Label>
<ComboBox.InputGroup>
<Input placeholder="Type to search..." />
<ComboBox.Trigger />
</ComboBox.InputGroup>
<ComboBox.Popover>
<ListBox>
<ListBox.Item id="aardvark" textValue="Aardvark">
Aardvark
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="dog" textValue="Dog">
Dog
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="kangaroo" textValue="Kangaroo">
Kangaroo
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="panda" textValue="Panda">
Panda
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="snake" textValue="Snake">
Snake
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</ComboBox.Popover>
</ComboBox>
<p class="text-sm text-muted">Input value: {inputValue() || "(empty)"}</p>
</div>
)
}Custom Filtering
import { ComboBox, Input, Label, ListBox } from "heroui-solid"
import { For } from "solid-js"
export function CustomFiltering() {
const animals = [
{ id: "cat", name: "Cat" },
{ id: "dog", name: "Dog" },
{ id: "bird", name: "Bird" },
{ id: "fish", name: "Fish" },
{ id: "hamster", name: "Hamster" }
]
return (
<ComboBox
class="w-[256px]"
defaultFilter={(text, inputValue) => {
if (!inputValue) return true
return text.toLowerCase().includes(inputValue.toLowerCase())
}}
>
<Label>Animal (custom filter)</Label>
<ComboBox.InputGroup>
<Input placeholder="Search animals..." />
<ComboBox.Trigger />
</ComboBox.InputGroup>
<ComboBox.Popover>
<ListBox>
<For each={animals}>
{(animal) => (
<ListBox.Item id={animal.id} textValue={animal.name}>
{animal.name}
<ListBox.ItemIndicator />
</ListBox.Item>
)}
</For>
</ListBox>
</ComboBox.Popover>
</ComboBox>
)
}Allows Custom Value
import { ComboBox, Description, Input, Label, ListBox } from "heroui-solid"
export function AllowsCustomValue() {
return (
<ComboBox allowsCustomValue class="w-[256px]">
<Label>Favorite Animal</Label>
<ComboBox.InputGroup>
<Input placeholder="Search or type an animal..." />
<ComboBox.Trigger />
</ComboBox.InputGroup>
<ComboBox.Popover>
<ListBox>
<ListBox.Item id="aardvark" textValue="Aardvark">
Aardvark
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="dog" textValue="Dog">
Dog
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="kangaroo" textValue="Kangaroo">
Kangaroo
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="panda" textValue="Panda">
Panda
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="snake" textValue="Snake">
Snake
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</ComboBox.Popover>
<Description>
You can type any animal name, even if it's not in the list
</Description>
</ComboBox>
)
}Disabled
import { ComboBox, Input, Label, ListBox } from "heroui-solid"
export function Disabled() {
return (
<ComboBox isDisabled class="w-[256px]" defaultSelectedKey="cat">
<Label>Favorite Animal</Label>
<ComboBox.InputGroup>
<Input placeholder="Search animals..." />
<ComboBox.Trigger />
</ComboBox.InputGroup>
<ComboBox.Popover>
<ListBox>
<ListBox.Item id="aardvark" textValue="Aardvark">
Aardvark
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="dog" textValue="Dog">
Dog
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="kangaroo" textValue="Kangaroo">
Kangaroo
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="panda" textValue="Panda">
Panda
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="snake" textValue="Snake">
Snake
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</ComboBox.Popover>
</ComboBox>
)
}Default Selected Key
import { ComboBox, Input, Label, ListBox } from "heroui-solid"
export function DefaultSelectedKey() {
return (
<ComboBox class="w-[256px]" defaultSelectedKey="cat">
<Label>Favorite Animal</Label>
<ComboBox.InputGroup>
<Input placeholder="Search animals..." />
<ComboBox.Trigger />
</ComboBox.InputGroup>
<ComboBox.Popover>
<ListBox>
<ListBox.Item id="aardvark" textValue="Aardvark">
Aardvark
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="dog" textValue="Dog">
Dog
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="kangaroo" textValue="Kangaroo">
Kangaroo
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="panda" textValue="Panda">
Panda
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="snake" textValue="Snake">
Snake
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</ComboBox.Popover>
</ComboBox>
)
}Full Width
import { ComboBox, Input, Label, ListBox } from "heroui-solid"
export function FullWidth() {
return (
<div class="w-[400px] space-y-4">
<ComboBox fullWidth>
<Label>Favorite Animal</Label>
<ComboBox.InputGroup>
<Input placeholder="Search animals..." />
<ComboBox.Trigger />
</ComboBox.InputGroup>
<ComboBox.Popover>
<ListBox>
<ListBox.Item id="aardvark" textValue="Aardvark">
Aardvark
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="dog" textValue="Dog">
Dog
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</ComboBox.Popover>
</ComboBox>
</div>
)
}In Surface
When used inside a Surface component, use variant="secondary" to apply the lower emphasis variant suitable for surface backgrounds.
import {
Button,
ComboBox,
FieldError,
Form,
Input,
Label,
ListBox,
Surface
} from "heroui-solid"
export function OnSurface() {
const onSubmit = (e: SubmitEvent) => {
e.preventDefault()
alert("Form submitted successfully!")
}
return (
<Surface class="w-[320px] rounded-3xl p-6">
<Form class="flex w-full flex-col gap-4" onSubmit={onSubmit}>
<ComboBox isRequired class="w-full" name="animal" variant="secondary">
<Label>Favorite Animal</Label>
<ComboBox.InputGroup>
<Input placeholder="Search animals..." />
<ComboBox.Trigger />
</ComboBox.InputGroup>
<ComboBox.Popover>
<ListBox>
<ListBox.Item id="aardvark" textValue="Aardvark">
Aardvark
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="dog" textValue="Dog">
Dog
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="kangaroo" textValue="Kangaroo">
Kangaroo
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="panda" textValue="Panda">
Panda
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="snake" textValue="Snake">
Snake
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</ComboBox.Popover>
<FieldError />
</ComboBox>
<Button type="submit">Submit</Button>
</Form>
</Surface>
)
}Menu Trigger
Use the menuTrigger prop to control when the popover opens:
focus(default): popover opens when the user focuses the inputinput: popover opens when the user edits the input textmanual: popover only opens when the user presses the trigger button or uses the arrow keys
Focus (default)
Input
Manual
import { ComboBox, Description, Input, Label, ListBox } from "heroui-solid"
export function MenuTrigger() {
return (
<div class="flex flex-col gap-8">
<div class="flex flex-col gap-2">
<p class="text-sm font-medium text-muted">Focus (default)</p>
<ComboBox class="w-[256px]" menuTrigger="focus">
<Label>Favorite Animal</Label>
<ComboBox.InputGroup>
<Input placeholder="Search animals..." />
<ComboBox.Trigger />
</ComboBox.InputGroup>
<ComboBox.Popover>
<ListBox>
<ListBox.Item id="aardvark" textValue="Aardvark">
Aardvark
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="dog" textValue="Dog">
Dog
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="kangaroo" textValue="Kangaroo">
Kangaroo
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="panda" textValue="Panda">
Panda
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="snake" textValue="Snake">
Snake
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</ComboBox.Popover>
<Description>Popover opens when the input is focused</Description>
</ComboBox>
</div>
<div class="flex flex-col gap-2">
<p class="text-sm font-medium text-muted">Input</p>
<ComboBox class="w-[256px]" menuTrigger="input">
<Label>Favorite Animal</Label>
<ComboBox.InputGroup>
<Input placeholder="Search animals..." />
<ComboBox.Trigger />
</ComboBox.InputGroup>
<ComboBox.Popover>
<ListBox>
<ListBox.Item id="aardvark" textValue="Aardvark">
Aardvark
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="dog" textValue="Dog">
Dog
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="kangaroo" textValue="Kangaroo">
Kangaroo
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="panda" textValue="Panda">
Panda
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="snake" textValue="Snake">
Snake
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</ComboBox.Popover>
<Description>
Popover opens when the user edits the input text
</Description>
</ComboBox>
</div>
<div class="flex flex-col gap-2">
<p class="text-sm font-medium text-muted">Manual</p>
<ComboBox class="w-[256px]" menuTrigger="manual">
<Label>Favorite Animal</Label>
<ComboBox.InputGroup>
<Input placeholder="Search animals..." />
<ComboBox.Trigger />
</ComboBox.InputGroup>
<ComboBox.Popover>
<ListBox>
<ListBox.Item id="aardvark" textValue="Aardvark">
Aardvark
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="dog" textValue="Dog">
Dog
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="kangaroo" textValue="Kangaroo">
Kangaroo
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="panda" textValue="Panda">
Panda
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="snake" textValue="Snake">
Snake
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</ComboBox.Popover>
<Description>
Popover only opens when the trigger button is pressed or arrow keys
are used
</Description>
</ComboBox>
</div>
</div>
)
}Styling
Passing Tailwind CSS classes
import { ComboBox, Input, Label, ListBox } from "heroui-solid";
function CustomComboBox() {
return (
<ComboBox class="w-full">
<Label>Favorite Animal</Label>
<ComboBox.InputGroup class="border rounded-lg p-2 bg-surface">
<Input placeholder="Search animals..." />
<ComboBox.Trigger class="text-muted" />
</ComboBox.InputGroup>
<ComboBox.Popover>
<ListBox>
<ListBox.Item id="1" textValue="Item 1" class="hover:bg-surface-secondary">
Item 1
</ListBox.Item>
</ListBox>
</ComboBox.Popover>
</ComboBox>
);
}Customizing the component classes
To customize the ComboBox component classes, you can use the @layer components directive.
Learn more.
@layer components {
.combo-box {
@apply flex flex-col gap-1;
}
.combo-box__input-group {
@apply relative inline-flex items-center;
}
.combo-box__trigger {
@apply absolute right-0 text-muted;
}
.combo-box__popover {
@apply rounded-lg border border-border bg-surface p-2;
}
}HeroUI follows the BEM methodology to ensure component variants and states are reusable and easy to customize.
CSS Classes
The ComboBox component uses these CSS classes (View source styles):
Base Classes
.combo-box- Base ComboBox container.combo-box__input-group- Container for the input and trigger button.combo-box__trigger- The button that triggers the popover.combo-box__popover- The popover container
State Classes
.combo-box[data-invalid="true"]- Invalid state.combo-box[data-disabled="true"]- Disabled ComboBox state.combo-box__trigger[data-focus-visible="true"]- Focused trigger state.combo-box__trigger[data-disabled="true"]- Disabled trigger state.combo-box__trigger[data-open="true"]- Open trigger state
Interactive States
The component supports both CSS pseudo-classes and data attributes for flexibility:
- Hover:
:hoveror[data-hovered="true"]on trigger - Focus:
:focus-visibleor[data-focus-visible="true"]on trigger - Disabled:
:disabledor[data-disabled="true"]on ComboBox - Open:
[data-open="true"]on trigger
API Reference
ComboBox Props
| Prop | Type | Default | Description |
|---|---|---|---|
inputValue | string | - | Current input value (controlled) |
defaultInputValue | string | - | Default input value (uncontrolled) |
onInputChange | (value: string) => void | - | Handler called when the input value changes |
selectedKey | Key | null | - | Current selected key (controlled) |
defaultSelectedKey | Key | null | - | Default selected key (uncontrolled) |
onSelectionChange | (key: Key | null) => void | - | Handler called when the selection changes |
disabledKeys | Iterable<Key> | - | Keys of disabled items |
defaultFilter | (text: string, inputValue: string) => boolean | - | Custom filter function for filtering items |
isDisabled | boolean | - | Whether the ComboBox is disabled |
isReadOnly | boolean | - | Whether the input can be selected but not changed by the user |
isRequired | boolean | - | Whether user input is required |
isInvalid | boolean | - | Whether the ComboBox value is invalid |
name | string | - | The name of the input, used when submitting an HTML form |
allowsCustomValue | boolean | - | Whether the ComboBox allows custom values not in the list |
allowsEmptyCollection | boolean | - | Whether the ComboBox allows an empty collection |
menuTrigger | "focus" | "input" | "manual" | "focus" | The interaction required to display the ComboBox menu |
fullWidth | boolean | false | Whether the ComboBox should take full width of its container |
variant | "primary" | "secondary" | "primary" | Visual variant of the component. secondary is a lower emphasis variant without shadow, suitable for use in surfaces |
class | string | - | Additional CSS classes |
children | JSX.Element | - | ComboBox content |
ComboBox.InputGroup Props
| Prop | Type | Default | Description |
|---|---|---|---|
class | string | - | Additional CSS classes |
children | JSX.Element | - | InputGroup content |
ComboBox.Trigger Props
| Prop | Type | Default | Description |
|---|---|---|---|
class | string | - | Additional CSS classes |
children | JSX.Element | - | Custom trigger content |
ComboBox.Popover Props
| Prop | Type | Default | Description |
|---|---|---|---|
placement | string | "bottom" | Placement relative to the trigger (Kobalte placements, e.g. "top") |
class | string | - | Additional CSS classes |
children | JSX.Element | - | Content children |
Examples
Basic Usage
import { ComboBox, Input, Label, ListBox } from "heroui-solid";
<ComboBox class="w-[256px]">
<Label>Favorite Animal</Label>
<ComboBox.InputGroup>
<Input placeholder="Search animals..." />
<ComboBox.Trigger />
</ComboBox.InputGroup>
<ComboBox.Popover>
<ListBox>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="dog" textValue="Dog">
Dog
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</ComboBox.Popover>
</ComboBox>;With Sections
import { ComboBox, Header, Input, Label, ListBox, Separator } from "heroui-solid";
<ComboBox class="w-[256px]">
<Label>Country</Label>
<ComboBox.InputGroup>
<Input placeholder="Search countries..." />
<ComboBox.Trigger />
</ComboBox.InputGroup>
<ComboBox.Popover>
<ListBox>
<ListBox.Section>
<Header>North America</Header>
<ListBox.Item id="usa" textValue="United States">
United States
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox.Section>
<Separator />
<ListBox.Section>
<Header>Europe</Header>
<ListBox.Item id="uk" textValue="United Kingdom">
United Kingdom
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox.Section>
</ListBox>
</ComboBox.Popover>
</ComboBox>;Controlled Selection
import { ComboBox, Input, Label, ListBox } from "heroui-solid";
import { createSignal } from "solid-js";
function ControlledComboBox() {
const [selectedKey, setSelectedKey] = createSignal<string | null>("cat");
return (
<ComboBox
class="w-[256px]"
selectedKey={selectedKey()}
onSelectionChange={setSelectedKey}
>
<Label>Animal</Label>
<ComboBox.InputGroup>
<Input placeholder="Search animals..." />
<ComboBox.Trigger />
</ComboBox.InputGroup>
<ComboBox.Popover>
<ListBox>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="dog" textValue="Dog">
Dog
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</ComboBox.Popover>
</ComboBox>
);
}Controlled Input Value
import { ComboBox, Input, Label, ListBox } from "heroui-solid";
import { createSignal } from "solid-js";
function ControlledInputComboBox() {
const [inputValue, setInputValue] = createSignal("");
return (
<ComboBox
class="w-[256px]"
inputValue={inputValue()}
onInputChange={setInputValue}
>
<Label>Search</Label>
<ComboBox.InputGroup>
<Input placeholder="Type to search..." />
<ComboBox.Trigger />
</ComboBox.InputGroup>
<ComboBox.Popover>
<ListBox>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="dog" textValue="Dog">
Dog
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</ComboBox.Popover>
</ComboBox>
);
}Custom Filtering
import { ComboBox, Input, Label, ListBox } from "heroui-solid";
<ComboBox
class="w-[256px]"
defaultFilter={(text, inputValue) => {
if (!inputValue) return true;
return text.toLowerCase().includes(inputValue.toLowerCase());
}}
>
<Label>Animal</Label>
<ComboBox.InputGroup>
<Input placeholder="Search animals..." />
<ComboBox.Trigger />
</ComboBox.InputGroup>
<ComboBox.Popover>
<ListBox>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="dog" textValue="Dog">
Dog
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</ComboBox.Popover>
</ComboBox>;Menu Trigger
Control when the popover opens using the menuTrigger prop:
import { ComboBox, Description, Input, Label, ListBox } from "heroui-solid";
// Opens on focus (default)
<ComboBox class="w-[256px]" menuTrigger="focus">
<Label>Favorite Animal</Label>
<ComboBox.InputGroup>
<Input placeholder="Search animals..." />
<ComboBox.Trigger />
</ComboBox.InputGroup>
<ComboBox.Popover>
<ListBox>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</ComboBox.Popover>
<Description>Popover opens when the input is focused</Description>
</ComboBox>;
// Opens when typing
<ComboBox class="w-[256px]" menuTrigger="input">
<Label>Favorite Animal</Label>
<ComboBox.InputGroup>
<Input placeholder="Search animals..." />
<ComboBox.Trigger />
</ComboBox.InputGroup>
<ComboBox.Popover>
<ListBox>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</ComboBox.Popover>
<Description>Popover opens when the user edits the input text</Description>
</ComboBox>;
// Opens only manually
<ComboBox class="w-[256px]" menuTrigger="manual">
<Label>Favorite Animal</Label>
<ComboBox.InputGroup>
<Input placeholder="Search animals..." />
<ComboBox.Trigger />
</ComboBox.InputGroup>
<ComboBox.Popover>
<ListBox>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</ComboBox.Popover>
<Description>Popover only opens when the trigger button is pressed or arrow keys are used</Description>
</ComboBox>;Form Value
The key of the selected item is submitted as part of an HTML form (using the item id). When allowsCustomValue is enabled, the typed text is submitted instead:
import { Button, ComboBox, FieldError, Form, Input, Label, ListBox } from "heroui-solid";
function FormValueExample() {
const onSubmit = (e: SubmitEvent) => {
e.preventDefault();
const formData = new FormData(e.currentTarget as HTMLFormElement);
console.log("Submitted value:", formData.get("animal")); // Will be "cat" (the key)
};
return (
<Form onSubmit={onSubmit}>
<ComboBox name="animal" isRequired>
<Label>Animal</Label>
<ComboBox.InputGroup>
<Input placeholder="Select an animal..." />
<ComboBox.Trigger />
</ComboBox.InputGroup>
<ComboBox.Popover>
<ListBox>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="dog" textValue="Dog">
Dog
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</ComboBox.Popover>
<FieldError />
</ComboBox>
<Button type="submit">Submit</Button>
</Form>
);
}Validation Behavior
Mark the field required with isRequired and render a FieldError to surface validation messages:
import { Button, ComboBox, FieldError, Form, Input, Label, ListBox } from "heroui-solid";
function ValidationExample() {
return (
<Form>
<ComboBox name="animal" isRequired>
<Label>Animal</Label>
<ComboBox.InputGroup>
<Input placeholder="Select an animal..." />
<ComboBox.Trigger />
</ComboBox.InputGroup>
<ComboBox.Popover>
<ListBox>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</ComboBox.Popover>
<FieldError />
</ComboBox>
<Button type="submit">Submit</Button>
</Form>
);
}Custom Validation
Use the isInvalid prop to drive realtime validation and show a message via FieldError:
import { ComboBox, FieldError, Input, Label, ListBox } from "heroui-solid";
import { createSignal } from "solid-js";
function CustomValidationExample() {
const [selectedKey, setSelectedKey] = createSignal<string | null>(null);
const isInvalid = () => selectedKey() === "snake";
return (
<ComboBox
class="w-[256px]"
isInvalid={isInvalid()}
selectedKey={selectedKey()}
onSelectionChange={setSelectedKey}
>
<Label>Favorite Animal</Label>
<ComboBox.InputGroup>
<Input placeholder="Search animals..." />
<ComboBox.Trigger />
</ComboBox.InputGroup>
<ComboBox.Popover>
<ListBox>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="dog" textValue="Dog">
Dog
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="snake" textValue="Snake">
Snake
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</ComboBox.Popover>
<FieldError>Snakes are not allowed</FieldError>
</ComboBox>
);
}Read Only
Use the isReadOnly prop to make the comboBox read-only:
import { ComboBox, Input, Label, ListBox } from "heroui-solid";
<ComboBox class="w-[256px]" isReadOnly defaultSelectedKey="cat">
<Label>Favorite Animal</Label>
<ComboBox.InputGroup>
<Input placeholder="Search animals..." />
<ComboBox.Trigger />
</ComboBox.InputGroup>
<ComboBox.Popover>
<ListBox>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="dog" textValue="Dog">
Dog
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</ComboBox.Popover>
</ComboBox>;Accessibility
The ComboBox component implements the ARIA comboBox pattern and provides:
- Full keyboard navigation support
- Screen reader announcements for selection changes and input changes
- Proper focus management
- Support for disabled states
- Typeahead search functionality
- HTML form integration
- Support for custom values
Differences from HeroUI React
- Uses
classinstead ofclassName, andonClickinstead of React Aria'sonPress. - Built on Kobalte's Combobox, which is data-driven: this port bridges the JSX item
API by collecting
ListBox.Itemdescriptors and registering them as Kobalte options. ComboBox.Popoverplacements use Kobalte's syntax ("top","bottom-start", …) instead of React Aria's ("top left").- No
renderprop — use Kobalte's polymorphicaswhere supported. - The asynchronous loading example (React Aria's
useAsyncList+Collection+ListBoxLoadMoreItem) has no Solid/Kobalte equivalent and is omitted.
Last updated: 7/19/26, 3:27 AM