---
layout: ~/layouts/MdxDocLayout.astro
title: "Select"
description: "Displays a list of options for the user to pick from—triggered by a button."
sourceFile: "components/select"
baseUIComponent: "select"
---

import ComponentExample from "~/components/docs/ComponentExample.astro";
import ComponentSection from "~/components/docs/ComponentSection.astro";
import CodeBlock from "~/components/docs/CodeBlock.astro";
import Heading from "~/components/docs/Heading.astro";
import PropsTable from "~/components/docs/PropsTable.astro";
import {
  SelectBasicDemo,
  SelectWithoutLabelDemo,
  SelectWithFieldDemo,
  SelectPlaceholderDemo,
  SelectWithTooltipDemo,
  SelectCustomRenderingDemo,
  SelectLoadingDemo,
  SelectLoadingDataDemo,
  SelectMultipleDemo,
  SelectComplexDemo,
  SelectDisabledOptionsDemo,
  SelectDisabledItemsDemo,
  SelectGroupedDemo,
  SelectGroupedWithDisabledDemo,
  SelectLongListDemo,
} from "~/components/demos/SelectDemo";

{/* Hero Demo */}

<ComponentSection>
  <ComponentExample demo="SelectBasicDemo">
    <SelectBasicDemo client:load />
  </ComponentExample>
</ComponentSection>

{/* Installation */}

<ComponentSection>
  <Heading level={2}>Installation</Heading>
  <Heading level={3}>Barrel</Heading>
  <CodeBlock code={`import { Select } from "@cloudflare/kumo";`} lang="tsx" />
  <Heading level={3}>Granular</Heading>
  <CodeBlock
    code={`import { Select } from "@cloudflare/kumo/components/select";`}
    lang="tsx"
  />
</ComponentSection>

{/* Usage */}

<ComponentSection>
  <Heading level={2}>Usage</Heading>

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

export default function Example() {
  const [value, setValue] = useState("apple");

  return (
    <Select
      label="Favorite Fruit"
      value={value}
      onValueChange={(v) => setValue(v ?? "apple")}
      items={{ apple: "Apple", banana: "Banana", cherry: "Cherry" }}
    />
  );
}
```

</ComponentSection>

{/* Examples */}

<ComponentSection>
  <Heading level={2}>Examples</Heading>

<Heading level={3}>Basic</Heading>
<p>
  A select with a visible label. When you provide the{" "}
  <code class="rounded bg-kumo-control px-1 py-0.5 text-sm">label</code> prop,
  the select automatically renders inside a Field wrapper with the label
  displayed above it.
</p>

  <ComponentExample demo="SelectBasicDemo">
    <SelectBasicDemo client:load />
  </ComponentExample>
</ComponentSection>

{/* Without Visible Label */}

<ComponentSection>
  <Heading level={3}>Without Visible Label</Heading>
  <p>
    When a visible label isn't needed (e.g., in compact UIs or when context is clear),
    use `aria-label` for accessibility.
  </p>

  <ComponentExample demo="SelectWithoutLabelDemo">
    <SelectWithoutLabelDemo client:load />
  </ComponentExample>
</ComponentSection>

{/* With Description and Error */}

<ComponentSection>
  <Heading level={3}>With Description and Error</Heading>
  <p>
    Select integrates with the Field wrapper to show description text and validation errors.
  </p>

  <ComponentExample demo="SelectWithFieldDemo">
    <SelectWithFieldDemo client:load />
  </ComponentExample>
</ComponentSection>

{/* Placeholder */}

<ComponentSection>
  <Heading level={3}>Placeholder</Heading>
  <p>
    Use the `placeholder` prop
    to show text when no value is selected.
  </p>

  <ComponentExample demo="SelectPlaceholderDemo">
    <SelectPlaceholderDemo client:load />
  </ComponentExample>
</ComponentSection>

{/* Label Tooltip */}

<ComponentSection>
  <Heading level={3}>Label with Tooltip</Heading>
  <p>
    Add a tooltip icon next to the label for additional context using <code
      class="rounded bg-kumo-control px-1 py-0.5 text-sm">labelTooltip</code>.
  </p>

  <ComponentExample demo="SelectWithTooltipDemo">
    <SelectWithTooltipDemo client:load />
  </ComponentExample>
</ComponentSection>

{/* Custom Rendering */}

<ComponentSection>
  <Heading level={3}>Custom Rendering</Heading>
  <p>
    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.
  </p>

<ComponentExample demo="SelectCustomRenderingDemo">
  <SelectCustomRenderingDemo client:load />
</ComponentExample>

<p>
  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{" "}
  <strong>isItemEqualToValue</strong> prop.
</p>

```tsx
<Select
  className="w-[200px]"
  renderValue={(v) => (
    <span>
      {v.emoji} {v.label}
    </span>
  )}
  value={value}
  onValueChange={(v) => setValue(v)}
  // 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>
