---
title: Combobox
description: A searchable select component for filtering and choosing from a list of options.
order: 9
tags: [components]
---

import { Preview } from "$lib/components/preview";
import { Button, Combobox } from "areia";

# Combobox

A searchable select component for filtering and choosing from a list of options.

[Source Code](https://github.com/ilhajs/areia/blob/main/packages/areia/src/components/combobox/index.ts)

Areia's `Combobox` is built on `@data-slot/combobox` and rendered as an Ilha island when you use `Combobox`. It supports keyboard navigation, filtering, form integration, disabled options, grouped items, and field helpers for labels, descriptions, and errors.

<Preview
  code={
    'import ilha from "ilha";\nimport { Combobox } from "areia";\n\nexport default ilha.render(() => (\n  <Combobox\n    id="fruit"\n    label="Fruit"\n    placeholder="Search fruit..."\n    items={{\n      apple: "Apple",\n      banana: "Banana",\n      cherry: "Cherry",\n      date: "Date",\n      elderberry: "Elderberry",\n    }}\n  />\n));'
  }
  lang="tsx"
>
  <Combobox
    id="fruit"
    label="Fruit"
    placeholder="Search fruit..."
    items={{
      apple: "Apple",
      banana: "Banana",
      cherry: "Cherry",
      date: "Date",
      elderberry: "Elderberry",
    }}
  />
</Preview>

## Import

```ts
import { Combobox } from "areia";
```

## Usage

Call `Combobox(...)` for an interactive island. The root renders the combobox markup and initializes the `@data-slot/combobox` controller after mount.

<Preview
  code={
    'import ilha from "ilha";\nimport { Combobox } from "areia";\n\nexport default ilha.render(() => (\n  <Combobox\n    id="ilha-topic"\n    label="Ilha topic"\n    placeholder="Search Ilha topics..."\n    items={{\n      islands: "Islands",\n      signals: "Signals",\n      jsx: "JSX rendering",\n      html: "html literals",\n      hydration: "Hydration",\n    }}\n  />\n));'
  }
  lang="tsx"
>
  <Combobox
    id="ilha-topic"
    label="Ilha topic"
    placeholder="Search Ilha topics..."
    items={{
      islands: "Islands",
      signals: "Signals",
      jsx: "JSX rendering",
      html: "html literals",
      hydration: "Hydration",
    }}
  />
</Preview>

For static server-rendered markup without mounting behavior, use `Combobox.Static(...)`. For most application usage, call `Combobox(...)`.

## Examples

### Basic

A combobox with a visible label and searchable options.

<Preview
  code={
    'import ilha from "ilha";\nimport { Combobox } from "areia";\n\nexport default ilha.render(() => (\n  <Combobox\n    id="basic-fruit"\n    label="Fruit"\n    placeholder="Choose a fruit"\n    items={{\n      apple: "Apple",\n      banana: "Banana",\n      cherry: "Cherry",\n      date: "Date",\n      elderberry: "Elderberry",\n    }}\n  />\n));'
  }
  lang="tsx"
>
  <Combobox
    id="basic-fruit"
    label="Fruit"
    placeholder="Choose a fruit"
    items={{
      apple: "Apple",
      banana: "Banana",
      cherry: "Cherry",
      date: "Date",
      elderberry: "Elderberry",
    }}
  />
</Preview>

### Sizes

Use the `size` prop to match Input sizing: `xs`, `sm`, `base`, and `lg`.

<Preview
  code={
    'import ilha from "ilha";\nimport { Combobox } from "areia";\n\nconst items = {\n  apple: "Apple",\n  banana: "Banana",\n  cherry: "Cherry",\n};\n\nexport default ilha.render(() => (\n  <div class="flex w-full max-w-sm flex-col gap-3">\n    <Combobox\n      size="xs"\n      placeholder="Extra small"\n      items={items}\n    />\n    <Combobox size="sm" placeholder="Small" items={items} />\n    <Combobox size="base" placeholder="Base" items={items} />\n    <Combobox size="lg" placeholder="Large" items={items} />\n  </div>\n));'
  }
  lang="tsx"
  codeOnly
/>

### With Description

The `label`, `description`, and `error` props enable the built-in field wrapper.

<Preview
  code={
    'import ilha from "ilha";\nimport { Combobox } from "areia";\n\nexport default ilha.render(() => (\n  <Combobox\n    id="region"\n    label="Region"\n    description="Choose the region closest to your users."\n    placeholder="Search regions..."\n    items={{\n      iad: "Washington, D.C.",\n      sfo: "San Francisco",\n      lhr: "London",\n      fra: "Frankfurt",\n      nrt: "Tokyo",\n    }}\n  />\n));'
  }
  lang="tsx"
>
  <Combobox
    id="region"
    label="Region"
    description="Choose the region closest to your users."
    placeholder="Search regions..."
    items={{
      iad: "Washington, D.C.",
      sfo: "San Francisco",
      lhr: "London",
      fra: "Frankfurt",
      nrt: "Tokyo",
    }}
  />
</Preview>

### With Error

Pass `error` as a string for simple validation messages. Error styling is automatically applied when `error` is truthy.

<Preview
  code={
    'import ilha from "ilha";\nimport { Combobox } from "areia";\n\nexport default ilha.render(() => (\n  <Combobox\n    id="database-error"\n    label="Database"\n    required\n    error="Select a database before continuing."\n    placeholder="Search databases..."\n    items={{\n      postgres: "PostgreSQL",\n      mysql: "MySQL",\n      mongodb: "MongoDB",\n      redis: "Redis",\n    }}\n  />\n));'
  }
  lang="tsx"
>
  <Combobox
    id="database-error"
    label="Database"
    required
    error="Select a database before continuing."
    placeholder="Search databases..."
    items={{
      postgres: "PostgreSQL",
      mysql: "MySQL",
      mongodb: "MongoDB",
      redis: "Redis",
    }}
  />
</Preview>

### Disabled

Set `disabled` to prevent interaction.

<Preview
  code={
    'import ilha from "ilha";\nimport { Combobox } from "areia";\n\nexport default ilha.render(() => (\n  <Combobox\n    label="Fruit"\n    placeholder="Search fruit..."\n    disabled\n    items={{\n      apple: "Apple",\n      banana: "Banana",\n      cherry: "Cherry",\n    }}\n  />\n));'
  }
  lang="tsx"
>
  <Combobox
    label="Fruit"
    placeholder="Search fruit..."
    disabled
    items={{
      apple: "Apple",
      banana: "Banana",
      cherry: "Cherry",
    }}
  />
</Preview>

### Disabled Items

Use item descriptor objects to disable individual options. Disabled rows are skipped during keyboard navigation and cannot be selected.

<Preview
  code={
    'import ilha from "ilha";\nimport { Combobox } from "areia";\n\nexport default ilha.render(() => (\n  <Combobox\n    id="plans"\n    label="Plan"\n    placeholder="Search plans..."\n    items={{\n      free: "Free",\n      pro: "Pro",\n      business: { label: "Business", disabled: true },\n      enterprise: { label: "Enterprise", disabled: true },\n    }}\n  />\n));'
  }
  lang="tsx"
>
  <Combobox
    id="plans"
    label="Plan"
    placeholder="Search plans..."
    items={{
      free: "Free",
      pro: "Pro",
      business: { label: "Business", disabled: true },
      enterprise: { label: "Enterprise", disabled: true },
    }}
  />
</Preview>

### Default Value

Use `defaultValue` with the option value string to preselect an item.

<Preview
  code={
    'import ilha from "ilha";\nimport { Combobox } from "areia";\n\nexport default ilha.render(() => (\n  <Combobox\n    id="default-database"\n    label="Database"\n    defaultValue="postgres"\n    items={{\n      postgres: "PostgreSQL",\n      mysql: "MySQL",\n      mongodb: "MongoDB",\n      redis: "Redis",\n    }}\n  />\n));'
  }
  lang="tsx"
>
  <Combobox
    id="default-database"
    label="Database"
    defaultValue="postgres"
    items={{
      postgres: "PostgreSQL",
      mysql: "MySQL",
      mongodb: "MongoDB",
      redis: "Redis",
    }}
  />
</Preview>

### Form Integration

Set `name` to create a hidden input that submits the selected value with a native form.

<Preview
  code={
    'import ilha from "ilha";\nimport { Button, Combobox } from "areia";\n\nexport default ilha.render(() => (\n  <form class="flex w-full max-w-sm flex-col gap-3">\n    <Combobox\n      id="form-fruit"\n      name="fruit"\n      label="Fruit"\n      required\n      placeholder="Search fruit..."\n      items={{\n        apple: "Apple",\n        banana: "Banana",\n        cherry: "Cherry",\n      }}\n    />\n    <Button type="submit">Submit</Button>\n  </form>\n));'
  }
  lang="tsx"
>
  <form class="flex w-full max-w-sm flex-col gap-3">
    <Combobox
      id="form-fruit"
      name="fruit"
      label="Fruit"
      required
      placeholder="Search fruit..."
      items={{
        apple: "Apple",
        banana: "Banana",
        cherry: "Cherry",
      }}
    />
    <Button type="submit">Submit</Button>
  </form>
</Preview>

### Custom Items

Use explicit child markup when you need custom labels, grouping, separators, or item attributes.

<Preview
  code={
    'import ilha from "ilha";\nimport { Combobox } from "areia";\n\nexport default ilha.render(() => (\n  <Combobox\n    id="custom-items"\n    label="Runtime"\n    placeholder="Search runtimes..."\n  >\n    <Combobox.Item value="bun" label="Bun">\n      <span class="font-medium">Bun</span>\n      <span class="text-areia-subtle"> JavaScript runtime</span>\n    </Combobox.Item>\n    <Combobox.Item value="node" label="Node.js">\n      <span class="font-medium">Node.js</span>\n      <span class="text-areia-subtle"> JavaScript runtime</span>\n    </Combobox.Item>\n    <Combobox.Item value="deno" label="Deno">\n      <span class="font-medium">Deno</span>\n      <span class="text-areia-subtle"> TypeScript runtime</span>\n    </Combobox.Item>\n  </Combobox>\n));'
  }
  lang="tsx"
>
  <Combobox
    id="custom-items"
    label="Runtime"
    placeholder="Search runtimes..."
  >
    <Combobox.Item value="bun" label="Bun">
      <span class="font-medium">Bun</span>
      <span class="text-areia-subtle"> JavaScript runtime</span>
    </Combobox.Item>
    <Combobox.Item value="node" label="Node.js">
      <span class="font-medium">Node.js</span>
      <span class="text-areia-subtle"> JavaScript runtime</span>
    </Combobox.Item>
    <Combobox.Item value="deno" label="Deno">
      <span class="font-medium">Deno</span>
      <span class="text-areia-subtle"> TypeScript runtime</span>
    </Combobox.Item>
  </Combobox>
</Preview>

### Grouped Items

Use `Combobox.Group` and `Combobox.GroupLabel` to organize related options.

<Preview
  code={
    'import ilha from "ilha";\nimport { Combobox } from "areia";\n\nexport default ilha.render(() => (\n  <Combobox\n    id="grouped-location"\n    label="Location"\n    placeholder="Search locations..."\n  >\n    <Combobox.Group>\n      <Combobox.GroupLabel>North America</Combobox.GroupLabel>\n      <Combobox.Item value="iad">\n        Washington, D.C.\n      </Combobox.Item>\n      <Combobox.Item value="sfo">San Francisco</Combobox.Item>\n    </Combobox.Group>\n    <Combobox.Group>\n      <Combobox.GroupLabel>Europe</Combobox.GroupLabel>\n      <Combobox.Item value="lhr">London</Combobox.Item>\n      <Combobox.Item value="fra">Frankfurt</Combobox.Item>\n    </Combobox.Group>\n  </Combobox>\n));'
  }
  lang="tsx"
>
  <Combobox
    id="grouped-location"
    label="Location"
    placeholder="Search locations..."
  >
    <Combobox.Group>
      <Combobox.GroupLabel>North America</Combobox.GroupLabel>
      <Combobox.Item value="iad">
        Washington, D.C.
      </Combobox.Item>
      <Combobox.Item value="sfo">San Francisco</Combobox.Item>
    </Combobox.Group>
    <Combobox.Group>
      <Combobox.GroupLabel>Europe</Combobox.GroupLabel>
      <Combobox.Item value="lhr">London</Combobox.Item>
      <Combobox.Item value="fra">Frankfurt</Combobox.Item>
    </Combobox.Group>
  </Combobox>
</Preview>

### Search Input Inside Popup

Use the low-level parts to render a select-like trigger with a separate search input inside the popup. This composition returns static markup; initialize it with `createCombobox` yourself if you are not using `Combobox`.

<Preview
  code={
    'import ilha from "ilha";\nimport { Combobox } from "areia";\n\nconst items = [\n  <Combobox.Item value="go" label="Go" children="🐹 Go" />,\n  <Combobox.Item\n    value="rust"\n    label="Rust"\n    children="🦀 Rust"\n  />,\n  <Combobox.Item\n    value="ts"\n    label="TypeScript"\n    children="🔷 TypeScript"\n  />,\n];\n\nexport default ilha.render(() => (\n  <Combobox\n    id="language-inside"\n    label="Language"\n    placeholder="Select language"\n  >\n    <Combobox.TriggerValue placeholder="Select language" />\n    <Combobox.Content>\n      <Combobox.Input placeholder="Search languages..." />\n      <Combobox.List>\n        <Combobox.Empty />\n        {items}\n      </Combobox.List>\n    </Combobox.Content>\n  </Combobox>\n));'
  }
  lang="tsx"
  codeOnly
/>

### Open on Focus and Auto Highlight

`openOnFocus` opens the popup when the input receives intentional focus. `autoHighlight` highlights the first matching item after typing.

<Preview
  code={
    'import ilha from "ilha";\nimport { Combobox } from "areia";\n\nexport default ilha.render(() => (\n  <Combobox\n    id="quick-search"\n    label="Quick search"\n    placeholder="Start typing..."\n    openOnFocus\n    autoHighlight\n    items={{\n      dashboard: "Dashboard",\n      settings: "Settings",\n      billing: "Billing",\n      support: "Support",\n    }}\n  />\n));'
  }
  lang="tsx"
>
  <Combobox
    id="quick-search"
    label="Quick search"
    placeholder="Start typing..."
    openOnFocus
    autoHighlight
    items={{
      dashboard: "Dashboard",
      settings: "Settings",
      billing: "Billing",
      support: "Support",
    }}
  />
</Preview>

### Autocomplete Behavior

Combobox supports autocomplete-style filtering natively. As the user types, the dropdown filters visible items automatically using the built-in substring matcher. Provide `items` and the controller handles narrowing without re-rendering.

<Preview
  code={
    'import ilha from "ilha";\nimport { Combobox } from "areia";\n\nexport default ilha.render(() => (\n  <Combobox\n    id="autocomplete"\n    label="Language"\n    placeholder="Start typing..."\n    openOnFocus\n    items={{\n      rust: "Rust",\n      ruby: "Ruby",\n      react: "React",\n      python: "Python",\n      php: "PHP",\n      perl: "Perl",\n    }}\n  />\n));'
  }
  lang="tsx"
>
  <Combobox
    id="autocomplete"
    label="Language"
    placeholder="Start typing..."
    openOnFocus
    items={{
      rust: "Rust",
      ruby: "Ruby",
      react: "React",
      python: "Python",
      php: "PHP",
      perl: "Perl",
    }}
  />
</Preview>

For a custom filter, pass a `filter` function instead of re-rendering with filtered items.

```ts
filter={(inputValue, itemValue, itemLabel) =>
  itemLabel.toLowerCase().startsWith(inputValue.toLowerCase())
}
```

Use `onInputValueChange` to read the typed text for side effects like remote fetching. Do not use it to drive re-renders of the item list — the controller already filters internally.

For free-form values that do not need to match the list, use `itemToStringValue` to control how the selected text is displayed, or treat the input value as the committed value when the user submits the form without selecting an item.

### Dropdown Height

`Combobox.Content` defaults to `24rem` or the available viewport height, whichever is smaller. Pass `class` or `className` when composing the low-level content yourself.

<Preview
  code={
    'import ilha from "ilha";\nimport { Combobox } from "areia";\n\nconst items = {\n  one: "One",\n  two: "Two",\n  three: "Three",\n  four: "Four",\n  five: "Five",\n};\n\nexport default ilha.render(() => (\n  <Combobox\n    id="short-dropdown"\n    label="Short dropdown"\n    placeholder="Search..."\n  >\n    <Combobox.TriggerInput placeholder="Search..." />\n    <Combobox.Content class="max-h-40">\n      <Combobox.List>\n        <Combobox.Empty />\n        {Object.entries(items).map(([value, label]) => (\n          <Combobox.Item value={value}>{label}</Combobox.Item>\n        ))}\n      </Combobox.List>\n    </Combobox.Content>\n  </Combobox>\n));'
  }
  lang="tsx"
  codeOnly
/>

## API Reference

### Combobox

`Combobox` is an Ilha island. It accepts standard HTML `div` attributes plus `@data-slot/combobox` options and Areia field props.

| Prop                 | Type                                                                                            | Default    | Description                                                                 |
| -------------------- | ----------------------------------------------------------------------------------------------- | ---------- | --------------------------------------------------------------------------- |
| `class`              | `string`                                                                                        | -          | Additional CSS classes applied to the combobox root.                        |
| `className`          | `string`                                                                                        | -          | Alias for `class`.                                                          |
| `size`               | `"xs" \| "sm" \| "base" \| "lg"`                                                                | `"base"`   | Size of the trigger input. Matches Input component sizes.                   |
| `inputSide`          | `"right" \| "top"`                                                                              | `"right"`  | Styling hook for chip/input layouts.                                        |
| `label`              | `unknown`                                                                                       | -          | Label content. Enables the field wrapper.                                   |
| `labelTooltip`       | `string`                                                                                        | -          | Tooltip text rendered as a native `title` on the label.                     |
| `description`        | `unknown`                                                                                       | -          | Helper text displayed below the combobox.                                   |
| `error`              | `unknown \| { message: unknown; match?: unknown }`                                              | -          | Error message. When truthy, error styling is automatically applied.         |
| `items`              | `Record<string, unknown \| { label: unknown; disabled?: boolean }> \| ComboboxItemDescriptor[]` | -          | Items rendered as selectable options. Object keys are option values.        |
| `children`           | `unknown`                                                                                       | -          | Explicit combobox content. Prefer the second `Combobox` argument.           |
| `placeholder`        | `string`                                                                                        | -          | Placeholder text for the trigger input or trigger value.                    |
| `defaultValue`       | `string`                                                                                        | -          | Initial selected value.                                                     |
| `defaultOpen`        | `boolean`                                                                                       | `false`    | Initial open state.                                                         |
| `disabled`           | `boolean`                                                                                       | `false`    | Disable interaction.                                                        |
| `required`           | `boolean`                                                                                       | `false`    | Mark the field as required. When `false` with a label, shows optional text. |
| `name`               | `string`                                                                                        | -          | Form field name. Creates a hidden input for submission.                     |
| `openOnFocus`        | `boolean`                                                                                       | `true`     | Open the popup when the input receives intentional focus.                   |
| `autoHighlight`      | `boolean`                                                                                       | `false`    | Auto-highlight the first visible item after typing.                         |
| `filter`             | `(inputValue: string, itemValue: string, itemLabel: string) => boolean`                         | -          | Custom filter function. Return `true` to show an item.                      |
| `itemToStringValue`  | `(item: HTMLElement \| null, value: string \| null) => string`                                  | -          | Custom selected-value text resolver.                                        |
| `onValueChange`      | `(value: string \| null) => void`                                                               | -          | Called when selection changes.                                              |
| `onOpenChange`       | `(open: boolean) => void`                                                                       | -          | Called when popup opens or closes.                                          |
| `onInputValueChange` | `(inputValue: string) => void`                                                                  | -          | Called when the user types in the input.                                    |
| `side`               | `"top" \| "bottom"`                                                                             | `"bottom"` | Preferred popup side.                                                       |
| `align`              | `"start" \| "center" \| "end"`                                                                  | `"start"`  | Popup alignment relative to the trigger.                                    |
| `sideOffset`         | `number`                                                                                        | `4`        | Distance from the trigger in pixels.                                        |
| `alignOffset`        | `number`                                                                                        | `0`        | Offset from the aligned edge in pixels.                                     |
| `avoidCollisions`    | `boolean`                                                                                       | `true`     | Adjust popup position to stay inside the viewport.                          |
| `collisionPadding`   | `number`                                                                                        | `8`        | Viewport edge padding used for collision detection.                         |

### Combobox.Item

Individual selectable option.

| Prop        | Type      | Default | Description                                       |
| ----------- | --------- | ------- | ------------------------------------------------- |
| `value`     | `string`  | -       | Submitted and selected value for the item.        |
| `children`  | `unknown` | -       | Visible item content.                             |
| `label`     | `string`  | -       | Text used for filtering when children are custom. |
| `disabled`  | `boolean` | -       | Whether the item cannot be selected.              |
| `class`     | `string`  | -       | Additional CSS classes.                           |
| `className` | `string`  | -       | Alias for `class`.                                |

### Additional Sub-components

| Component               | Description                                           |
| ----------------------- | ----------------------------------------------------- |
| `Combobox.TriggerInput` | Input trigger with clear and dropdown buttons.        |
| `Combobox.TriggerValue` | Button trigger that displays the selected value.      |
| `Combobox.Content`      | Popup container for the input and list.               |
| `Combobox.Input`        | Search input for popup-input composition.             |
| `Combobox.List`         | Scrollable list wrapper.                              |
| `Combobox.Empty`        | Empty state shown when no items match.                |
| `Combobox.Group`        | Group container for related items.                    |
| `Combobox.GroupLabel`   | Visible label for a group.                            |
| `Combobox.Separator`    | Visual separator between groups/items.                |
| `Combobox.Chip`         | Visual chip helper for custom multi-value interfaces. |

## Accessibility

### Keyboard Navigation

| Key         | Action                                                        |
| ----------- | ------------------------------------------------------------- |
| `ArrowDown` | Open the popup when closed, or move to the next visible item. |
| `ArrowUp`   | Open the popup when closed, or move to the previous item.     |
| `Home`      | Move to the first visible item.                               |
| `End`       | Move to the last visible item.                                |
| `Enter`     | Select the highlighted item.                                  |
| `Escape`    | Close the popup, or clear the selected value when closed.     |
| `Tab`       | Close the popup and continue normal tab navigation.           |

### Label Requirement

Comboboxes should have an accessible name via one of:

- `label` prop
- `aria-label` on a custom `Combobox.TriggerInput`
- `aria-labelledby` for custom label association

### Form Behavior

When `name` is provided, the controller creates a hidden input with the selected value so the combobox participates in native form submission.

### Error Association

When an `id` is provided, descriptions and error messages are automatically associated with the combobox field wrapper using `aria-describedby`.
