Skip to Content
Hashti Design System
Components

Select

Collapsible menu allowing users to select an option from a predefined list.

Select

The Select component presents a collapsible menu of options, enabling users to choose a single value from a predetermined set. When closed, it displays the currently selected value or a welcoming placeholder; when triggered, it opens a floating listbox styled with Hashti’s serene architectural geometry.

Built on React Aria Components’ Select, it provides full typeahead searching, keyboard navigation, popover collision repositioning, and screen reader announcements.

Live Interactive SelectTest single selection and multi-select chip tags with remove buttons.
Historic Portal City
Glaze Palette (Multi-Select)

Import

import { Select, ListBoxItem, ListBoxSection, SectionHeader } from "@hashti/react";

Sub-parts for advanced composition:

import { Select, FieldLayout, Popover, ListBox } from "@hashti/react";

Examples

Standard Select

A basic select dropdown using static <ListBoxItem> children:

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

export function BasicSelectDemo() {
  return (
    <div className="max-w-xs">
      <Select label="Architectural Style" placeholder="Choose a style">
        <ListBoxItem id="isfahani">Isfahani Style (سبک اصفهانی)</ListBoxItem>
        <ListBoxItem id="razi">Razi Style (سبک رازی)</ListBoxItem>
        <ListBoxItem id="azari">Azari Style (سبک آذری)</ListBoxItem>
        <ListBoxItem id="khorasani">Khorasani Style (سبک خراسانی)</ListBoxItem>
      </Select>
    </div>
  );
}

Dynamic Items Collection

Render options dynamically from an array of objects:

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

interface City {
  id: string;
  name: string;
  province: string;
}

const CITIES: City[] = [
  { id: "isfahan", name: "Isfahan", province: "Isfahan Province" },
  { id: "shiraz", name: "Shiraz", province: "Fars Province" },
  { id: "yazd", name: "Yazd", province: "Yazd Province" },
  { id: "kashan", name: "Kashan", province: "Isfahan Province" },
  { id: "tabriz", name: "Tabriz", province: "East Azerbaijan" },
];

export function DynamicSelectDemo() {
  const [selectedCity, setSelectedCity] = useState<Key>("isfahan");

  return (
    <div className="max-w-xs flex flex-col gap-2">
      <Select
        label="Historical Center"
        items={CITIES}
        selectedKey={selectedCity}
        onSelectionChange={setSelectedCity}
        description="Select your target preservation region."
      >
        {(city) => (
          <ListBoxItem id={city.id} textValue={city.name}>
            <div className="flex flex-col">
              <span className="font-medium">{city.name}</span>
              <span className="text-xs text-gray-500">{city.province}</span>
            </div>
          </ListBoxItem>
        )}
      </Select>
    </div>
  );
}

Disabled Options & Disabled State

You can disable specific individual options using disabledKeys, or disable the entire select component with isDisabled:

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

export function DisabledSelectDemo() {
  return (
    <div className="flex flex-col gap-4 max-w-xs">
      {/* Individual items disabled */}
      <Select label="Room Type" disabledKeys={["penthouse"]} placeholder="Select room">
        <ListBoxItem id="standard">Standard Garden View</ListBoxItem>
        <ListBoxItem id="suite">Courtyard Suite</ListBoxItem>
        <ListBoxItem id="penthouse">Rooftop Pavilion (Sold Out)</ListBoxItem>
      </Select>

      {/* Whole component disabled */}
      <Select label="Booking Season" isDisabled defaultSelectedKey="spring">
        <ListBoxItem id="spring">Spring Nowruz (Bookings Closed)</ListBoxItem>
        <ListBoxItem id="autumn">Autumn</ListBoxItem>
      </Select>
    </div>
  );
}

Validation Error State

When validation fails, pass isInvalid and an errorMessage:

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

export function InvalidSelectDemo() {
  return (
    <div className="max-w-xs">
      <Select
        label="Vault Material"
        placeholder="Choose material..."
        isInvalid
        errorMessage="Selecting a structural vault material is required to proceed."
      >
        <ListBoxItem id="adobe">Sun-dried Adobe</ListBoxItem>
        <ListBoxItem id="fired-brick">Fired Kiln Brick</ListBoxItem>
        <ListBoxItem id="glazed-tile">Glazed Ceramic Tile</ListBoxItem>
      </Select>
    </div>
  );
}

Multi-Selection with Chips, Badges, or Text

Set selectionMode="multiple" to enable picking several options. The multipleDisplay prop controls how the selected values are displayed in the closed trigger:

  • 'chips' (default): Interactive tags with individual remove buttons (×).
  • 'badge': Displays the first selected item along with a count pill badge (e.g. +2).
  • 'text': Comma-separated plain text summary.
