Basic Usage

A select with a visible label. When you provide the label prop, the select automatically renders inside a Field wrapper with the label displayed above it.

import { Select } from "@cloudflare/kumo";

function App() {
  const [value, setValue] = useState("apple");

  return (
    <Select
      label="Favorite Fruit"
      className="w-[200px]"
      value={value}
      onValueChange={(v) => setValue(v ?? "apple")}
      items={{ apple: "Apple", banana: "Banana", cherry: "Cherry" }}
    />
  )
}

Without Visible Label

When a visible label isn't needed (e.g., in compact UIs or when context is clear), use aria-label for accessibility.

import { Select } from "@cloudflare/kumo";

function App() {
  const [value, setValue] = useState("apple");

  return (
    <Select
      aria-label="Select a fruit"
      className="w-[200px]"
      value={value}
      onValueChange={(v) => setValue(v ?? "apple")}
      items={{ apple: "Apple", banana: "Banana", cherry: "Cherry" }}
    />
  )
}

With Description and Error

Select integrates with the Field wrapper to show description text and validation errors.

Please select an issue type
import { Select } from "@cloudflare/kumo";

function App() {
  const [value, setValue] = useState<string | null>(null);

  return (
    <Select
      label="Issue Type"
      description="Choose the category that best describes your issue"
      error={!value ? "Please select an issue type" : undefined}
      className="w-[280px]"
      value={value}
      onValueChange={(v) => setValue(v as string | null)}
      items={{
        bug: "Bug",
        documentation: "Documentation",
        feature: "Feature",
      }}
    />
  )
}

Placeholder

Use the placeholder prop to show text when no value is selected.

import { Select } from "@cloudflare/kumo";

function App() {
  const [value, setValue] = useState<string | null>(null);

  return (
    <Select
      label="Category"
      placeholder="Choose a category..."
      className="w-[200px]"
      value={value}
      onValueChange={(v) => setValue(v as string | null)}
      items={{
        bug: "Bug",
        documentation: "Documentation",
        feature: "Feature",
      }}
    />
  )
}

Label with Tooltip

Add a tooltip icon next to the label for additional context using labelTooltip.

import { Select } from "@cloudflare/kumo";

function App() {
  const [value, setValue] = useState<string | null>(null);

  return (
    <Select
      label="Priority"
      labelTooltip="Higher priority issues are addressed first"
      placeholder="Select priority"
      className="w-[200px]"
      value={value}
      onValueChange={(v) => setValue(v as string | null)}
      items={{
        low: "Low",
        medium: "Medium",
        high: "High",
        critical: "Critical",
      }}
    />
  )
}

Custom Rendering

A select component that demonstrates custom rendering capabilities for both the trigger button and dropdown options, allowing you to work with complex object data structures instead of simple string values.

const languages = [
  { value: "en", label: "English", emoji: "🇬🇧" },
  { value: "fr", label: "French", emoji: "🇫🇷" },
  { value: "de", label: "German", emoji: "🇩🇪" },
  { value: "es", label: "Spanish", emoji: "🇪🇸" },
  { value: "it", label: "Italian", emoji: "🇮🇹" },
  { value: "pt", label: "Portuguese", emoji: "🇵🇹" },
];

function App() {
  const [value, setValue] = useState(languages[0]);

  return (
    <Select
      className="w-[200px]"
      renderValue={(v) => (
        <span>
          {v.emoji} {v.label}
        </span>
      )}
      value={value}
      onValueChange={(v) => setValue(v as any)}
    >
      {languages.map((language) => (
        <Select.Option key={language.value} value={language}>
          {language.emoji} {language.label}
        </Select.Option>
      ))}
    </Select>
  );
}

Select compares value with items to find which one is selected. For object items, it will compare if the object is the same reference not by value by default. If you want to compare object items by value, you can use isItemEqualToValue prop.

