Skip to main content

A listbox displays a list of options and allows a user to select one or more of them

Import

import { ListBox } from "heroui-solid";

Usage

  • B
    bob@heroui.com
  • F
    fred@heroui.com
  • M
    martha@heroui.com
import { Avatar, Description, Label, ListBox } from "heroui-solid"

export function Default() {
  return (
    <ListBox aria-label="Users" class="w-[220px]" selectionMode="single">
      <ListBox.Item id="1" textValue="Bob">
        <Avatar size="sm">
          <Avatar.Image
            alt="Bob"
            src="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg"
          />
          <Avatar.Fallback>B</Avatar.Fallback>
        </Avatar>
        <div class="flex flex-col">
          <Label>Bob</Label>
          <Description>bob@heroui.com</Description>
        </div>
        <ListBox.ItemIndicator />
      </ListBox.Item>
      <ListBox.Item id="2" textValue="Fred">
        <Avatar size="sm">
          <Avatar.Image
            alt="Fred"
            src="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg"
          />
          <Avatar.Fallback>F</Avatar.Fallback>
        </Avatar>
        <div class="flex flex-col">
          <Label>Fred</Label>
          <Description>fred@heroui.com</Description>
        </div>
        <ListBox.ItemIndicator />
      </ListBox.Item>
      <ListBox.Item id="3" textValue="Martha">
        <Avatar size="sm">
          <Avatar.Image
            alt="Martha"
            src="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg"
          />
          <Avatar.Fallback>M</Avatar.Fallback>
        </Avatar>
        <div class="flex flex-col">
          <Label>Martha</Label>
          <Description>martha@heroui.com</Description>
        </div>
        <ListBox.ItemIndicator />
      </ListBox.Item>
    </ListBox>
  )
}

Anatomy

Import the ListBox component and access all parts using dot notation.

import { ListBox, Label, Description, Header } from "heroui-solid";

export default () => (
  <ListBox>
    <ListBox.Item>
      <Label />
      <Description />
      <ListBox.ItemIndicator />
    </ListBox.Item>
    <ListBox.Section>
      <Header />
      <ListBox.Item>
        <Label />
      </ListBox.Item>
    </ListBox.Section>
  </ListBox>
);

With Sections

  • Create a new file
    N
  • Make changes
    E

  • Move to trash
    D
import { Pencil, SquarePlus, TrashBin } from "gravity-icons-solid"
import {
  Description,
  Header,
  Kbd,
  Label,
  ListBox,
  Separator,
  Surface
} from "heroui-solid"

export function WithSections() {
  return (
    <Surface class="w-[256px] rounded-3xl shadow-surface">
      <ListBox
        aria-label="File actions"
        class="w-full p-2"
        selectionMode="none"
        onAction={(key) => alert(`Selected item: ${key}`)}
      >
        <ListBox.Section>
          <Header>Actions</Header>
          <ListBox.Item id="new-file" textValue="New file">
            <div class="flex h-8 items-start justify-center pt-px">
              <SquarePlus class="size-4 shrink-0 text-muted" />
            </div>
            <div class="flex flex-col">
              <Label>New file</Label>
              <Description>Create a new file</Description>
            </div>
            <Kbd class="ms-auto" variant="light">
              <Kbd.Abbr keyValue="command" />
              <Kbd.Content>N</Kbd.Content>
            </Kbd>
          </ListBox.Item>
          <ListBox.Item id="edit-file" textValue="Edit file">
            <div class="flex h-8 items-start justify-center pt-px">
              <Pencil class="size-4 shrink-0 text-muted" />
            </div>
            <div class="flex flex-col">
              <Label>Edit file</Label>
              <Description>Make changes</Description>
            </div>
            <Kbd class="ms-auto" variant="light">
              <Kbd.Abbr keyValue="command" />
              <Kbd.Content>E</Kbd.Content>
            </Kbd>
          </ListBox.Item>
        </ListBox.Section>
        <Separator />
        <ListBox.Section>
          <Header>Danger zone</Header>
          <ListBox.Item
            id="delete-file"
            textValue="Delete file"
            variant="danger"
          >
            <div class="flex h-8 items-start justify-center pt-px">
              <TrashBin class="size-4 shrink-0 text-danger" />
            </div>
            <div class="flex flex-col">
              <Label>Delete file</Label>
              <Description>Move to trash</Description>
            </div>
            <Kbd class="ms-auto" variant="light">
              <Kbd.Abbr keyValue="command" />
              <Kbd.Abbr keyValue="shift" />
              <Kbd.Content>D</Kbd.Content>
            </Kbd>
          </ListBox.Item>
        </ListBox.Section>
      </ListBox>
    </Surface>
  )
}

