Combobox

Filterable select for choosing from a predefined list of items.

"use client";

import { useRef } from "react";
import {
  Combobox,
  ComboboxEmpty,
  ComboboxInput,
  ComboboxItem,
  ComboboxList,
  ComboboxPopup,
  ComboboxPortal,
  ComboboxPositioner,
  ComboboxTrigger,
} from "@/components/ui/combobox/combobox";
import styles from "./combobox-demo.module.css";

type Country = {
  code: string;
  name: string;
  flag: string;
};

const countries: Country[] = [
  { code: "AU", name: "Australia", flag: "🇦🇺" },
  { code: "BR", name: "Brazil", flag: "🇧🇷" },
  { code: "CA", name: "Canada", flag: "🇨🇦" },
  { code: "CN", name: "China", flag: "🇨🇳" },
  { code: "DK", name: "Denmark", flag: "🇩🇰" },
  { code: "FI", name: "Finland", flag: "🇫🇮" },
  { code: "FR", name: "France", flag: "🇫🇷" },
  { code: "DE", name: "Germany", flag: "🇩🇪" },
  { code: "IN", name: "India", flag: "🇮🇳" },
  { code: "IT", name: "Italy", flag: "🇮🇹" },
  { code: "JP", name: "Japan", flag: "🇯🇵" },
  { code: "MX", name: "Mexico", flag: "🇲🇽" },
  { code: "NL", name: "Netherlands", flag: "🇳🇱" },
  { code: "NO", name: "Norway", flag: "🇳🇴" },
  { code: "PL", name: "Poland", flag: "🇵🇱" },
  { code: "ES", name: "Spain", flag: "🇪🇸" },
  { code: "SE", name: "Sweden", flag: "🇸🇪" },
  { code: "CH", name: "Switzerland", flag: "🇨🇭" },
  { code: "GB", name: "United Kingdom", flag: "🇬🇧" },
  { code: "US", name: "United States", flag: "🇺🇸" },
];

export default function ComboboxDemo() {
  const anchorRef = useRef<HTMLDivElement>(null);

  return (
    <div className={styles.container}>
      <label className={styles.label} htmlFor="cb-input">
        Select your country
      </label>

      <div className={styles.comboboxWrapper}>
        <Combobox<Country>
          items={countries}
          itemToStringLabel={(item) => item?.name || ""}
          itemToStringValue={(item) => item?.code || ""}
        >
          <div className={styles.inputWrapper} ref={anchorRef}>
            <ComboboxInput id="cb-input" placeholder="Search countries..." />
            <ComboboxTrigger />
          </div>

          <ComboboxPortal>
            <ComboboxPositioner anchor={anchorRef}>
              <ComboboxPopup className={styles.popup}>
                <ComboboxEmpty>No country found.</ComboboxEmpty>
                <ComboboxList>
                  {(country: Country) => (
                    <ComboboxItem indicatorPosition="right" key={country.code} value={country}>
                      <div className={styles.countryContainer}>
                        <span className={styles.countryFlag}>{country.flag}</span>
                        <span className={styles.countryName}>{country.name}</span>
                      </div>
                    </ComboboxItem>
                  )}
                </ComboboxList>
              </ComboboxPopup>
            </ComboboxPositioner>
          </ComboboxPortal>
        </Combobox>
      </div>
    </div>
  );
}

npx shadcn@latest add @roiui/combobox
npx shadcn@latest add @roiui/combobox-tailwind

anatomy
<Combobox>
  <ComboboxInput />
  <ComboboxTrigger />
  <ComboboxPortal>
      <ComboboxPositioner>
          <ComboboxPopup>
              <ComboboxList>
                  <ComboboxItem>
                      <ComboboxItemText />
                      <ComboboxItemIndicator />
                  </ComboboxItem>
              </ComboboxList>
          </ComboboxPopup>
      </ComboboxPositioner>
  </ComboboxPortal>
</Combobox>

"use client";

import { useRef } from "react";
import {
  Combobox,
  ComboboxClear,
  ComboboxEmpty,
  ComboboxInput,
  ComboboxItem,
  ComboboxList,
  ComboboxPopup,
  ComboboxPortal,
  ComboboxPositioner,
  ComboboxTrigger,
  ComboboxValue,
} from "@/components/ui/combobox/combobox";
import styles from "./combobox-clearable.module.css";

