Skip to Content
Hashti Design System
Components

ComboBox

Autocomplete input combining a text field with a filtered popup listbox for item lookups.

ComboBox

The ComboBox component merges a single-line text input with a filtered dropdown listbox. Users can either type directly to filter through available options or click the dropdown trigger to browse the full set.

ComboBox is particularly suited for high-density forms, long dataset lookups (such as geographic regions, currencies, or user directories), and cases where custom values can optionally be typed.

Live Interactive ComboBoxFilter architectural elements or type a new item to create it on the fly.

Import

import { ComboBox, ListBoxItem } from "@hashti/react";

Sub-parts for advanced composition:

import {
  ComboBox,
  FieldControl,
  FieldGroupInput,
  FieldButton,
  Popover,
  ListBox,
} from "@hashti/react";

Examples

Basic Autocomplete Filtering

A standard ComboBox that automatically filters suggestions matching the user’s typed input:

import { ComboBox, ListBoxItem } from "@hashti/react";

export function BasicComboBoxDemo() {
  return (
    <div className="max-w-xs">
      <ComboBox label="Persian UNESCO World Heritage Site" placeholder="Type to search...">
        <ListBoxItem id="persepolis">Persepolis (تخت جمشید)</ListBoxItem>
        <ListBoxItem id="naqsh-e-jahan">Naqsh-e Jahan Square (میدان نقش جهان)</ListBoxItem>
        <ListBoxItem id="pasargadae">Pasargadae (پاسارگاد)</ListBoxItem>
        <ListBoxItem id="yazd">Historic City of Yazd (شهر تاریخی یزد)</ListBoxItem>
        <ListBoxItem id="golestan">Golestan Palace (کاخ گلستان)</ListBoxItem>
        <ListBoxItem id="chogha-zanbil">Chogha Zanbil (چغازنبیل)</ListBoxItem>
      </ComboBox>
    </div>
  );
}

Controlled Input & Selection

Track both the typed text (inputValue) and the selected key (selectedKey):

import { useState } from "react";
import { ComboBox, ListBoxItem } from "@hashti/react";
import type { Key } from "react-aria-components";

export function ControlledComboBoxDemo() {
  const [selectedKey, setSelectedKey] = useState<Key | null>(null);
  const [inputValue, setInputValue] = useState("");

  return (
    <div className="max-w-xs flex flex-col gap-3">
      <ComboBox
        label="Select Artisan Workshop"
        placeholder="Search workshops..."
        selectedKey={selectedKey}
        onSelectionChange={setSelectedKey}
        inputValue={inputValue}
        onInputChange={setInputValue}
        description="Choose or type a workshop location."
      >
        <ListBoxItem id="isfahan-tile">Isfahan Glazed Tile Atelier</ListBoxItem>
        <ListBoxItem id="kashan-rose">Kashan Rosewater Distillery</ListBoxItem>
        <ListBoxItem id="shiraz-carpet">Shiraz Nomadic Carpet Loom</ListBoxItem>
        <ListBoxItem id="yazd-silk">Yazd Termeh Silk Weaving</ListBoxItem>
      </ComboBox>
      <div className="text-xs text-gray-500">
        Selected Key: <b>{String(selectedKey ?? "none")}</b> | Input: <b>"{inputValue}"</b>
      </div>
    </div>
  );
}

Allowing Custom Values

Set allowsCustomValue to enable users to type and submit free-form text that isn’t present in the list of suggestions:

import { ComboBox, ListBoxItem } from "@hashti/react";

export function CustomValueComboBoxDemo() {
  return (
    <div className="max-w-xs">
      <ComboBox
        label="Project Tag / Category"
        placeholder="Pick existing or type new..."
        allowsCustomValue
        description="Select an existing architectural category or type your own."
      >
        <ListBoxItem id="restoration">Restoration</ListBoxItem>
        <ListBoxItem id="vernacular">Vernacular</ListBoxItem>
        <ListBoxItem id="sustainability">Sustainability</ListBoxItem>
        <ListBoxItem id="structural">Structural Masonry</ListBoxItem>
      </ComboBox>
    </div>
  );
}

Custom Item Rendering with Descriptions

ListBox items can render custom layouts including secondary titles, status badges, and metadata:

import { ComboBox, ListBoxItem } from "@hashti/react";

interface Member {
  id: string;
  name: string;
  role: string;
  department: string;
}

const MEMBERS: Member[] = [
  { id: "1", name: "Darius Jamshidi", role: "Principal Architect", department: "Restoration" },
  { id: "2", name: "Anahita Moradi", role: "Structural Engineer", department: "Vaulting" },
  { id: "3", name: "Cyrus Tavakoli", role: "Materials Specialist", department: "Ceramics" },
];

