# List Box

Choose one item from a visible list.

Workspace list with descriptions

[Complete TSX file](/examples/list-box/default.tsx)

```tsx
"use client";

import { ListBox, ListBoxItem } from "@/siteplane/components/list-box";

const workspaces = [
  {
    value: "personal",
    title: "Personal",
    description: "Private drafts and experiments",
  },
  {
    value: "team",
    title: "Team",
    description: "Shared projects and billing",
  },
  {
    value: "organization",
    title: "Organization",
    description: "Company-wide members and settings",
  },
];

export default function Example() {
  return (
    <ListBox aria-label="Workspace" className="max-w-sm" defaultValue="team">
      {workspaces.map((workspace) => (
        <ListBoxItem
          key={workspace.value}
          textValue={workspace.title}
          value={workspace.value}
        >
          <span className="grid min-w-0 gap-0.5 text-left">
            <span className="font-medium">{workspace.title}</span>
            <span className="text-muted-foreground text-xs">
              {workspace.description}
            </span>
          </span>
        </ListBoxItem>
      ))}
    </ListBox>
  );
}
```

## Installation

Complete [one-time setup](/docs/installation/) first.

```bash
pnpm dlx shadcn@4.18.0 add @siteplane/list-box
```

With npm: `npx shadcn@4.18.0 add @siteplane/list-box`. [Registry JSON](/registry/list-box.json) lists source files and dependencies.

For this page’s examples, also install:

```bash
pnpm dlx shadcn@4.18.0 add @siteplane/input
```

Field is installed with Input.

## Usage

```tsx
import { ListBox, ListBoxItem } from "@/siteplane/components/list-box";

<ListBox aria-label="Workspace">
  <ListBoxItem value="design">Design</ListBoxItem>
</ListBox>;
```

## Composition

```text
ListBox
└── ListBoxItem
```

## Controlled selection

Control the selection with `value` and `onValueChange`; both use the item’s string `value`, not React Aria `selectedKeys`. Name the list with a visible `FieldLabel` and point `aria-labelledby` at it; disabled items stay visible but can’t be selected.

Controlled workspace list

[Complete TSX file](/examples/list-box/controlled.tsx)

```tsx
"use client";

import { Field, FieldLabel } from "@/siteplane/components/field";
import { ListBox, ListBoxItem } from "@/siteplane/components/list-box";
import { useId, useState } from "react";

export default function Example() {
  const labelId = useId();
  const [value, setValue] = useState("design");
  return (
    <Field className="w-full max-w-sm">
      <FieldLabel id={labelId}>Workspace</FieldLabel>
      <ListBox aria-labelledby={labelId} value={value} onValueChange={setValue}>
        <ListBoxItem value="design">Design</ListBoxItem>
        <ListBoxItem value="engineering">Engineering</ListBoxItem>
        <ListBoxItem value="archive" disabled>
          Archive
        </ListBoxItem>
      </ListBox>
    </Field>
  );
}
```

## Row selection

Both appearances fill the selected row. The default, `check`, also shows a check that draws in when a row is selected and out when another row is picked, the way the [Checkbox](/docs/components/checkbox/) check does, and appears instantly under reduced motion. Set `selectionAppearance="row"` to drop the check, so the fill alone marks the selected row.

List with row selection

[Complete TSX file](/examples/list-box/appearance.tsx)

```tsx
"use client";

import { ListBox, ListBoxItem } from "@/siteplane/components/list-box";

export default function Example() {
  return (
    <ListBox
      aria-label="Default view"
      className="max-w-sm"
      defaultValue="board"
      selectionAppearance="row"
    >
      <ListBoxItem value="board">Board</ListBoxItem>
      <ListBoxItem value="list">List</ListBoxItem>
      <ListBoxItem value="calendar">Calendar</ListBoxItem>
    </ListBox>
  );
}
```

## API reference

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| ListBox.value / defaultValue | string | uncontrolled / unset | Selected item value. |
| ListBox.onValueChange | (value: string) => void | unset | Reports the next non-empty selection. |
| ListBox.selectionAppearance | ListBoxSelectionAppearance: check \| row | check | Selected-item treatment. |
| ListBox.size | sm \| md \| lg \| xl | provider / lg | Shared item size. |
| ListBoxItem.value | string | required | Stable item identity. |
| ListBoxItem.textValue | string | derived from children | Plain text for typeahead and rich options. |
| ListBoxItem.disabled | boolean | false | Prevents selection. |
| ListBoxItem.multiline | boolean | true | Allows wrapped content. |
| ListBoxItem.data-slot | string | list-box-item | Slot override. |
ListBoxProps extends native div props excluding role, aria-orientation and
defaultValue. Its React Aria implementation fixes vertical orientation, single
selection, replace behavior, disallowEmptySelection and focus wrapping. It does
not expose multiple selection, selectedKeys or onSelectionChange.
ListBoxItemProps extends ButtonBaseProps but omits align, aria-disabled,
aria-selected, onClick, onKeyDown, role, render, size, tabIndex, value, variant
and width; its own value is a required string. Item must be inside ListBox. The
root owns selection and the item supplies its own ghost Button/React Aria
composition. Both accept string className.

[Read the installed source](/source/list-box).

## Usage notes

Arrow keys navigate the vertical list and typing uses text derived recursively from each item’s children. Give the list an accessible name. For rich items, supply `ListBoxItem.textValue` so typeahead matches only the option title. This component does not submit a native form value; serialize selected state yourself.