const frameworks = [
  { value: "next", label: "Next.js" },
  { value: "remix", label: "Remix" },
  { value: "astro", label: "Astro" },
  { value: "nuxt", label: "Nuxt" },
  { value: "sveltekit", label: "SvelteKit" },
  { value: "gatsby", label: "Gatsby" },
];

type Framework = (typeof frameworks)[0];

export default function ComboboxClearable() {
  const anchorRef = useRef<HTMLDivElement>(null);

  return (
    <div className={styles.container}>
      <label className={styles.label} htmlFor="clearable-input">
        Select a framework
      </label>

      <div className={styles.comboboxWrapper}>
        <Combobox<Framework>
          items={frameworks}
          itemToStringLabel={(item) => item?.label || ""}
          itemToStringValue={(item) => item?.value || ""}
        >
          <div className={styles.inputWrapper} ref={anchorRef}>
            <ComboboxInput id="clearable-input" placeholder="Search frameworks..." />
            <ComboboxValue>
              {(value: Framework | null) => (value ? <ComboboxClear /> : <ComboboxTrigger />)}
            </ComboboxValue>
          </div>

          <ComboboxPortal>
            <ComboboxPositioner anchor={anchorRef}>
              <ComboboxPopup className={styles.popup}>
                <ComboboxEmpty>No frameworks found.</ComboboxEmpty>
                <ComboboxList>
                  {(item: Framework) => (
                    <ComboboxItem indicatorPosition="right" key={item.value} value={item}>
                      <span style={{ flex: 1 }}>{item.label}</span>
                    </ComboboxItem>
                  )}
                </ComboboxList>
              </ComboboxPopup>
            </ComboboxPositioner>
          </ComboboxPortal>
        </Combobox>
      </div>
    </div>
  );
}

"use client";

import { useRef } from "react";
import {
  Combobox,
  ComboboxEmpty,
  ComboboxInput,
  ComboboxItem,
  ComboboxList,
  ComboboxPopup,
  ComboboxPortal,
  ComboboxPositioner,
  ComboboxTrigger,
} from "@/components/ui/combobox/combobox";
import styles from "./combobox-auto-highlight.module.css";

const countries = [
  { value: "us", label: "United States" },
  { value: "uk", label: "United Kingdom" },
  { value: "ca", label: "Canada" },
  { value: "au", label: "Australia" },
  { value: "de", label: "Germany" },
  { value: "fr", label: "France" },
  { value: "jp", label: "Japan" },
  { value: "br", label: "Brazil" },
];

type Country = (typeof countries)[0];

export default function ComboboxAutoHighlight() {
  const anchorRef = useRef<HTMLDivElement>(null);

  return (
    <div className={styles.container}>
      <label className={styles.label} htmlFor="auto-highlight-input">
        Select a country
      </label>

      <div className={styles.comboboxWrapper}>
        <Combobox<Country>
          autoHighlight
          items={countries}
          itemToStringLabel={(item) => item?.label || ""}
          itemToStringValue={(item) => item?.value || ""}
        >
          <div className={styles.inputWrapper} ref={anchorRef}>
            <ComboboxInput id="auto-highlight-input" placeholder="Start typing..." />
            <ComboboxTrigger />
          </div>

          <ComboboxPortal>
            <ComboboxPositioner anchor={anchorRef}>
              <ComboboxPopup className={styles.popup}>
                <ComboboxEmpty>No countries found.</ComboboxEmpty>
                <ComboboxList>
                  {(item: Country) => (
                    <ComboboxItem indicatorPosition="right" key={item.value} value={item}>
                      <span style={{ flex: 1 }}>{item.label}</span>
                    </ComboboxItem>
                  )}
                </ComboboxList>
              </ComboboxPopup>
            </ComboboxPositioner>
          </ComboboxPortal>
        </Combobox>
      </div>
    </div>
  );
}

"use client";

import { useRef } from "react";
import {
  Combobox,
  ComboboxEmpty,
  ComboboxGroup,
  ComboboxGroupLabel,
  ComboboxInput,
  ComboboxItem,
  ComboboxList,
  ComboboxPopup,
  ComboboxPortal,
  ComboboxPositioner,
  ComboboxTrigger,
} from "@/components/ui/combobox/combobox";
import styles from "./combobox-grouped.module.css";

type Item = {
  value: string;
  label: string;
};

type Group = {
  label: string;
  items: Item[];
};

