Skip to main content

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

Search and select your favorite animal
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

You can type any animal name, even if it's not in the list
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>
  )
}

Use the menuTrigger prop to control when the popover opens:

  • focus (default): popover opens when the user focuses the input
  • input: popover opens when the user edits the input text
  • manual: popover only opens when the user presses the trigger button or uses the arrow keys

Focus (default)

Popover opens when the input is focused

Input

Popover opens when the user edits the input text

Manual

Popover only opens when the trigger button is pressed or arrow keys are used
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: :hover or [data-hovered="true"] on trigger
  • Focus: :focus-visible or [data-focus-visible="true"] on trigger
  • Disabled: :disabled or [data-disabled="true"] on ComboBox
  • Open: [data-open="true"] on trigger

API Reference

ComboBox Props

PropTypeDefaultDescription
inputValuestring-Current input value (controlled)
defaultInputValuestring-Default input value (uncontrolled)
onInputChange(value: string) => void-Handler called when the input value changes
selectedKeyKey | null-Current selected key (controlled)
defaultSelectedKeyKey | null-Default selected key (uncontrolled)
onSelectionChange(key: Key | null) => void-Handler called when the selection changes
disabledKeysIterable<Key>-Keys of disabled items
defaultFilter(text: string, inputValue: string) => boolean-Custom filter function for filtering items
isDisabledboolean-Whether the ComboBox is disabled
isReadOnlyboolean-Whether the input can be selected but not changed by the user
isRequiredboolean-Whether user input is required
isInvalidboolean-Whether the ComboBox value is invalid
namestring-The name of the input, used when submitting an HTML form
allowsCustomValueboolean-Whether the ComboBox allows custom values not in the list
allowsEmptyCollectionboolean-Whether the ComboBox allows an empty collection
menuTrigger"focus" | "input" | "manual""focus"The interaction required to display the ComboBox menu
fullWidthbooleanfalseWhether 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
classstring-Additional CSS classes
childrenJSX.Element-ComboBox content

ComboBox.InputGroup Props

PropTypeDefaultDescription
classstring-Additional CSS classes
childrenJSX.Element-InputGroup content

ComboBox.Trigger Props

PropTypeDefaultDescription
classstring-Additional CSS classes
childrenJSX.Element-Custom trigger content

ComboBox.Popover Props

PropTypeDefaultDescription
placementstring"bottom"Placement relative to the trigger (Kobalte placements, e.g. "top")
classstring-Additional CSS classes
childrenJSX.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>;

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 class instead of className, and onClick instead of React Aria's onPress.
  • Built on Kobalte's Combobox, which is data-driven: this port bridges the JSX item API by collecting ListBox.Item descriptors and registering them as Kobalte options.
  • ComboBox.Popover placements use Kobalte's syntax ("top", "bottom-start", …) instead of React Aria's ("top left").
  • No render prop — use Kobalte's polymorphic as where 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

HeroUI SolidUnofficial SolidJS port of HeroUI v3, built on Kobalte and @heroui/styles