Nasaq
Components

Combobox

Searchable select for long lists, single or multiple (chips), with Arabic-aware filtering and an empty state. Wraps Base UI Combobox.

PreviewOpen ↗

Code

import {  Combobox,  ComboboxContent,  ComboboxEmpty,  ComboboxInput,  ComboboxItem,  ComboboxList,  Field,  FieldLabel,} from "@fadymondy/nasaq/web";const COUNTRIES = [  { value: "sa", label: "Saudi Arabia" },  { value: "eg", label: "Egypt" },  { value: "jo", label: "Jordan" },];export function CountryField() {  return (    <Field>      <FieldLabel>Country</FieldLabel>      <Combobox items={COUNTRIES}>        <ComboboxInput placeholder="Search a country…" />        <ComboboxContent>          <ComboboxEmpty>No results</ComboboxEmpty>          <ComboboxList>            {(item: { value: string; label: string }) => (              <ComboboxItem key={item.value} value={item}>                {item.label}              </ComboboxItem>            )}          </ComboboxList>        </ComboboxContent>      </Combobox>    </Field>  );}

Forms · beta

Live examples and controls: Combobox in the lab.

Install

npx shadcn@latest add https://docs.nasaqui.com/r/combobox.json

A form control for choosing from a long list by typing. It looks like a Select, filters as you type, and in multiple mode shows the selection as removable chips. Filtering folds Arabic letter variants and diacritics, so typing اداره finds إدارة.

When to use

  • One or many choices from a list too long to scan (countries, people, tags).

When not to use

  • Four to fifteen options: use Select.
  • Free text with no fixed options: use Input.

Import

import { Combobox, ComboboxInput, ComboboxContent, ComboboxList, ComboboxItem, ComboboxEmpty } from "@fadymondy/nasaq/web";
// inside this monorepo: "@nasaq/web"

Quick start

import {
  Combobox,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxInput,
  ComboboxItem,
  ComboboxList,
  Field,
  FieldLabel,
} from "@fadymondy/nasaq/web";

const COUNTRIES = [
  { value: "sa", label: "Saudi Arabia" },
  { value: "eg", label: "Egypt" },
  { value: "jo", label: "Jordan" },
];

export function CountryField() {
  return (
    <Field>
      <FieldLabel>Country</FieldLabel>
      <Combobox items={COUNTRIES}>
        <ComboboxInput placeholder="Search a country…" />
        <ComboboxContent>
          <ComboboxEmpty>No results</ComboboxEmpty>
          <ComboboxList>
            {(item: { value: string; label: string }) => (
              <ComboboxItem key={item.value} value={item}>
                {item.label}
              </ComboboxItem>
            )}
          </ComboboxList>
        </ComboboxContent>
      </Combobox>
    </Field>
  );
}

Anatomy

Combobox                  Base UI Combobox.Root (filter defaults to comboboxFilter)
├─ ComboboxInput          single: box with input, clear and chevron   data-slot="combobox-input-group"
│  or ComboboxChips       multiple: chips + inline input              data-slot="combobox-chips"
│     └─ chip             data-slot="combobox-chip" / "combobox-chip-remove"
└─ ComboboxContent        portal + positioner + popup                 data-slot="combobox-content"
   ├─ ComboboxEmpty       data-slot="combobox-empty"
   └─ ComboboxList        data-slot="combobox-list"
      ├─ ComboboxGroup / ComboboxLabel / ComboboxSeparator
      └─ ComboboxItem     data-slot="combobox-item", check at the inline-start

API

Combobox: Base UI Combobox.Root props (items, value, defaultValue, onValueChange, multiple, name, disabled, itemToStringLabel, isItemEqualToValue, inputValue, ...). Items shaped { value, label } need no extra config. Differences from Base UI:

PropTypeDefaultDescription
filternull | (item, query, itemToString?) => booleancomboboxFilterArabic-aware substring match. null turns filtering off (async search).

ComboboxInput (single mode)

PropTypeDefaultDescription
clearablebooleantrueShow a clear button when there is a value.
clearLabelstring"Clear"Accessible name of the clear button. Localise.
triggerLabelstring"Open"Accessible name of the chevron button. Localise.

Other props go to the Base UI Combobox.Input (placeholder, id, aria-invalid).

ComboboxChips (multiple mode)

PropTypeDefaultDescription
placeholderstringnoneShown only while nothing is selected.
itemToLabel(item: unknown) => ReactNodeitem labelChip text.
removeLabelstring"Remove"Accessible name of each remove button. Localise.
inputPropsCombobox.Input propsnoneProps for the inline input.

ComboboxContent: side ("bottom"), align ("start"), sideOffset (4), plus Base UI Popup props. In multi mode the popup is anchored to the chips box and matches its width.

ComboboxList: takes a render function (item) => ReactNode over the filtered items, or plain children.

ComboboxItem: value is the item object; disabled is supported. ComboboxEmpty shows only when the filter leaves nothing and requires items on the root.

comboboxFilter(item, query, itemToString?): the default filter, exported for reuse.

Examples

Multiple with chips, controlled, Arabic copy:

import {
  Combobox,
  ComboboxChips,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxItem,
  ComboboxList,
} from "@fadymondy/nasaq/web";
import { useState } from "react";

type Option = { value: string; label: string };
const ITEMS: Option[] = [
  { value: "sa", label: "المملكة العربية السعودية" },
  { value: "eg", label: "مصر" },
  { value: "jo", label: "الأردن" },
];

export function Countries() {
  const [value, setValue] = useState<Option[]>([]);
  return (
    <Combobox multiple items={ITEMS} value={value} onValueChange={setValue}>
      <ComboboxChips placeholder="ابحث عن دولة…" removeLabel="إزالة" />
      <ComboboxContent>
        <ComboboxEmpty>لا توجد نتائج</ComboboxEmpty>
        <ComboboxList>
          {(item: Option) => (
            <ComboboxItem key={item.value} value={item}>
              {item.label}
            </ComboboxItem>
          )}
        </ComboboxList>
      </ComboboxContent>
    </Combobox>
  );
}

Accessibility

KeyAction
Down / UpOpens the list and moves the highlight.
EnterSelects the highlighted item.
EscapeCloses the list.
Backspace (multi, empty input)Highlights then removes the last chip.
Left / Right (multi)Moves between chips.

The input has role="combobox" and the list role="listbox"; the empty state is announced politely. The clear, chevron and chip-remove buttons are icon-only: pass localised clearLabel, triggerLabel, removeLabel. Label the control with FieldLabel.

RTL & i18n

Padding, the check indicator and chip layout use logical properties and mirror in RTL. Filtering folds case, diacritics, tatweel and alef/yeh/teh-marbuta variants. Localise the empty message and the button labels.

Styling & tokens

Uses border-input, bg-card, border-nq-focus, border-nq-danger, h-control, rounded-control, bg-popover, shadow-floating, bg-nq-selected. State attributes: data-highlighted, data-disabled, data-invalid. Extend with className.

Do / Don't

  • Do provide an empty state. Do label the control.
  • Don't use it for fewer than ~5 options; use Select.

Lab

https://docs.nasaqui.com/?path=/docs/components-forms-combobox--docs

On this page