const groups: Group[] = [
  {
    label: "Fruits",
    items: [
      { value: "apple", label: "Apple" },
      { value: "banana", label: "Banana" },
      { value: "orange", label: "Orange" },
    ],
  },
  {
    label: "Vegetables",
    items: [
      { value: "carrot", label: "Carrot" },
      { value: "broccoli", label: "Broccoli" },
      { value: "spinach", label: "Spinach" },
    ],
  },
  {
    label: "Dairy",
    items: [
      { value: "milk", label: "Milk" },
      { value: "cheese", label: "Cheese" },
      { value: "yogurt", label: "Yogurt" },
    ],
  },
];

export default function ComboboxGrouped() {
  const anchorRef = useRef<HTMLDivElement>(null);

  return (
    <div className={styles.container}>
      <label className={styles.label} htmlFor="grouped-input">
        Select an item
      </label>

      <div className={styles.comboboxWrapper}>
        <Combobox<Group>
          items={groups}
          itemToStringLabel={(item) => (item as unknown as Item)?.label || ""}
          itemToStringValue={(item) => (item as unknown as Item)?.value || ""}
        >
          <div className={styles.inputWrapper} ref={anchorRef}>
            <ComboboxInput id="grouped-input" placeholder="Search items..." />
            <ComboboxTrigger />
          </div>

          <ComboboxPortal>
            <ComboboxPositioner anchor={anchorRef}>
              <ComboboxPopup className={styles.popup}>
                <ComboboxEmpty>No items found.</ComboboxEmpty>
                <ComboboxList>
                  {(group: Group) => (
                    <ComboboxGroup key={group.label}>
                      <ComboboxGroupLabel>{group.label}</ComboboxGroupLabel>
                      {group.items.map((item) => (
                        <ComboboxItem indicatorPosition="right" key={item.value} value={item}>
                          <span style={{ flex: 1 }}>{item.label}</span>
                        </ComboboxItem>
                      ))}
                    </ComboboxGroup>
                  )}
                </ComboboxList>
              </ComboboxPopup>
            </ComboboxPositioner>
          </ComboboxPortal>
        </Combobox>
      </div>
    </div>
  );
}

"use client";

import { useId, useRef } from "react";
import {
  Combobox,
  ComboboxChip,
  ComboboxChipRemove,
  ComboboxChips,
  ComboboxChipsInput,
  ComboboxEmpty,
  ComboboxItem,
  ComboboxList,
  ComboboxPopup,
  ComboboxPortal,
  ComboboxPositioner,
  ComboboxValue,
} from "@/components/ui/combobox/combobox";
import styles from "./combobox-multiple.module.css";

interface Language {
  id: string;
  value: string;
}

const languages: Language[] = [
  { id: "js", value: "JavaScript" },
  { id: "ts", value: "TypeScript" },
  { id: "py", value: "Python" },
  { id: "java", value: "Java" },
  { id: "cpp", value: "C++" },
  { id: "cs", value: "C#" },
  { id: "php", value: "PHP" },
  { id: "ruby", value: "Ruby" },
  { id: "go", value: "Go" },
  { id: "rust", value: "Rust" },
  { id: "swift", value: "Swift" },
];

export default function ComboboxMultiple() {
  const containerRef = useRef<HTMLDivElement>(null);
  const id = useId();

  return (
    <div className={styles.container}>
      <label className={styles.label} htmlFor={id}>
        Programming languages
      </label>

      <div className={styles.comboboxWrapper}>
        <Combobox<Language, true> items={languages} multiple>
          <ComboboxChips ref={containerRef}>
            <ComboboxValue>
              {(value: Language[]) => (
                <>
                  {value.map((language) => (
                    <ComboboxChip aria-label={language.value} key={language.id}>
                      {language.value}
                      <ComboboxChipRemove aria-label="Remove" />
                    </ComboboxChip>
                  ))}
                  <ComboboxChipsInput id={id} placeholder={value.length > 0 ? "" : "e.g. TypeScript"} />
                </>
              )}
            </ComboboxValue>
          </ComboboxChips>

          <ComboboxPortal>
            <ComboboxPositioner anchor={containerRef}>
              <ComboboxPopup className={styles.popup}>
                <ComboboxEmpty>No languages found.</ComboboxEmpty>
                <ComboboxList>
                  {(language: Language) => (
                    <ComboboxItem indicatorPosition="right" key={language.id} value={language}>
                      <span style={{ flex: 1 }}>{language.value}</span>
                    </ComboboxItem>
                  )}
                </ComboboxList>
              </ComboboxPopup>
            </ComboboxPositioner>
          </ComboboxPortal>
        </Combobox>
      </div>
    </div>
  );
}