```

</ComponentSection>

{/* Loading */}

<ComponentSection>
  <Heading level={3}>Loading</Heading>
  <p>
    A select component with loading state. The loading state is passed to
    the component via the <code
      class="rounded bg-kumo-control px-1 py-0.5 text-sm">loading</code> prop.
  </p>

  <ComponentExample demo="SelectLoadingDemo">
    <div class="flex flex-col gap-4">
      <p class="text-kumo-strong">Loading State</p>
      <SelectLoadingDemo client:load />
      <p class="text-kumo-strong">Loading From Server (simulated 2s delay)</p>
      <SelectLoadingDataDemo client:load />
    </div>
  </ComponentExample>
</ComponentSection>

{/* Multiple Item */}

<ComponentSection>
  <Heading level={3}>Multiple Item</Heading>
  <p>
    A select component with multiple selection enabled. The value is an
    array of selected items.
  </p>

  <ComponentExample demo="SelectMultipleDemo">
    <SelectMultipleDemo client:load />
  </ComponentExample>
</ComponentSection>

{/* More Example */}

<ComponentSection>
  <Heading level={3}>More Example</Heading>

  <ComponentExample demo="SelectComplexDemo">
    <SelectComplexDemo client:load />
  </ComponentExample>
</ComponentSection>

{/* Disabled Options */}

<ComponentSection>
  <Heading level={3}>Disabled Options</Heading>
  <p>
    Options can be disabled with the <code class="rounded bg-kumo-control px-1 py-0.5 text-sm">disabled</code> prop.
    Disabled options are greyed out and cannot be selected.
  </p>

  <ComponentExample demo="SelectDisabledOptionsDemo">
    <SelectDisabledOptionsDemo client:load />
  </ComponentExample>
</ComponentSection>

{/* Disabled Items via Prop */}

<ComponentSection>
  <Heading level={3}>Disabled Items (via items prop)</Heading>
  <p>
    The <code class="rounded bg-kumo-control px-1 py-0.5 text-sm">items</code> object-map prop
    accepts descriptor objects with <code class="rounded bg-kumo-control px-1 py-0.5 text-sm">disabled</code> alongside
    plain string values.
  </p>

  <ComponentExample demo="SelectDisabledItemsDemo">
    <SelectDisabledItemsDemo client:load />
  </ComponentExample>
</ComponentSection>

{/* Grouped Options */}

<ComponentSection>
  <Heading level={3}>Grouped Options</Heading>
  <p>
    Use <code class="rounded bg-kumo-control px-1 py-0.5 text-sm">Select.Group</code>,{" "}
    <code class="rounded bg-kumo-control px-1 py-0.5 text-sm">Select.GroupLabel</code>, and{" "}
    <code class="rounded bg-kumo-control px-1 py-0.5 text-sm">Select.Separator</code> to
    organize options under labeled headers with visual dividers.
  </p>

  <ComponentExample demo="SelectGroupedDemo">
    <SelectGroupedDemo client:load />
  </ComponentExample>
</ComponentSection>

{/* Groups with Disabled */}

<ComponentSection>
  <Heading level={3}>Groups with Disabled Options</Heading>
  <p>
    Combine groups, separators, and disabled options with info tooltips to
    clearly separate available and unavailable choices.
  </p>

  <ComponentExample demo="SelectGroupedWithDisabledDemo">
    <SelectGroupedWithDisabledDemo client:load />
  </ComponentExample>
</ComponentSection>

{/* Long List - Test scrolling behavior */}

<ComponentSection>
  <Heading level={3}>Long List (Scrolling Test)</Heading>
  <p>
    A select component with many options to test popup scrolling behavior.
    The popup should scroll smoothly without bounce/overscroll issues.
  </p>

  <ComponentExample demo="SelectLongListDemo">
    <SelectLongListDemo client:load />
  </ComponentExample>
</ComponentSection>

{/* API Reference */}

<ComponentSection>
  <Heading level={2}>API Reference</Heading>
  <Heading level={3}>Select</Heading>
  <PropsTable component="Select" />

<Heading level={3}>Select.Option</Heading>
<PropsTable component="Select.Option" />

  <Heading level={3}>Select.Group</Heading>
  <p>Groups related options together with an accessible <code class="rounded bg-kumo-control px-1 py-0.5 text-sm">role="group"</code>. Use with <code class="rounded bg-kumo-control px-1 py-0.5 text-sm">Select.GroupLabel</code> to provide a visible heading.</p>

  <Heading level={3}>Select.GroupLabel</Heading>
  <p>A visible heading for a <code class="rounded bg-kumo-control px-1 py-0.5 text-sm">Select.Group</code>. Automatically associated with its parent group for accessibility.</p>

  <Heading level={3}>Select.Separator</Heading>
  <p>A visual divider line between option groups. Renders with <code class="rounded bg-kumo-control px-1 py-0.5 text-sm">role="separator"</code>.</p>
</ComponentSection>