Multi Select

  • B
    bob@heroui.com
  • F
    fred@heroui.com
  • M
    martha@heroui.com
import { Avatar, Description, Label, ListBox, Surface } from "heroui-solid"

export function MultiSelect() {
  return (
    <Surface class="w-[256px] rounded-3xl shadow-surface">
      <ListBox aria-label="Users" selectionMode="multiple">
        <ListBox.Item id="1" textValue="Bob">
          <Avatar size="sm">
            <Avatar.Image
              alt="Bob"
              src="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg"
            />
            <Avatar.Fallback>B</Avatar.Fallback>
          </Avatar>
          <div class="flex flex-col">
            <Label>Bob</Label>
            <Description>bob@heroui.com</Description>
          </div>
          <ListBox.ItemIndicator />
        </ListBox.Item>
        <ListBox.Item id="2" textValue="Fred">
          <Avatar size="sm">
            <Avatar.Image
              alt="Fred"
              src="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg"
            />
            <Avatar.Fallback>F</Avatar.Fallback>
          </Avatar>
          <div class="flex flex-col">
            <Label>Fred</Label>
            <Description>fred@heroui.com</Description>
          </div>
          <ListBox.ItemIndicator />
        </ListBox.Item>
        <ListBox.Item id="3" textValue="Martha">
          <Avatar size="sm">
            <Avatar.Image
              alt="Martha"
              src="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg"
            />
            <Avatar.Fallback>M</Avatar.Fallback>
          </Avatar>
          <div class="flex flex-col">
            <Label>Martha</Label>
            <Description>martha@heroui.com</Description>
          </div>
          <ListBox.ItemIndicator />
        </ListBox.Item>
      </ListBox>
    </Surface>
  )
}

With Disabled Items

  • Create a new file
    N
  • Make changes
    E

  • Move to trash
    D
import { Pencil, SquarePlus, TrashBin } from "gravity-icons-solid"
import {
  Description,
  Header,
  Kbd,
  Label,
  ListBox,
  Separator,
  Surface
} from "heroui-solid"

export function WithDisabledItems() {
  return (
    <Surface class="w-[256px] rounded-3xl shadow-surface">
      <ListBox
        aria-label="File actions"
        class="w-full p-2"
        disabledKeys={["delete-file"]}
        selectionMode="none"
        onAction={(key) => alert(`Selected item: ${key}`)}
      >
        <ListBox.Section>
          <Header>Actions</Header>
          <ListBox.Item id="new-file" textValue="New file">
            <div class="flex h-8 items-start justify-center pt-px">
              <SquarePlus class="size-4 shrink-0 text-muted" />
            </div>
            <div class="flex flex-col">
              <Label>New file</Label>
              <Description>Create a new file</Description>
            </div>
            <Kbd class="ms-auto" variant="light">
              <Kbd.Abbr keyValue="command" />
              <Kbd.Content>N</Kbd.Content>
            </Kbd>
          </ListBox.Item>
          <ListBox.Item id="edit-file" textValue="Edit file">
            <div class="flex h-8 items-start justify-center pt-px">
              <Pencil class="size-4 shrink-0 text-muted" />
            </div>
            <div class="flex flex-col">
              <Label>Edit file</Label>
              <Description>Make changes</Description>
            </div>
            <Kbd class="ms-auto" variant="light">
              <Kbd.Abbr keyValue="command" />
              <Kbd.Content>E</Kbd.Content>
            </Kbd>
          </ListBox.Item>
        </ListBox.Section>
        <Separator />
        <ListBox.Section>
          <Header>Danger zone</Header>
          <ListBox.Item
            id="delete-file"
            textValue="Delete file"
            variant="danger"
          >
            <div class="flex h-8 items-start justify-center pt-px">
              <TrashBin class="size-4 shrink-0 text-danger" />
            </div>
            <div class="flex flex-col">
              <Label>Delete file</Label>
              <Description>Move to trash</Description>
            </div>
            <Kbd class="ms-auto" variant="light">
              <Kbd.Abbr keyValue="command" />
              <Kbd.Abbr keyValue="shift" />
              <Kbd.Content>D</Kbd.Content>
            </Kbd>
          </ListBox.Item>
        </ListBox.Section>
      </ListBox>
    </Surface>
  )
}