function App() {
  const languages = [
    { value: "en", label: "English", emoji: "🇬🇧" },
    { value: "fr", label: "French", emoji: "🇫🇷" },
    // ...
  ];

  const [value, setValue] = useState(languages[0]);

  return (
    <Select
      className="w-[200px]"
      renderValue={(v) => (
        <span>
          {v.emoji} {v.label}
        </span>
      )}
      value={value}
      onValueChange={(v) => setValue(v as any)}
      // Provides custom comparison logic
      isItemEqualToValue={(item, value) => item.value === value.value}
    >
      {languages.map((language) => (
        <Select.Option key={language.value} value={language}>
          {language.emoji} {language.label}
        </Select.Option>
      ))}
    </Select>
  );
}

Loading

A select component with loading state. The loading state is passed to the component via the loading prop.

Loading State

Loading From Server (simulated 2s delay)

function App() {
  // NULL is for unselected value
  const [value, setValue] = useState(null);
  const { data, isLoading } = useQuery({...});

  return (
    <Select value={value} onValueChange={(v) => setValue(v as any)} loading={isLoading}>
      {data?.map((item) => (
        <Select.Option key={item.id} value={item.id}>
          {item.name}
        </Select.Option>
      ))}
    </Select>
  );
}

Multiple Item

A select component with multiple selection enabled. The value is an array of selected items.

function App() {
  const [value, setValue] = useState<string[]>(["Name", "Location", "Size"]);

  return (
    <Select
      className="w-[250px]"
      multiple
      renderValue={(value) => {
        if (value.length > 3) {
          return (
            <span className="line-clamp-1">
              {value.slice(2).join(", ") + ` and ${value.length - 2} more`}
            </span>
          );
        }
        return <span>{value.join(", ")}</span>;
      }}
      value={value}
      onValueChange={(v) => setValue(v as any)}
    >
      <Select.Option value="Name">Name</Select.Option>
      <Select.Option value="Location">Location</Select.Option>
      <Select.Option value="Size">Size</Select.Option>
      <Select.Option value="Read">Read</Select.Option>
      <Select.Option value="Write">Write</Select.Option>
      <Select.Option value="CreatedAt">Created At</Select.Option>
    </Select>
  );
}

More Example

Select the primary author for this document

const authors = [
  { id: 1, name: "John Doe", title: "Programmer" },
  { id: 2, name: "Alice Smith", title: "Software Engineer" },
  { id: 3, name: "Michael Chan", title: "UI/UX Designer" },
  { id: 4, name: "Sok Dara", title: "DevOps Engineer" },
  { id: 5, name: "Emily Johnson", title: "Product Manager" },
  { id: 6, name: "Visal In", title: "System Engineer" },
  { id: 7, name: "Laura Kim", title: "Technical Writer" },
];

function App() {
  const [value, setValue] = useState<(typeof authors)[0] | null>(null);

  return (
    <Select
      className="w-[200px]"
      onValueChange={(v) => setValue(v as any)}
      value={value}
      isItemEqualToValue={(item, value) => item?.id === value?.id}
      renderValue={(author) => {
        return author?.name ?? "Please select author";
      }}
    >
      {authors.map((author) => (
        <Select.Option key={author.id} value={author}>
          <div className="flex items-center gap-2 w-[300px] justify-between">
            <Text>{author.name}</Text>
            <Text variant="secondary">{author.title}</Text>
          </div>
        </Select.Option>
      ))}
    </Select>
  )
}

API Reference

Select

PropTypeDefaultDescription
classNamestring-Additional CSS classes merged via `cn()`.
labelReactNode-Label content for the select. When provided, enables the Field wrapper with a visible label above the select. For accessibility without a visible label, use `aria-label` instead.
hideLabelboolean--
placeholderstring-Placeholder text shown when no value is selected.
loadingboolean-When `true`, shows a skeleton loader in place of the selected value.
disabledboolean-Whether the select is disabled.
requiredboolean-Whether the select is required. When `false`, shows "(optional)" text.
labelTooltipReactNode-Tooltip content displayed next to the label via an info icon.
valuestring-Currently selected value (controlled mode).
childrenReactNode-`Select.Option` elements to render in the dropdown.
descriptionReactNode-Helper text displayed below the select.
errorstring | object-Error message string or validation error object with `match` key.
onValueChange(value: string) => void-Callback when selection changes
defaultValuestring-Initial value for uncontrolled mode

Select.Option

PropTypeDefault

No component-specific props. Accepts standard HTML attributes.