Skip to content

Combobox

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

Source Code

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.

import ilha from "ilha";
import { Combobox } from "areia";

export default ilha.render(() => (
  <Combobox
    id="fruit"
    label="Fruit"
    placeholder="Search fruit..."
    items={{
      apple: "Apple",
      banana: "Banana",
      cherry: "Cherry",
      date: "Date",
      elderberry: "Elderberry",
    }}
  />
));

Import

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.

import ilha from "ilha";
import { Combobox } from "areia";

export default ilha.render(() => (
  <Combobox
    id="ilha-topic"
    label="Ilha topic"
    placeholder="Search Ilha topics..."
    items={{
      islands: "Islands",
      signals: "Signals",
      jsx: "JSX rendering",
      html: "html literals",
      hydration: "Hydration",
    }}
  />
));

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.

import ilha from "ilha";
import { Combobox } from "areia";

export default ilha.render(() => (
  <Combobox
    id="basic-fruit"
    label="Fruit"
    placeholder="Choose a fruit"
    items={{
      apple: "Apple",
      banana: "Banana",
      cherry: "Cherry",
      date: "Date",
      elderberry: "Elderberry",
    }}
  />
));

Sizes

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

import ilha from "ilha";
import { Combobox } from "areia";

const items = {
  apple: "Apple",
  banana: "Banana",
  cherry: "Cherry",
};

export default ilha.render(() => (
  <div class="flex w-full max-w-sm flex-col gap-3">
    <Combobox
      size="xs"
      placeholder="Extra small"
      items={items}
    />
    <Combobox size="sm" placeholder="Small" items={items} />
    <Combobox size="base" placeholder="Base" items={items} />
    <Combobox size="lg" placeholder="Large" items={items} />
  </div>
));

With Description

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

Choose the region closest to your users.

import ilha from "ilha";
import { Combobox } from "areia";

export default ilha.render(() => (
  <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",
    }}
  />
));

With Error

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

Select a database before continuing.
import ilha from "ilha";
import { Combobox } from "areia";

export default ilha.render(() => (
  <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",
    }}
  />
));

Disabled

Set disabled to prevent interaction.

import ilha from "ilha";
import { Combobox } from "areia";

export default ilha.render(() => (
  <Combobox
    label="Fruit"
    placeholder="Search fruit..."
    disabled
    items={{
      apple: "Apple",
      banana: "Banana",
      cherry: "Cherry",
    }}
  />
));

Disabled Items

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

import ilha from "ilha";
import { Combobox } from "areia";

export default ilha.render(() => (
  <Combobox
    id="plans"
    label="Plan"
    placeholder="Search plans..."
    items={{
      free: "Free",
      pro: "Pro",
      business: { label: "Business", disabled: true },
      enterprise: { label: "Enterprise", disabled: true },
    }}
  />
));

Default Value

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

import ilha from "ilha";
import { Combobox } from "areia";

export default ilha.render(() => (
  <Combobox
    id="default-database"
    label="Database"
    defaultValue="postgres"
    items={{
      postgres: "PostgreSQL",
      mysql: "MySQL",
      mongodb: "MongoDB",
      redis: "Redis",
    }}
  />
));

Form Integration

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

import ilha from "ilha";
import { Button, Combobox } from "areia";

export default ilha.render(() => (
  <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>
));

Custom Items

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

import ilha from "ilha";
import { Combobox } from "areia";

export default ilha.render(() => (
  <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>
));

Grouped Items

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

import ilha from "ilha";
import { Combobox } from "areia";

export default ilha.render(() => (
  <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>
));

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.

import ilha from "ilha";
import { Combobox } from "areia";

const items = [
  <Combobox.Item value="go" label="Go" children="🐹 Go" />,
  <Combobox.Item
    value="rust"
    label="Rust"
    children="🦀 Rust"
  />,
  <Combobox.Item
    value="ts"
    label="TypeScript"
    children="🔷 TypeScript"
  />,
];

export default ilha.render(() => (
  <Combobox
    id="language-inside"
    label="Language"
    placeholder="Select language"
  >
    <Combobox.TriggerValue placeholder="Select language" />
    <Combobox.Content>
      <Combobox.Input placeholder="Search languages..." />
      <Combobox.List>
        <Combobox.Empty />
        {items}
      </Combobox.List>
    </Combobox.Content>
  </Combobox>
));