Custom Check Icon

  • B
    bob@heroui.com
  • F
    fred@heroui.com
  • M
    martha@heroui.com
import { Check } from "gravity-icons-solid"
import { Avatar, Description, Label, ListBox, Surface } from "heroui-solid"

export function CustomCheckIcon() {
  return (
    <Surface class="w-[256px] rounded-3xl shadow-surface">
      <ListBox aria-label="Users" selectionMode="multiple">
        <ListBox.Item id="1" textValue="Bob">
          <Avatar size="sm">
            <Avatar.Image
              alt="Bob"
              src="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg"
            />
            <Avatar.Fallback>B</Avatar.Fallback>
          </Avatar>
          <div class="flex flex-col">
            <Label>Bob</Label>
            <Description>bob@heroui.com</Description>
          </div>
          <ListBox.ItemIndicator>
            <Check class="size-4 text-accent-soft-foreground" />
          </ListBox.ItemIndicator>
        </ListBox.Item>
        <ListBox.Item id="2" textValue="Fred">
          <Avatar size="sm">
            <Avatar.Image
              alt="Fred"
              src="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg"
            />
            <Avatar.Fallback>F</Avatar.Fallback>
          </Avatar>
          <div class="flex flex-col">
            <Label>Fred</Label>
            <Description>fred@heroui.com</Description>
          </div>
          <ListBox.ItemIndicator>
            <Check class="size-4 text-accent-soft-foreground" />
          </ListBox.ItemIndicator>
        </ListBox.Item>
        <ListBox.Item id="3" textValue="Martha">
          <Avatar size="sm">
            <Avatar.Image
              alt="Martha"
              src="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg"
            />
            <Avatar.Fallback>M</Avatar.Fallback>
          </Avatar>
          <div class="flex flex-col">
            <Label>Martha</Label>
            <Description>martha@heroui.com</Description>
          </div>
          <ListBox.ItemIndicator>
            <Check class="size-4 text-accent-soft-foreground" />
          </ListBox.ItemIndicator>
        </ListBox.Item>
      </ListBox>
    </Surface>
  )
}

Controlled

  • B
    bob@heroui.com
  • F
    fred@heroui.com
  • M
    martha@heroui.com

Selected: 1

import { Check } from "gravity-icons-solid"
import { Avatar, Description, Label, ListBox, Surface } from "heroui-solid"
import { createSignal } from "solid-js"

export function Controlled() {
  const [selected, setSelected] = createSignal<Set<string>>(new Set(["1"]))

  const selectedItems = () => Array.from(selected())

  return (
    <div class="space-y-4">
      <Surface class="w-[256px] rounded-3xl shadow-surface">
        <ListBox
          aria-label="Users"
          selectedKeys={selected()}
          selectionMode="multiple"
          onSelectionChange={setSelected}
        >
          <ListBox.Item id="1" textValue="Bob">
            <Avatar size="sm">
              <Avatar.Image
                alt="Bob"
                src="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg"
              />
              <Avatar.Fallback>B</Avatar.Fallback>
            </Avatar>
            <div class="flex flex-col">
              <Label>Bob</Label>
              <Description>bob@heroui.com</Description>
            </div>
            <ListBox.ItemIndicator>
              <Check class="size-4 text-accent-soft-foreground" />
            </ListBox.ItemIndicator>
          </ListBox.Item>
          <ListBox.Item id="2" textValue="Fred">
            <Avatar size="sm">
              <Avatar.Image
                alt="Fred"
                src="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg"
              />
              <Avatar.Fallback>F</Avatar.Fallback>
            </Avatar>
            <div class="flex flex-col">
              <Label>Fred</Label>
              <Description>fred@heroui.com</Description>
            </div>
            <ListBox.ItemIndicator>
              <Check class="size-4 text-accent-soft-foreground" />
            </ListBox.ItemIndicator>
          </ListBox.Item>
          <ListBox.Item id="3" textValue="Martha">
            <Avatar size="sm">
              <Avatar.Image
                alt="Martha"
                src="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg"
              />
              <Avatar.Fallback>M</Avatar.Fallback>
            </Avatar>
            <div class="flex flex-col">
              <Label>Martha</Label>
              <Description>martha@heroui.com</Description>
            </div>
            <ListBox.ItemIndicator>
              <Check class="size-4 text-accent-soft-foreground" />
            </ListBox.ItemIndicator>
          </ListBox.Item>
        </ListBox>
      </Surface>
      <p class="text-sm text-muted">
        Selected:{" "}
        {selectedItems().length > 0 ? selectedItems().join(", ") : "None"}
      </p>
    </div>
  )
}

