import { useState } from "react";
import { Select } from "@cloudflare/kumo";
/** Basic Select with visible label - the recommended pattern. */
export function SelectBasicDemo() {
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" }}
/>
);
} Installation
Barrel
import { Select } from "@cloudflare/kumo"; Granular
import { Select } from "@cloudflare/kumo/components/select"; Usage
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" }}
/>
);
} Examples
Basic
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 { useState } from "react";
import { Select } from "@cloudflare/kumo";
/** Basic Select with visible label - the recommended pattern. */
export function SelectBasicDemo() {
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 { useState } from "react";
import { Select } from "@cloudflare/kumo";
/** Select without visible label - use aria-label for accessibility. */
export function SelectWithoutLabelDemo() {
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.
import { useState } from "react";
import { Select } from "@cloudflare/kumo";
/** Select with label, description, and error handling. */
export function SelectWithFieldDemo() {
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 { useState } from "react";
import { Select } from "@cloudflare/kumo";
/** Select with placeholder text when no value is selected. */
export function SelectPlaceholderDemo() {
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 { useState } from "react";
import { Select } from "@cloudflare/kumo";
/** Select with label tooltip for additional context. */
export function SelectWithTooltipDemo() {
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.
import { useState } from "react";
import { Select } from "@cloudflare/kumo";
/** Select with custom rendering for complex option display. */
export function SelectCustomRenderingDemo() {
const [value, setValue] = useState(languages[0]);
return (
<Select
label="Language"
className="w-[200px]"
renderValue={(v) => (
<span>
{v.emoji} {v.label}
</span>
)}
value={value}
onValueChange={(v) => setValue(v as (typeof languages)[0])}
>
{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.
<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> 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)
import { Select } from "@cloudflare/kumo";
/** Select in loading state. */
export function SelectLoadingDemo() {
return <Select aria-label="Loading select" className="w-[200px]" loading />;
} Multiple Item
A select component with multiple selection enabled. The value is an array of selected items.
import { useState } from "react";
import { Select } from "@cloudflare/kumo";
/** Multi-select for choosing multiple values. */
export function SelectMultipleDemo() {
const [value, setValue] = useState<string[]>(["Name", "Location", "Size"]);
return (
<Select
label="Visible Columns"
className="w-[250px]"
multiple
renderValue={(value) => {
if (value.length > 3) {
return (
<span className="line-clamp-1">
{value.slice(0, 2).join(", ") + ` and ${value.length - 2} more`}
</span>
);
}
return <span>{value.join(", ")}</span>;
}}
value={value}
onValueChange={(v) => setValue(v as string[])}
>
<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
import { useState } from "react";
import { Select, Text } from "@cloudflare/kumo";
/** Select with complex object values and custom option rendering. */
export function SelectComplexDemo() {
const [value, setValue] = useState<(typeof authors)[0] | null>(null);
return (
<Select
label="Author"
description="Select the primary author for this document"
className="w-[200px]"
onValueChange={(v) => setValue(v as (typeof authors)[0] | null)}
value={value}
isItemEqualToValue={(item, value) => item?.id === value?.id}
renderValue={(author) => {
return author?.name ?? "Select an author";
}}
>
{authors.map((author) => (
<Select.Option key={author.id} value={author}>
<div className="flex w-[300px] items-center justify-between gap-2">
<Text>{author.name}</Text>
<Text variant="secondary">{author.title}</Text>
</div>
</Select.Option>
))}
</Select>
);
} Disabled Options
Options can be disabled with the disabled prop.
Disabled options are greyed out and cannot be selected.
import { useState } from "react";
import { Select } from "@cloudflare/kumo";
/** Select with disabled options that cannot be selected. */
export function SelectDisabledOptionsDemo() {
const [value, setValue] = useState<string | null>(null);
return (
<Select
label="Deployment Region"
placeholder="Choose a region..."
className="w-[250px]"
value={value}
onValueChange={(v) => setValue(v as string | null)}
>
<Select.Option value="us-east">US East</Select.Option>
<Select.Option value="us-west">US West</Select.Option>
<Select.Option value="eu-west" disabled>
EU West
</Select.Option>
<Select.Option value="ap-south" disabled>
AP South
</Select.Option>
</Select>
);
} Disabled Items (via items prop)
The items object-map prop
accepts descriptor objects with disabled alongside
plain string values.
import { useState } from "react";
import { Select } from "@cloudflare/kumo";
/** Select using the items prop with disabled descriptors. */
export function SelectDisabledItemsDemo() {
const [value, setValue] = useState<string | null>("free");
return (
<Select
label="Plan"
className="w-[200px]"
value={value}
onValueChange={(v) => setValue(v as string | null)}
items={{
free: "Free",
pro: "Pro",
business: { label: "Business", disabled: true },
enterprise: { label: "Enterprise", disabled: true },
}}
/>
);
} Grouped Options
Use Select.Group,
Select.GroupLabel, and
Select.Separator to
organize options under labeled headers with visual dividers.
import { useState } from "react";
import { Select } from "@cloudflare/kumo";
/** Select with grouped options and separators. */
export function SelectGroupedDemo() {
const [value, setValue] = useState<string | null>(null);
return (
<Select
label="Food"
placeholder="Pick a food..."
className="w-[220px]"
value={value}
onValueChange={(v) => setValue(v as string | null)}
>
<Select.Group>
<Select.GroupLabel>Fruits</Select.GroupLabel>
<Select.Option value="apple">Apple</Select.Option>
<Select.Option value="banana">Banana</Select.Option>
<Select.Option value="cherry">Cherry</Select.Option>
</Select.Group>
<Select.Separator />
<Select.Group>
<Select.GroupLabel>Vegetables</Select.GroupLabel>
<Select.Option value="carrot">Carrot</Select.Option>
<Select.Option value="broccoli">Broccoli</Select.Option>
<Select.Option value="spinach">Spinach</Select.Option>
</Select.Group>
</Select>
);
} Groups with Disabled Options
Combine groups, separators, and disabled options with info tooltips to clearly separate available and unavailable choices.
import { useState } from "react";
import { Select } from "@cloudflare/kumo";
/** Select combining groups, separators, and disabled options. */
export function SelectGroupedWithDisabledDemo() {
const [value, setValue] = useState<string | null>(null);
return (
<Select
label="Server Region"
placeholder="Select a region..."
className="w-[260px]"
value={value}
onValueChange={(v) => setValue(v as string | null)}
>
<Select.Group>
<Select.GroupLabel>Available</Select.GroupLabel>
<Select.Option value="us-east-1">US East (N. Virginia)</Select.Option>
<Select.Option value="us-west-2">US West (Oregon)</Select.Option>
<Select.Option value="eu-west-1">EU West (Ireland)</Select.Option>
</Select.Group>
<Select.Separator />
<Select.Group>
<Select.GroupLabel>Unavailable</Select.GroupLabel>
<Select.Option value="ap-south-1" disabled>
AP South (Mumbai)
</Select.Option>
<Select.Option value="sa-east-1" disabled>
SA East (São Paulo)
</Select.Option>
</Select.Group>
</Select>
);
} Long List (Scrolling Test)
A select component with many options to test popup scrolling behavior. The popup should scroll smoothly without bounce/overscroll issues.
Tests scrolling behavior with many options
import { useState } from "react";
import { Select } from "@cloudflare/kumo";
/** Select with a long list to test popup scrolling behavior. */
export function SelectLongListDemo() {
const [value, setValue] = useState<string | null>(null);
return (
<Select
label="Long List Select"
description="Tests scrolling behavior with many options"
placeholder="Choose an option..."
className="w-[220px]"
value={value}
onValueChange={(v) => setValue(v as string | null)}
>
{longListItems.map((item) => (
<Select.Option key={item.value} value={item.value}>
{item.label}
</Select.Option>
))}
</Select>
);
} API Reference
Select
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | - | Additional CSS classes merged via `cn()`. |
| label | ReactNode | - | 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. |
| hideLabel | boolean | - | - |
| placeholder | string | - | Placeholder text shown when no value is selected. |
| loading | boolean | - | When `true`, shows a skeleton loader in place of the selected value. |
| disabled | boolean | - | Whether the select is disabled. |
| required | boolean | - | Whether the select is required. When `false`, shows "(optional)" text. |
| labelTooltip | ReactNode | - | Tooltip content displayed next to the label via an info icon. |
| value | string | - | Currently selected value (controlled mode). |
| children | ReactNode | - | `Select.Option` elements to render in the dropdown. |
| description | ReactNode | - | Helper text displayed below the select. |
| error | string | object | - | Error message string or validation error object with `match` key. |
| onValueChange | (value: string) => void | - | Callback when selection changes |
| defaultValue | string | - | Initial value for uncontrolled mode |
Select.Option
| Prop | Type | Default |
|---|
No component-specific props. Accepts standard HTML attributes.
Select.Group
Groups related options together with an accessible role=“group”. Use with Select.GroupLabel to provide a visible heading.
Select.GroupLabel
A visible heading for a Select.Group. Automatically associated with its parent group for accessibility.
Select.Separator
A visual divider line between option groups. Renders with role=“separator”.