Open on Focus and Auto Highlight

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

import ilha from "ilha";
import { Combobox } from "areia";

export default ilha.render(() => (
  <Combobox
    id="quick-search"
    label="Quick search"
    placeholder="Start typing..."
    openOnFocus
    autoHighlight
    items={{
      dashboard: "Dashboard",
      settings: "Settings",
      billing: "Billing",
      support: "Support",
    }}
  />
));

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.

import ilha from "ilha";
import { Combobox } from "areia";

export default ilha.render(() => (
  <Combobox
    id="autocomplete"
    label="Language"
    placeholder="Start typing..."
    openOnFocus
    items={{
      rust: "Rust",
      ruby: "Ruby",
      react: "React",
      python: "Python",
      php: "PHP",
      perl: "Perl",
    }}
  />
));

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

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.

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

import ilha from "ilha";
import { Combobox } from "areia";

const items = {
  one: "One",
  two: "Two",
  three: "Three",
  four: "Four",
  five: "Five",
};

export default ilha.render(() => (
  <Combobox
    id="short-dropdown"
    label="Short dropdown"
    placeholder="Search..."
  >
    <Combobox.TriggerInput placeholder="Search..." />
    <Combobox.Content class="max-h-40">
      <Combobox.List>
        <Combobox.Empty />
        {Object.entries(items).map(([value, label]) => (
          <Combobox.Item value={value}>{label}</Combobox.Item>
        ))}
      </Combobox.List>
    </Combobox.Content>
  </Combobox>
));

API Reference

Combobox

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

PropTypeDefaultDescription
classstring-Additional CSS classes applied to the combobox root.
classNamestring-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.
labelunknown-Label content. Enables the field wrapper.
labelTooltipstring-Tooltip text rendered as a native title on the label.
descriptionunknown-Helper text displayed below the combobox.
errorunknown | { message: unknown; match?: unknown }-Error message. When truthy, error styling is automatically applied.
itemsRecord<string, unknown | { label: unknown; disabled?: boolean }> | ComboboxItemDescriptor[]-Items rendered as selectable options. Object keys are option values.
childrenunknown-Explicit combobox content. Prefer the second Combobox argument.
placeholderstring-Placeholder text for the trigger input or trigger value.
defaultValuestring-Initial selected value.
defaultOpenbooleanfalseInitial open state.
disabledbooleanfalseDisable interaction.
requiredbooleanfalseMark the field as required. When false with a label, shows optional text.
namestring-Form field name. Creates a hidden input for submission.
openOnFocusbooleantrueOpen the popup when the input receives intentional focus.
autoHighlightbooleanfalseAuto-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.
sideOffsetnumber4Distance from the trigger in pixels.
alignOffsetnumber0Offset from the aligned edge in pixels.
avoidCollisionsbooleantrueAdjust popup position to stay inside the viewport.
collisionPaddingnumber8Viewport edge padding used for collision detection.

Combobox.Item

Individual selectable option.

PropTypeDefaultDescription
valuestring-Submitted and selected value for the item.
childrenunknown-Visible item content.
labelstring-Text used for filtering when children are custom.
disabledboolean-Whether the item cannot be selected.
classstring-Additional CSS classes.
classNamestring-Alias for class.

Additional Sub-components

ComponentDescription
Combobox.TriggerInputInput trigger with clear and dropdown buttons.
Combobox.TriggerValueButton trigger that displays the selected value.
Combobox.ContentPopup container for the input and list.
Combobox.InputSearch input for popup-input composition.
Combobox.ListScrollable list wrapper.
Combobox.EmptyEmpty state shown when no items match.
Combobox.GroupGroup container for related items.
Combobox.GroupLabelVisible label for a group.
Combobox.SeparatorVisual separator between groups/items.
Combobox.ChipVisual chip helper for custom multi-value interfaces.

Accessibility

Keyboard Navigation

KeyAction
ArrowDownOpen the popup when closed, or move to the next visible item.
ArrowUpOpen the popup when closed, or move to the previous item.
HomeMove to the first visible item.
EndMove to the last visible item.
EnterSelect the highlighted item.
EscapeClose the popup, or clear the selected value when closed.
TabClose 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.