Scrollbar Modes

HeroUI thin

  • Aardvark
  • Alpaca
  • Antelope
  • Bear
  • Cat
  • Dog
  • Fox
  • Giraffe
  • Kangaroo
  • Koala
  • Lemur
  • Otter
  • Panda
  • Penguin
  • Rabbit
  • Snake
  • Turtle
  • Wombat
  • Zebra

Browser default

  • Aardvark
  • Alpaca
  • Antelope
  • Bear
  • Cat
  • Dog
  • Fox
  • Giraffe
  • Kangaroo
  • Koala
  • Lemur
  • Otter
  • Panda
  • Penguin
  • Rabbit
  • Snake
  • Turtle
  • Wombat
  • Zebra

Hidden

  • Aardvark
  • Alpaca
  • Antelope
  • Bear
  • Cat
  • Dog
  • Fox
  • Giraffe
  • Kangaroo
  • Koala
  • Lemur
  • Otter
  • Panda
  • Penguin
  • Rabbit
  • Snake
  • Turtle
  • Wombat
  • Zebra
import { ListBox, Surface } from "heroui-solid"
import { For } from "solid-js"

type ScrollbarMode = {
  id: string
  label: string
  scrollbar?: "thin" | "default" | "none"
}

const modes: ScrollbarMode[] = [
  {
    id: "heroui",
    label: "HeroUI thin",
    scrollbar: "thin"
  },
  {
    id: "browser",
    label: "Browser default",
    scrollbar: "default"
  },
  {
    id: "hidden",
    label: "Hidden",
    scrollbar: "none"
  }
]

const animals = [
  { id: "aardvark", name: "Aardvark" },
  { id: "alpaca", name: "Alpaca" },
  { id: "antelope", name: "Antelope" },
  { id: "bear", name: "Bear" },
  { id: "cat", name: "Cat" },
  { id: "dog", name: "Dog" },
  { id: "fox", name: "Fox" },
  { id: "giraffe", name: "Giraffe" },
  { id: "kangaroo", name: "Kangaroo" },
  { id: "koala", name: "Koala" },
  { id: "lemur", name: "Lemur" },
  { id: "otter", name: "Otter" },
  { id: "panda", name: "Panda" },
  { id: "penguin", name: "Penguin" },
  { id: "rabbit", name: "Rabbit" },
  { id: "snake", name: "Snake" },
  { id: "turtle", name: "Turtle" },
  { id: "wombat", name: "Wombat" },
  { id: "zebra", name: "Zebra" }
]

function ScrollbarListBox(props: { mode: ScrollbarMode }) {
  return (
    <div class="flex w-[260px] flex-col gap-2">
      <h3 class="px-1 text-sm font-semibold text-muted">{props.mode.label}</h3>
      <Surface
        class="overflow-hidden rounded-3xl shadow-surface"
        data-scrollbar={props.mode.scrollbar}
      >
        <div class="h-52 scrollbar overflow-y-auto p-1">
          <ListBox
            aria-label={`${props.mode.label} animals`}
            selectionMode="single"
          >
            <For each={animals}>
              {(animal) => (
                <ListBox.Item
                  class="text-sm leading-5 font-medium"
                  id={`${props.mode.id}-${animal.id}`}
                  textValue={animal.name}
                >
                  {animal.name}
                </ListBox.Item>
              )}
            </For>
          </ListBox>
        </div>
      </Surface>
    </div>
  )
}

export function ScrollbarModes() {
  return (
    <div class="flex w-full flex-wrap justify-center gap-4">
      <For each={modes}>{(mode) => <ScrollbarListBox mode={mode} />}</For>
    </div>
  )
}

Virtualization

ListBox supports virtualization through Virtualizer, enabling efficient rendering of large datasets by displaying only the rows visible within the viewport. It wraps the ListBox and windows the collection with @tanstack/solid-virtual over Kobalte's virtualized listbox.

  • emma.smith@acme.com
  • liam.smith@acme.com
  • olivia.smith@acme.com
  • noah.smith@acme.com
  • ava.smith@acme.com
  • james.smith@acme.com
  • sophia.smith@acme.com
  • oliver.smith@acme.com
  • isabella.smith@acme.com
  • lucas.smith@acme.com
  • mia.smith@acme.com
  • ethan.smith@acme.com
  • charlotte.smith@acme.com
  • mason.smith@acme.com
  • amelia.smith@acme.com
  • logan.smith@acme.com
  • harper.smith@acme.com
