Abraham

Combobox

Component

About

Here is Combobox component

Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds Combobox to your project and fits it to your theme.

Code

How Combobox is used: the demo's code. The component's own source comes with a free account.

Usage.tsx

"use client";

import {
  Combobox,
  useCombobox,
  useListCollection,
} from "@ark-ui/react/combobox";
import { Portal } from "@ark-ui/react/portal";
import { ChevronDownIcon, XIcon, LoaderIcon } from "lucide-react";
import { useState, useEffect } from "react";

// User type definition
interface User {
  id: number;
  name: string;
  email: string;
  avatar: string;
}

// Simulated API data
const allUsers = [
  { id: 1, name: "John Doe", email: "john@example.com", avatar: "JD" },
  { id: 2, name: "Jane Smith", email: "jane@example.com", avatar: "JS" },
  { id: 3, name: "Bob Johnson", email: "bob@example.com", avatar: "BJ" },
  { id: 4, name: "Alice Brown", email: "alice@example.com", avatar: "AB" },
  { id: 5, name: "Charlie Wilson", email: "charlie@example.com", avatar: "CW" },
  { id: 6, name: "Diana Davis", email: "diana@example.com", avatar: "DD" },
];

// Simulate API call
const searchUsers = async (query: string): Promise<User[]> => {
  await new Promise((resolve) => setTimeout(resolve, 500)); // Simulate network delay

  if (!query) return [];

  return allUsers.filter(
    (user) =>
      user.name.toLowerCase().includes(query.toLowerCase()) ||
      user.email.toLowerCase().includes(query.toLowerCase())
  );
};

export default function AsyncCombobox() {
  const [isLoading, setIsLoading] = useState(false);
  const [inputValue, setInputValue] = useState("");

  const { collection, set } = useListCollection<User>({
    initialItems: [],
    itemToString: (item) => item.name,
    itemToValue: (item) => item.id.toString(),
  });

  const combobox = useCombobox({
    collection,
    placeholder: "Type to search users...",
    inputValue,
    onInputValueChange: (details) => setInputValue(details.inputValue),
  });

  useEffect(() => {
    const searchAsync = async () => {
      if (inputValue.length === 0) {
        set([]);
        setIsLoading(false);
        return;
      }

      if (inputValue.length < 2) {
        set([]);
        setIsLoading(false);
        return;
      }

      setIsLoading(true);
      try {
        const results = await searchUsers(inputValue);
        set(results);
      } catch (error) {
        console.error("Failed to search users:", error);
        set([]);
      } finally {
        setIsLoading(false);
      }
    };

    searchAsync();
  }, [inputValue, set]);

  return (
    <Combobox.RootProvider value={combobox} className="w-full max-w-sm">
      <Combobox.Label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
        Search Users
      </Combobox.Label>
      <Combobox.Control className="relative">
        <Combobox.Input className="w-full px-3 py-2 pr-20 border border-gray-300 dark:border-gray-600 rounded-md shadow-xs bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400 focus:outline-hidden focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 focus:border-blue-500 dark:focus:border-blue-400" />
        <div className="absolute inset-y-0 right-0 flex items-center">
          {isLoading && (
            <LoaderIcon className="h-4 w-4 text-blue-500 dark:text-blue-400 animate-spin mx-2" />
          )}
          <Combobox.ClearTrigger className="px-2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
            <XIcon className="h-4 w-4" />
          </Combobox.ClearTrigger>
          <Combobox.Trigger className="px-2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
            <ChevronDownIcon className="h-4 w-4" />
          </Combobox.Trigger>
        </div>
      </Combobox.Control>
      <Portal>
        <Combobox.Positioner>
          <Combobox.Content className="mt-1 max-h-60 w-full overflow-auto rounded-md bg-white dark:bg-gray-800 py-1 shadow-lg ring-1 ring-black ring-opacity-5 dark:ring-gray-600 focus:outline-hidden z-50">
            <Combobox.ItemGroup>
              <Combobox.ItemGroupLabel className="px-3 py-2 text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">
                Users
              </Combobox.ItemGroupLabel>

              {isLoading ? (
                <div className="px-3 py-8 text-center text-gray-500 dark:text-gray-400">
                  <LoaderIcon className="h-5 w-5 animate-spin mx-auto mb-2 text-blue-500 dark:text-blue-400" />
                  <p className="text-sm">Searching...</p>
                </div>
              ) : collection.items.length === 0 ? (
                <div className="px-3 py-8 text-center text-gray-500 dark:text-gray-400">
                  <p className="text-sm">
                    {inputValue.length < 2
                      ? "Type at least 2 characters to search"
                      : "No users found"}
                  </p>
                </div>
              ) : (
                collection.items.map((user) => (
                  <Combobox.Item
                    key={user.id}
                    item={user}
                    className="relative cursor-pointer select-none py-2 pl-3 pr-9 text-gray-900 dark:text-gray-100 hover:bg-gray-50 dark:hover:bg-gray-700 data-highlighted:bg-gray-50 dark:data-highlighted:bg-gray-700 transition-colors"
                  >
                    <div className="flex items-center">
                      <div className="shrink-0 h-8 w-8 bg-blue-500 dark:bg-blue-600 rounded-full flex items-center justify-center text-white text-xs font-medium mr-3">
                        {user.avatar}
                      </div>
                      <div className="flex-1 min-w-0">
                        <Combobox.ItemText className="block text-sm font-medium text-gray-900 dark:text-gray-100 truncate">
                          {user.name}
                        </Combobox.ItemText>
                        <p className="text-sm text-gray-500 dark:text-gray-400 truncate">
                          {user.email}
                        </p>
                      </div>
                    </div>
                    <Combobox.ItemIndicator className="absolute inset-y-0 pr-3 text-blue-600 dark:text-blue-400">
                      ✓
                    </Combobox.ItemIndicator>
                  </Combobox.Item>
                ))
              )}
            </Combobox.ItemGroup>
          </Combobox.Content>
        </Combobox.Positioner>
      </Portal>
    </Combobox.RootProvider>
  );
}

Dependencies

More by Abraham

View all
  • Signature Pad

    Signature Pad

    106
  • Carousel

    Carousel

    81
  • timeline-animation

    timeline-animation

    65
  • Basic Toast

    Basic Toast

    56
  • Calendar

    Calendar

    54
  • QR Code

    QR Code

    51
  •  Menu

    Menu

    50
  • Steps

    Steps

    43