import { useState } from "react";
import { Select, ListBoxItem } from "@hashti/react";
import type { Selection } from "react-aria-components";

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

  return (
    <div className="max-w-md space-y-4">
      {/* Chips display (default) */}
      <Select
        label="Technologies (Chips)"
        selectionMode="multiple"
        multipleDisplay="chips"
        selectedKeys={selected}
        onSelectionChange={setSelected}
      >
        <ListBoxItem id="react">React 19</ListBoxItem>
        <ListBoxItem id="astro">Astro</ListBoxItem>
        <ListBoxItem id="tailwind">Tailwind CSS v4</ListBoxItem>
        <ListBoxItem id="bun">Bun Runtime</ListBoxItem>
      </Select>

      {/* Badge display */}
      <Select
        label="Technologies (Badge)"
        selectionMode="multiple"
        multipleDisplay="badge"
        selectedKeys={selected}
        onSelectionChange={setSelected}
      >
        <ListBoxItem id="react">React 19</ListBoxItem>
        <ListBoxItem id="astro">Astro</ListBoxItem>
        <ListBoxItem id="tailwind">Tailwind CSS v4</ListBoxItem>
        <ListBoxItem id="bun">Bun Runtime</ListBoxItem>
      </Select>
    </div>
  );
}

Grouped Sections

Use <ListBoxSection> and <SectionHeader> to organize items into clear categorical groupings:

import { Select, ListBoxItem, ListBoxSection, SectionHeader } from "@hashti/react";

export function GroupedSelectDemo() {
  return (
    <div className="max-w-xs">
      <Select label="Spatial Elements" placeholder="Select an architectural feature">
        <ListBoxSection>
          <SectionHeader className="font-semibold text-xs text-gray-400 px-3 py-1">
            Transitional Spaces
          </SectionHeader>
          <ListBoxItem id="hashti">Hashti (Octagonal Vestibule)</ListBoxItem>
          <ListBoxItem id="dalan">Dalan (Connecting Corridor)</ListBoxItem>
          <ListBoxItem id="sar-dar">Sar-dar (Entrance Portal)</ListBoxItem>
        </ListBoxSection>

        <ListBoxSection>
          <SectionHeader className="font-semibold text-xs text-gray-400 px-3 py-1">
            Interior Sanctuaries
          </SectionHeader>
          <ListBoxItem id="iwan">Iwan (Vaulted Hall)</ListBoxItem>
          <ListBoxItem id="howz-khaneh">Howz-Khaneh (Pool Room)</ListBoxItem>
          <ListBoxItem id="panj-dari">Panj-Dari (Five-Door Living Room)</ListBoxItem>
        </ListBoxSection>
      </Select>
    </div>
  );
}

Props Reference

PropTypeDefaultDescription
selectionMode'single' | 'multiple''single'Selection mode allowing single or multiple option choices.
multipleDisplay'chips' | 'badge' | 'text''chips'Display format for multi-selected items in the trigger.
selectedKeyKey—The currently selected option ID (controlled).
defaultSelectedKeyKey—The initial selected option ID (uncontrolled).
onSelectionChange(key: Key) => void—Handler invoked when an option is selected.
itemsIterable<T>—Dynamic item collection array or iterable.
childrenReactNode | ((item: T) => ReactNode)—Options to render within the select dropdown.
placeholderstring—Prompt text shown when no item has been selected.
labelReactNode—Accessible label for the select element.
descriptionReactNode—Guidance text rendered with the field.
errorMessageReactNode—Error text rendered when isInvalid is true.
size'sm' | 'md' | 'lg''md'Height and scale of the trigger button.
isDisabledbooleanfalseWhether the select dropdown is disabled.
isInvalidbooleanfalseMarks the select as invalid and activates error styling.
isRequiredbooleanfalseIndicates that an option selection is required.
disabledKeysIterable<Key>—Keys of specific options that should be disabled.
renderValue(items: T[]) => ReactNode—Custom renderer for the closed trigger display value.

Accessibility

Select complies with the WAI-ARIA combobox specification:

  • Opening & Closing:
    • Pressing Space, Enter, or ↓ opens the popup listbox.
    • Pressing Esc closes the listbox and restores focus to the select trigger button.
  • Keyboard Navigation:
    • ↑ and ↓ navigate between options.
    • Home moves focus to the first item; End jumps to the last item.
    • Typeahead Letter Jumping: Typing letters (e.g. “I”, “S”, “H”) immediately jumps focus to the next matching item.
  • Focus Management:
    • Focus is never lost; closing returns focus directly to the trigger button.
    • While open, aria-activedescendant is tracked on the focused item, ensuring screen readers announce options accurately.
  • ARIA Attributes:
    • The trigger has role="combobox", aria-haspopup="listbox", aria-expanded="false|true", and aria-labelledby referencing the label.

Keyboard Interactions

KeyAction
Space / Enter / ↓Opens the dropdown listbox and focuses the active item.
↑ / ↓Navigates between items in the listbox.
Home / EndMoves focus to the first or last available option.
EscCloses the dropdown listbox and restores focus to the trigger.
TabCloses the listbox and moves focus to the next interactive page element.

WAI-ARIA & Data Attributes

AttributeTargetDescription
[data-hovered]Trigger, ItemPresent when the pointer hovers over the element.
[data-focused]Trigger, ItemPresent when the element holds keyboard or pointer focus.
[data-pressed]Trigger, ItemActive while the element is actively pressed.
[data-selected]ItemPresent on currently selected listbox options.
[data-disabled]Trigger, ItemPresent when the element is disabled from interaction.