import {
  Description,
  Label,
  ListBox,
  ListLayout,
  Virtualizer
} from "heroui-solid"

interface User {
  id: number
  name: string
  email: string
}

export function Virtualization() {
  const firstNames = [
    "Emma",
    "Liam",
    "Olivia",
    "Noah",
    "Ava",
    "James",
    "Sophia",
    "Oliver",
    "Isabella",
    "Lucas",
    "Mia",
    "Ethan",
    "Charlotte",
    "Mason",
    "Amelia",
    "Logan",
    "Harper",
    "Alexander",
    "Ella",
    "Benjamin"
  ]

  const lastNames = [
    "Smith",
    "Johnson",
    "Williams",
    "Brown",
    "Jones",
    "Garcia",
    "Miller",
    "Davis",
    "Rodriguez",
    "Martinez",
    "Anderson",
    "Taylor",
    "Thomas",
    "Jackson",
    "White",
    "Harris",
    "Clark",
    "Lewis",
    "Robinson",
    "Walker"
  ]

  function generateUsers(n: number): User[] {
    const users: User[] = []

    for (let i = 0; i < n; i++) {
      const firstName = firstNames[i % firstNames.length]
      const lastName =
        lastNames[Math.floor(i / firstNames.length) % lastNames.length]
      const name = `${firstName} ${lastName}`

      users.push({
        email: `${firstName?.toLowerCase()}.${lastName?.toLowerCase()}@acme.com`,
        id: i + 1,
        name
      })
    }

    return users
  }

  const users = generateUsers(1000)

  return (
    <Virtualizer layout={ListLayout} layoutOptions={{ rowHeight: 50 }}>
      <ListBox
        aria-label="Virtualized list with 1000 items"
        class="h-[400px] w-[300px] overflow-y-auto"
        items={users}
      >
        {(user: User) => (
          <ListBox.Item id={String(user.id)} textValue={user.name}>
            <div class="flex flex-col">
              <Label>{user.name}</Label>
              <Description>{user.email}</Description>
            </div>
            <ListBox.ItemIndicator />
          </ListBox.Item>
        )}
      </ListBox>
    </Virtualizer>
  )
}

Styling

Passing Tailwind CSS classes

import { ListBox } from "heroui-solid";

function CustomListBox() {
  return (
    <ListBox class="border rounded-lg p-2 bg-surface">
      <ListBox.Item id="1" textValue="Item 1" class="hover:bg-surface-secondary">
        Item 1
      </ListBox.Item>
    </ListBox>
  );
}

Customizing the component classes

To customize the ListBox component classes, you can use the @layer components directive.
Learn more.

@layer components {
  .list-box {
    @apply rounded-lg border border-border bg-surface p-2;
  }

  .list-box-item {
    @apply rounded px-2 py-1 cursor-pointer;
  }

  .list-box-item--danger {
    @apply text-danger;
  }

  .list-box-item__indicator {
    @apply text-accent;
  }
}

HeroUI follows the BEM methodology to ensure component variants and states are reusable and easy to customize.

CSS Classes

The ListBox component uses these CSS classes (View source styles):

Base Classes

  • .list-box - Base listbox container
  • .list-box-item - Individual listbox item
  • .list-box-item__indicator - Selection indicator icon
  • .list-box-section - Section container for grouping items

Variant Classes

  • .list-box--default - Default variant styling
  • .list-box--danger - Danger variant styling
  • .list-box-item--default - Default item variant
  • .list-box-item--danger - Danger item variant

State Classes

  • .list-box-item[aria-selected="true"] - Selected item state
  • .list-box-item:focus-visible - Focused item state
  • .list-box-item[data-disabled] - Disabled item state (Kobalte attribute)

Interactive States

The component supports both CSS pseudo-classes and data attributes for flexibility:

  • Hover / keyboard highlight: :hover or [data-highlighted] on item (Kobalte attribute)
  • Focus: :focus-visible on item
  • Selected: [aria-selected="true"] on item
  • Disabled: [data-disabled] on item (Kobalte attribute)

API Reference

ListBox Props

