Moul UIBeta
Components

ComboBox

Documentation for the ComboBox component.

React Aria

ComboBox

Preview

The ComboBox component is built with React Aria and styled using StyleX.

Import

import { ComboBox } from '@moul-dev/ui';

Usage

Here is a basic example of how to use the ComboBox component:

import { ComboBox } from '@moul-dev/ui';

export default function Example() {
  return (
    <ComboBox>
      {/* Component content */}
    </ComboBox>
  );
}

Multi-select with TagGroup

You can build a multi-select ComboBox by managing the selection state in a list/set, filtering out the selected items from the dropdown, and rendering them as a TagGroup below the ComboBox.

import { ComboBox, ComboBoxItem, TagGroup, Tag } from '@moul-dev/ui';
import { useState } from 'react';

export default function Example() {
  const provinces = [
    { id: 'phnom-penh', name: 'Phnom Penh' },
    { id: 'siem-reap', name: 'Siem Reap' },
    { id: 'battambang', name: 'Battambang' },
    { id: 'sihanoukville', name: 'Sihanoukville' },
    { id: 'kampot', name: 'Kampot' },
    { id: 'kandal', name: 'Kandal' },
    { id: 'kampong-cham', name: 'Kampong Cham' },
    { id: 'koh-kong', name: 'Koh Kong' },
    { id: 'kep', name: 'Kep' },
  ];

  const [selectedKeys, setSelectedKeys] = useState<Set<any>>(new Set());
  const [inputValue, setInputValue] = useState('');

  const availableProvinces = provinces.filter(
    (p) => !selectedKeys.has(p.id)
  );

  const handleSelectionChange = (key: any) => {
    if (key) {
      setSelectedKeys((prev) => {
        const next = new Set(prev);
        next.add(key);
        return next;
      });
      setInputValue('');
    }
  };

  const handleRemove = (keys: Set<any>) => {
    setSelectedKeys((prev) => {
      const next = new Set(prev);
      for (const k of keys) {
        next.delete(k);
      }
      return next;
    });
  };

  return (
    <div className="w-full max-w-sm flex flex-col gap-4">
      <ComboBox
        label="Cambodian Provinces"
        placeholder="Select a province"
        inputValue={inputValue}
        onInputChange={setInputValue}
        onSelectionChange={handleSelectionChange}
        selectedKey={null}
      >
        {availableProvinces.map((province) => (
          <ComboBoxItem key={province.id} id={province.id}>
            {province.name}
          </ComboBoxItem>
        ))}
      </ComboBox>

      {selectedKeys.size > 0 && (
        <TagGroup
          label="Selected Provinces"
          onRemove={handleRemove}
          variant="primary"
        >
          {[...selectedKeys].map((key) => {
            const province = provinces.find((p) => p.id === key);
            return (
              <Tag key={key} id={key}>
                {province?.name || key}
              </Tag>
            );
          })}
        </TagGroup>
      )}
    </div>
  );
}

Item Actions

Use the onAction prop on <ComboBoxItem> to perform custom actions when an item is pressed (such as dynamic "Create" actions). Custom action items bypass selection and will not populate the input.

Pair this with allowsEmptyCollection so the menu stays open when typing a query with no existing matches.

You can also pass onAction to <ComboBox> to be notified when any option is activated while preserving normal value selection.

import { ComboBox, ComboBoxItem } from '@moul-dev/ui';
import { useState } from 'react';

export default function Example() {
  const [items, setItems] = useState([
    { id: 'react', name: 'React' },
    { id: 'vue', name: 'Vue' },
    { id: 'svelte', name: 'Svelte' },
  ]);
  const [inputValue, setInputValue] = useState('');

  const isExisting = items.some(
    (item) => item.name.toLowerCase() === inputValue.trim().toLowerCase()
  );

  const handleCreate = (name: string) => {
    const trimmed = name.trim();
    if (!trimmed) return;
    const id = trimmed.toLowerCase().replace(/\s+/g, '-');
    setItems((prev) => [...prev, { id, name: trimmed }]);
    setInputValue('');
  };

  return (
    <ComboBox
      label="Frontend Framework"
      placeholder="Type or select..."
      allowsEmptyCollection
      inputValue={inputValue}
      onInputChange={setInputValue}
      onAction={(key) => console.log('Activated item key:', key)}
    >
      {inputValue.trim().length > 0 && !isExisting && (
        <ComboBoxItem
          id="create-new"
          onAction={() => handleCreate(inputValue)}
        >
          {`Create "${inputValue.trim()}"`}
        </ComboBoxItem>
      )}
      {items.map((item) => (
        <ComboBoxItem key={item.id} id={item.id}>
          {item.name}
        </ComboBoxItem>
      ))}
    </ComboBox>
  );
}

Props

ComboBox Props

Prop

Type

ComboBoxItem Props

Prop

Type

Default className: react-aria-ComboBox

Render PropCSS Selector
isOpen
Whether the combobox list popover is open.
[data-open]
isFocused
Whether the combobox is focused.
[data-focused]
isFocusVisible
Whether the combobox is keyboard focused.
[data-focus-visible]
isDisabled
Whether the combobox is disabled.
[data-disabled]

On this page