<script lang="ts">
import * as Select from "kumo-svelte/components/select";
let value = $state("apple");
</script>
<Select.Root
label="Favorite Fruit"
class="w-[200px]"
{value}
onValueChange={(next) => (value = next as string)}
items={{ apple: "Apple", banana: "Banana", cherry: "Cherry" }}
/>
Import
import * as Select from "kumo-svelte/components/select"; Usage
<script lang="ts">
import * as Select from "kumo-svelte/components/select";
let value = $state("apple");
</script>
<Select.Root
label="Favorite Fruit"
{value}
onValueChange={(next) => (value = next as string)}
items={{ apple: "Apple", banana: "Banana", cherry: "Cherry" }}
/> Examples
Basic
A select with a visible label. When you provide the label prop, the select renders with field labeling.
<script lang="ts">
import * as Select from "kumo-svelte/components/select";
let value = $state("apple");
</script>
<Select.Root
label="Favorite Fruit"
class="w-[200px]"
{value}
onValueChange={(next) => (value = next as string)}
items={{ apple: "Apple", banana: "Banana", cherry: "Cherry" }}
/>
Sizes
Use the size prop to match Input sizing (xs, sm, base, lg).
<script lang="ts">
import * as Select from "kumo-svelte/components/select";
const sizes = ["xs", "sm", "base", "lg"] as const;
</script>
<div class="grid gap-4">
{#each sizes as size}
<div class="flex items-center gap-3">
<span class="w-10 text-sm text-kumo-subtle">{size}</span>
<Select.Root
aria-label={`Select size ${size}`}
{size}
class="w-[200px]"
placeholder="Choose..."
items={{ a: "Option A", b: "Option B" }}
/>
</div>
{/each}
</div>
Without Visible Label
When a visible label isn’t needed, use aria-label for accessibility.
<script lang="ts">
import * as Select from "kumo-svelte/components/select";
let value = $state("apple");
</script>
<Select.Root
aria-label="Select a fruit"
class="w-[200px]"
{value}
onValueChange={(next) => (value = next as string)}
items={{ apple: "Apple", banana: "Banana", cherry: "Cherry" }}
/>
With Description
Select integrates with Field-style description text below the input.
Choose the category that best describes your issue
<script lang="ts">
import * as Select from "kumo-svelte/components/select";
let value = $state("");
</script>
<Select.Root
label="Issue Type"
description="Choose the category that best describes your issue"
class="w-[280px]"
{value}
onValueChange={(next) => (value = next as string)}
items={{ bug: "Bug", documentation: "Documentation", feature: "Feature" }}
/>
With Error
Pass the error prop to display a validation error. When an error is present, it replaces the description in the UI.
Please select an issue type
<script lang="ts">
import * as Select from "kumo-svelte/components/select";
</script>
<Select.Root
label="Issue Type"
error="Please select an issue type"
class="w-[280px]"
items={{ bug: "Bug", documentation: "Documentation", feature: "Feature" }}
/>
Placeholder
Use the placeholder prop to show text when no value is selected.
<script lang="ts">
import * as Select from "kumo-svelte/components/select";
let value = $state("");
</script>
<Select.Root
label="Category"
placeholder="Choose a category..."
class="w-[200px]"
{value}
onValueChange={(next) => (value = next as string)}
items={{ bug: "Bug", documentation: "Documentation", feature: "Feature" }}
/>
Label with Tooltip
Add a tooltip icon next to the label for additional context using labelTooltip.
<script lang="ts">
import * as Select from "kumo-svelte/components/select";
let value = $state("");
</script>
{#snippet labelTooltip()}
Higher priority issues are addressed first
{/snippet}
<Select.Root
label="Priority"
{labelTooltip}
placeholder="Select priority"
class="w-[200px]"
{value}
onValueChange={(next) => (value = next as string)}
items={{ low: "Low", medium: "Medium", high: "High", critical: "Critical" }}
/>
Custom Rendering
Use Select.Option children and labels to customize how options appear in the list.
<script lang="ts">
import * as Select from "kumo-svelte/components/select";
const languages = [
{ value: "en", label: "English", emoji: "🇬🇧" },
{ value: "fr", label: "French", emoji: "🇫🇷" },
{ value: "de", label: "German", emoji: "🇩🇪" },
{ value: "es", label: "Spanish", emoji: "🇪🇸" },
];
let value = $state("en");
</script>
<Select.Root label="Language" class="w-[200px]" {value} onValueChange={(next) => (value = next as string)}>
{#each languages as language (language.value)}
<Select.Option value={language.value} label={`${language.emoji} ${language.label}`}>
{language.emoji} {language.label}
</Select.Option>
{/each}
</Select.Root>
Loading
A select component with loading state. The loading state is passed via the loading prop.
Loading State
Loading From Server (simulated delay)
<script lang="ts">
import * as Select from "kumo-svelte/components/select";
</script>
<Select.Root aria-label="Loading select" class="w-[200px]" loading />
Multiple Selection
Enable multiple selection with the multiple prop. The value becomes an array of selected strings.
<script lang="ts">
import * as Select from "kumo-svelte/components/select";
let value = $state<string[]>(["Name", "Location", "Size"]);
</script>
<Select.Root
label="Visible Columns"
class="w-[250px]"
multiple
{value}
onValueChange={(next) => (value = next 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.Root>
More Example
Select the primary author for this document
<script lang="ts">
import { Text } from "kumo-svelte/components/text";
import * as Select from "kumo-svelte/components/select";
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" },
];
let value = $state("");
</script>
<Select.Root
label="Author"
description="Select the primary author for this document"
placeholder="Select an author"
class="w-[220px]"
{value}
onValueChange={(next) => (value = next as string)}
>
{#each authors as author (author.id)}
<Select.Option value={author.id} label={author.name}>
<div class="flex w-[300px] items-center justify-between gap-2">
<Text>{author.name}</Text>
<Text variant="secondary">{author.title}</Text>
</div>
</Select.Option>
{/each}
</Select.Root>
Disabled Options
Options can be disabled with the disabled prop. Disabled options are greyed out and cannot be selected.
<script lang="ts">
import * as Select from "kumo-svelte/components/select";
const regions = [
{ value: "us-east", label: "US East" },
{ value: "us-west", label: "US West" },
{ value: "eu-west", label: "EU West", disabled: true },
{ value: "ap-south", label: "AP South", disabled: true },
];
let value = $state("");
</script>
<Select.Root
label="Deployment Region"
placeholder="Choose a region..."
class="w-[250px]"
{value}
onValueChange={(next) => (value = next as string)}
>
{#each regions as region (region.value)}
<Select.Option value={region.value} disabled={region.disabled}>{region.label}</Select.Option>
{/each}
</Select.Root>
Disabled Items (via items prop)
The items object-map prop accepts descriptor objects with disabled alongside plain string values.
<script lang="ts">
import * as Select from "kumo-svelte/components/select";
let value = $state("free");
</script>
<Select.Root
label="Plan"
class="w-[200px]"
{value}
onValueChange={(next) => (value = next as string)}
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.
<script lang="ts">
import * as Select from "kumo-svelte/components/select";
const foods = {
fruits: [
{ value: "apple", label: "Apple" },
{ value: "banana", label: "Banana" },
{ value: "cherry", label: "Cherry" },
],
vegetables: [
{ value: "carrot", label: "Carrot" },
{ value: "broccoli", label: "Broccoli" },
{ value: "spinach", label: "Spinach" },
],
};
let value = $state("");
</script>
<Select.Root
label="Food"
placeholder="Pick a food..."
class="w-[220px]"
{value}
onValueChange={(next) => (value = next as string)}
>
<Select.Group>
<Select.GroupLabel>Fruits</Select.GroupLabel>
{#each foods.fruits as food (food.value)}
<Select.Option value={food.value}>{food.label}</Select.Option>
{/each}
</Select.Group>
<Select.Separator />
<Select.Group>
<Select.GroupLabel>Vegetables</Select.GroupLabel>
{#each foods.vegetables as food (food.value)}
<Select.Option value={food.value}>{food.label}</Select.Option>
{/each}
</Select.Group>
</Select.Root>
Groups with Disabled Options
Combine groups, separators, and disabled options to separate available and unavailable choices.
<script lang="ts">
import * as Select from "kumo-svelte/components/select";
const regions = {
available: [
{ value: "us-east-1", label: "US East (N. Virginia)" },
{ value: "us-west-2", label: "US West (Oregon)" },
{ value: "eu-west-1", label: "EU West (Ireland)" },
],
unavailable: [
{ value: "ap-south-1", label: "AP South (Mumbai)" },
{ value: "sa-east-1", label: "SA East (São Paulo)" },
],
};
let value = $state("");
</script>
<Select.Root
label="Server Region"
placeholder="Select a region..."
class="w-[260px]"
{value}
onValueChange={(next) => (value = next as string)}
>
<Select.Group>
<Select.GroupLabel>Available</Select.GroupLabel>
{#each regions.available as region (region.value)}
<Select.Option value={region.value}>{region.label}</Select.Option>
{/each}
</Select.Group>
<Select.Separator />
<Select.Group>
<Select.GroupLabel>Unavailable</Select.GroupLabel>
{#each regions.unavailable as region (region.value)}
<Select.Option value={region.value} disabled>{region.label}</Select.Option>
{/each}
</Select.Group>
</Select.Root>
Long List (Scrolling Test)
A select component with many options to test popup scrolling behavior.
Tests scrolling behavior with many options
<script lang="ts">
import * as Select from "kumo-svelte/components/select";
const items = Array.from({ length: 50 }, (_, index) => ({
value: `item-${index + 1}`,
label: `Option ${index + 1}`,
}));
let value = $state("");
</script>
<Select.Root
label="Long List Select"
description="Tests scrolling behavior with many options"
placeholder="Choose an option..."
class="w-[220px]"
{value}
onValueChange={(next) => (value = next as string)}
{items}
/>
API Reference
Select
| Prop | Type | Default |
|---|---|---|
| aria-label | string | — |
| aria-labelledby | string | — |
| allowDeselect | boolean | false |
| children | Snippet | — |
| class | string | — |
| container | PortalProps["to"] | — |
| defaultValue | string | string[] | — |
| description | Snippet | string | — |
| disabled | boolean | false |
| error | string | { message: Snippet | string; match: FieldErrorMatch } | — |
| hideLabel | boolean | false |
| items | SelectItems | — |
| label | Snippet | string | — |
| labelTooltip | Snippet | — |
| loading | boolean | false |
| multiple | boolean | false |
| name | string | — |
| onValueChange | (value: string | string[]) => void | — |
| placeholder | string | — |
| required | boolean | — |
| size | KumoSelectSize | KUMO_SELECT_DEFAULT_VARIANTS.size |
| value | string | string[] | — |
Select.Content
No component-specific props. This component accepts child content or standard forwarded attributes.
Select.Group
No component-specific props. This component accepts child content or standard forwarded attributes.
Select.GroupHeading
No component-specific props. This component accepts child content or standard forwarded attributes.
Select.Item
| Prop | Type | Default |
|---|---|---|
| children | Snippet | — |
| class | string | — |
| disabled | boolean | false |
| label | string | — |
| value* | string | — |
Select.Portal
No component-specific props. This component accepts child content or standard forwarded attributes.
Select.Separator
No component-specific props. This component accepts child content or standard forwarded attributes.
Select.Trigger
| Prop | Type | Default |
|---|---|---|
| icon | Snippet | — |
| loading | boolean | false |
| size | KumoSelectSize | KUMO_SELECT_DEFAULT_VARIANTS.size |
| showIcon | boolean | true |
Select.Value
No component-specific props. This component accepts child content or standard forwarded attributes.
Select.Viewport
No component-specific props. This component accepts child content or standard forwarded attributes.
Option Values
Select values are strings. For rich option rows, pass custom Select.Option children and a string label for the trigger display.