PropTypeDefaultDescription
aria-labelstring-Accessibility label for the listbox
aria-labelledbystring-ID of element that labels the listbox
selectionMode"none" | "single" | "multiple""single"Selection behavior
selectedKeysIterable<string>-Controlled selected keys
defaultSelectedKeysIterable<string>-Initial selected keys
onSelectionChange(keys: Set<string>) => void-Handler called when selection changes
disabledKeysIterable<string>-Keys of disabled items
onAction(key: string) => void-Handler called when an item is activated
variant"default" | "danger""default"Visual variant
classstring-Additional CSS classes
childrenJSX.Element-ListBox items and sections

Inside a Select, the selection props are ignored — the enclosing Select root owns the selection state.

ListBox.Item Props

PropTypeDefaultDescription
idstring-Unique identifier for the item
textValuestringidText value for accessibility and typeahead
isDisabledbooleanfalseWhether this item is disabled
variant"default" | "danger""default"Visual variant
classstring-Additional CSS classes
childrenJSX.Element-Item content

ListBox.ItemIndicator Props

PropTypeDefaultDescription
classstring-Additional CSS classes
childrenJSX.Element-Custom indicator content (defaults to the checkmark)
forceMountboolean-Render the indicator even while unselected

ListBox.Section Props

PropTypeDefaultDescription
classstring-Additional CSS classes
childrenJSX.Element-Section content including Header and Items

Examples

Basic Usage

import { ListBox, Label, Description } from "heroui-solid";

<ListBox aria-label="Users" selectionMode="single">
  <ListBox.Item id="1" textValue="Bob">
    <Label>Bob</Label>
    <Description>bob@heroui.com</Description>
  </ListBox.Item>
  <ListBox.Item id="2" textValue="Alice">
    <Label>Alice</Label>
    <Description>alice@heroui.com</Description>
  </ListBox.Item>
</ListBox>

With Sections

import { ListBox, Header, Separator } from "heroui-solid";

<ListBox aria-label="Actions" selectionMode="none" onAction={(key) => console.log(key)}>
  <ListBox.Section>
    <Header>Actions</Header>
    <ListBox.Item id="new" textValue="New file">New file</ListBox.Item>
    <ListBox.Item id="edit" textValue="Edit file">Edit file</ListBox.Item>
  </ListBox.Section>
  <Separator />
  <ListBox.Section>
    <Header>Danger zone</Header>
    <ListBox.Item id="delete" textValue="Delete" variant="danger">Delete</ListBox.Item>
  </ListBox.Section>
</ListBox>

Controlled Selection

import { ListBox } from "heroui-solid";
import { createSignal } from "solid-js";

function ControlledListBox() {
  const [selected, setSelected] = createSignal<Set<string>>(new Set(["1"]));

  return (
    <ListBox
      aria-label="Options"
      selectedKeys={selected()}
      selectionMode="multiple"
      onSelectionChange={setSelected}
    >
      <ListBox.Item id="1" textValue="Option 1">Option 1</ListBox.Item>
      <ListBox.Item id="2" textValue="Option 2">Option 2</ListBox.Item>
      <ListBox.Item id="3" textValue="Option 3">Option 3</ListBox.Item>
    </ListBox>
  );
}

Custom Indicator

import { Check } from "gravity-icons-solid";
import { ListBox } from "heroui-solid";

<ListBox aria-label="Options" selectionMode="multiple">
  <ListBox.Item id="1" textValue="Option 1">
    Option 1
    <ListBox.ItemIndicator>
      <Check class="size-4" />
    </ListBox.ItemIndicator>
  </ListBox.Item>
</ListBox>

Accessibility

The ListBox component implements the ARIA listbox pattern and provides:

  • Full keyboard navigation support
  • Proper focus management
  • Support for disabled states
  • Typeahead search functionality

Differences from HeroUI React

  • Built on Kobalte's Listbox, which is data-driven: this port bridges the JSX item API by collecting ListBox.Item and ListBox.Section descriptors and passing them as Kobalte options.
  • Sections render flat: Kobalte's collection has no group wrapper, so ListBox.Section renders as a presentational label between sibling options instead of React Aria's role="group" with an accessible name.
  • Sections are not supported inside a Select yet (its popover marker protocol can't defer the section header).
  • ListBox.ItemIndicator takes plain children instead of a render function — it only mounts while its item is selected (pass forceMount to keep it in the DOM).
  • Virtualization (Virtualizer / ListLayout) is not ported.
  • Selection is reported as Kobalte's Set<string> instead of React Aria's Selection ("all" | Set<Key>), and keys are strings.
  • The React render prop is replaced by Kobalte's as prop.

Last updated: 7/19/26, 3:27 AM

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