export function RichItemComboBoxDemo() {
  return (
    <div className="max-w-sm">
      <ComboBox label="Assign Team Lead" items={MEMBERS} placeholder="Filter by name or role...">
        {(member) => (
          <ListBoxItem id={member.id} textValue={member.name}>
            <div className="flex flex-col py-1">
              <span className="font-semibold text-sm">{member.name}</span>
              <span className="text-xs text-gray-500">
                {member.role} • {member.department}
              </span>
            </div>
          </ListBoxItem>
        )}
      </ComboBox>
    </div>
  );
}

Multi-Selection with Chips or Badges

When selectionMode="multiple" is enabled, users can select several options. The multipleDisplay prop governs how selected choices appear:

  • 'chips' (default): Interactive tags with removable × buttons rendered directly below the input field.
  • 'badge': A compact counter badge showing the number of selected items.
  • 'text': Plain text description.
import { useState } from "react";
import { ComboBox, ListBoxItem } from "@hashti/react";
import type { Selection } from "react-aria-components";

export function MultiComboBoxDemo() {
  const [selected, setSelected] = useState<Selection>(new Set(["fuma", "react"]));

  return (
    <div className="max-w-md space-y-4">
      <ComboBox
        label="Select Frameworks & Tools"
        placeholder="Type or pick multiple tools..."
        selectionMode="multiple"
        multipleDisplay="chips"
        selectedKeys={selected}
        onSelectionChange={setSelected}
      >
        <ListBoxItem id="react">React 19</ListBoxItem>
        <ListBoxItem id="astro">Astro v5</ListBoxItem>
        <ListBoxItem id="fuma">Fumadocs</ListBoxItem>
        <ListBoxItem id="tailwind">Tailwind CSS v4</ListBoxItem>
        <ListBoxItem id="bun">Bun Runtime</ListBoxItem>
      </ComboBox>
    </div>
  );
}

Validation Error State

import { ComboBox, ListBoxItem } from "@hashti/react";

export function InvalidComboBoxDemo() {
  return (
    <div className="max-w-xs">
      <ComboBox
        label="Geographic Zone"
        placeholder="Select zone..."
        isInvalid
        errorMessage="Please select an approved conservation zone."
      >
        <ListBoxItem id="zone-a">Zone A: Central Historic Core</ListBoxItem>
        <ListBoxItem id="zone-b">Zone B: Buffer Perimeter</ListBoxItem>
      </ComboBox>
    </div>
  );
}

Props Reference

PropTypeDefaultDescription
selectionMode'single' | 'multiple''single'Allowed selection mode (single item or multi-select).
multipleDisplay'chips' | 'badge' | 'text''chips'Display format for multi-selected items in the combobox.
itemsIterable<T>—Dynamic list of items for autocomplete.
selectedKeyKey | null—Currently selected item key (controlled).
defaultSelectedKeyKey—Initially selected item key (uncontrolled).
onSelectionChange(key: Key | null) => void—Callback fired when an item is selected.
inputValuestring—Text content within the input field (controlled).
defaultInputValuestring—Default text content for the input field.
onInputChange(value: string) => void—Callback fired as the user types into the input.
allowsCustomValuebooleanfalseWhen true, accepts free-form text outside the suggested options.
placeholderstring—Placeholder text when the field is empty.
labelReactNode—Accessible label for the input.
descriptionReactNode—Explanatory text beneath or above the control.
errorMessageReactNode—Error text shown when isInvalid is true.
size'sm' | 'md' | 'lg''md'Height and control sizing.
isDisabledbooleanfalseWhether the entire combobox is disabled.
isInvalidbooleanfalseWhether validation has failed.
isRequiredbooleanfalseMarks the combobox as mandatory for submission.
disabledKeysIterable<Key>—Keys of specific options that should be disabled.

Accessibility

ComboBox complies with the WAI-ARIA 1.2 Combobox with Listbox Popup design pattern:

  • Keyboard Interaction:
    • ↓ opens the popup listbox and focuses the first item.
    • ↑ and ↓ navigate between matching suggestions.
    • Enter commits the highlighted suggestion into the input and closes the popup.
    • Esc closes the dropdown without modifying the input or clears the field.
    • Alt + ↓ or clicking the disclosure button toggles the dropdown list.
  • ARIA Structure:
    • The text input has role="combobox", aria-autocomplete="list", aria-expanded="false|true", and aria-controls referencing the popup listbox.
    • aria-activedescendant communicates the currently focused suggestion to assistive software without moving DOM focus out of the input.
  • Screen Reader Support: When options filter as the user types, screen readers announce the number of matching items automatically.

Keyboard Interactions

KeyAction
↓Opens popup listbox or navigates to the next option.
↑Navigates to the previous option in the listbox.
EnterSelects the active option and closes the dropdown list.
EscCloses the filtered popup listbox and retains input text.
TabSelects active match or moves focus to the next field.

WAI-ARIA & Data Attributes

AttributeTargetDescription
[data-hovered]Input, ItemApplied when pointer hovers over the input or item.
[data-focused]Input, ItemActive when the element holds keyboard focus.
[data-selected]ItemPresent when an option is the current selected value.
[data-disabled]Input, ItemPresent when interaction is disabled.