Select

Enables users to choose from a list of options presented in a dropdown.

llms.txt
	<script lang="ts">
  import { Select } from "bits-ui";
  import Check from "phosphor-svelte/lib/Check";
  import Palette from "phosphor-svelte/lib/Palette";
  import CaretUpDown from "phosphor-svelte/lib/CaretUpDown";
  import CaretDoubleUp from "phosphor-svelte/lib/CaretDoubleUp";
  import CaretDoubleDown from "phosphor-svelte/lib/CaretDoubleDown";
 
  const themes = [
    { value: "light-monochrome", label: "Light Monochrome" },
    { value: "dark-green", label: "Dark Green" },
    { value: "svelte-orange", label: "Svelte Orange" },
    { value: "punk-pink", label: "Punk Pink" },
    { value: "ocean-blue", label: "Ocean Blue", disabled: true },
    { value: "sunset-orange", label: "Sunset Orange" },
    { value: "sunset-red", label: "Sunset Red" },
    { value: "forest-green", label: "Forest Green" },
    { value: "lavender-purple", label: "Lavender Purple", disabled: true },
    { value: "mustard-yellow", label: "Mustard Yellow" },
    { value: "slate-gray", label: "Slate Gray" },
    { value: "neon-green", label: "Neon Green" },
    { value: "coral-reef", label: "Coral Reef" },
    { value: "midnight-blue", label: "Midnight Blue" },
    { value: "crimson-red", label: "Crimson Red" },
    { value: "mint-green", label: "Mint Green" },
    { value: "pastel-pink", label: "Pastel Pink" },
    { value: "golden-yellow", label: "Golden Yellow" },
    { value: "deep-purple", label: "Deep Purple" },
    { value: "turquoise-blue", label: "Turquoise Blue" },
    { value: "burnt-orange", label: "Burnt Orange" }
  ];
 
  let value = $state<string>("");
  const selectedLabel = $derived(
    value
      ? themes.find((theme) => theme.value === value)?.label
      : "Select a theme"
  );
</script>
 
<Select.Root type="single" onValueChange={(v) => (value = v)} items={themes}>
  <Select.Trigger
    class="h-input rounded-9px border-border-input bg-background data-placeholder:text-foreground-alt/50 inline-flex w-[296px] touch-none select-none items-center border px-[11px] text-sm transition-colors"
    aria-label="Select a theme"
  >
    <Palette class="text-muted-foreground mr-[9px] size-6" />
    {selectedLabel}
    <CaretUpDown class="text-muted-foreground ml-auto size-6" />
  </Select.Trigger>
  <Select.Portal>
    <Select.Content
      class="focus-override border-muted bg-background shadow-popover data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 outline-hidden z-50 h-96 max-h-[var(--bits-select-content-available-height)] w-[var(--bits-select-anchor-width)] min-w-[var(--bits-select-anchor-width)] select-none rounded-xl border px-1 py-3 data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1"
      sideOffset={10}
    >
      <Select.ScrollUpButton class="flex w-full items-center justify-center">
        <CaretDoubleUp class="size-3" />
      </Select.ScrollUpButton>
      <Select.Viewport class="p-1">
        {#each themes as theme, i (i + theme.value)}
          <Select.Item
            class="rounded-button data-highlighted:bg-muted outline-hidden data-disabled:opacity-50 flex h-10 w-full select-none items-center py-3 pl-5 pr-1.5 text-sm capitalize"
            value={theme.value}
            label={theme.label}
            disabled={theme.disabled}
          >
            {#snippet children({ selected })}
              {theme.label}
              {#if selected}
                <div class="ml-auto">
                  <Check aria-label="check" />
                </div>
              {/if}
            {/snippet}
          </Select.Item>
        {/each}
      </Select.Viewport>
      <Select.ScrollDownButton class="flex w-full items-center justify-center">
        <CaretDoubleDown class="size-3" />
      </Select.ScrollDownButton>
    </Select.Content>
  </Select.Portal>
</Select.Root>

Overview

The Select component provides users with a selectable list of options. It's designed to offer an enhanced selection experience with features like typeahead search, keyboard navigation, and customizable grouping. This component is particularly useful for scenarios where users need to choose from a predefined set of options, offering more functionality than a standard select element.

Key Features

  • Typeahead Search: Users can quickly find options by typing
  • Keyboard Navigation: Full support for keyboard interactions, allowing users to navigate through options using arrow keys, enter to select, and more.
  • Grouped Options: Ability to organize options into logical groups, enhancing readability and organization of large option sets.
  • Scroll Management: Includes scroll up/down buttons for easy navigation in long lists.
  • Accessibility: Built-in ARIA attributes and keyboard support ensure compatibility with screen readers and adherence to accessibility standards.
  • Portal Support: Option to render the select content in a portal, preventing layout issues in complex UI structures.

Architecture

The Select component is composed of several sub-components, each with a specific role:

  • Root: The main container component that manages the state and context for the combobox.
  • Trigger: The button or element that opens the dropdown list.
  • Portal: Responsible for portalling the dropdown content to the body or a custom target.
  • Group: A container for grouped items, used to group related items.
  • GroupHeading: A heading for a group of items, providing a descriptive label for the group.
  • Item: An individual item within the list.
  • Separator: A visual separator between items.
  • Content: The dropdown container that displays the items. It uses Floating UI to position the content relative to the trigger.
  • ContentStatic (Optional): An alternative to the Content component, that enables you to opt-out of Floating UI and position the content yourself.
  • Viewport: The visible area of the dropdown content, used to determine the size and scroll behavior.
  • ScrollUpButton: A button that scrolls the content up when the content is larger than the viewport.
  • ScrollDownButton: A button that scrolls the content down when the content is larger than the viewport.
  • Arrow: An arrow element that points to the trigger when using the Combobox.Content component.

Structure

Here's an overview of how the Select component is structured in code:

	<script lang="ts">
  import { Select } from "bits-ui";
</script>
 
<Select.Root>
  <Select.Trigger />
  <Select.Portal>
    <Select.Content>
      <Select.ScrollUpButton />
      <Select.Viewport>
        <Select.Item />
        <Select.Group>
          <Select.GroupHeading />
          <Select.Item />
        </Select.Group>
        <Select.ScrollDownButton />
      </Select.Viewport>
    </Select.Content>
  </Select.Portal>
</Select.Root>

Reusable Components

As you can see from the structure above, there are a number of pieces that make up the Select component. These pieces are provided to give you maximum flexibility and customization options, but can be a burden to write out everywhere you need to use a select in your application.

To ease this burden, it's recommended to create your own reusable select component that wraps the primitives and provides a more convenient API for your use cases.

Here's an example of how you might create a reusable MySelect component that receives a list of options and renders each of them as an item.

MySelect.svelte
	<script lang="ts">
  import { Select, type WithoutChildren } from "bits-ui";
 
  type Props = WithoutChildren<Select.RootProps> & {
    placeholder?: string;
    items: { value: string; label: string; disabled?: boolean }[];
    contentProps?: WithoutChildren<Select.ContentProps>;
    // any other specific component props if needed
  };
 
  let {
    value = $bindable(),
    items,
    contentProps,
    placeholder,
    ...restProps
  }: Props = $props();
 
  const selectedLabel = $derived(
    items.find((item) => item.value === value)?.label
  );
</script>
 
<!--
TypeScript Discriminated Unions + destructing (required for "bindable") do not
get along, so we shut typescript up by casting `value` to `never`, however,
from the perspective of the consumer of this component, it will be typed appropriately.
-->
<Select.Root bind:value={value as never} {...restProps}>
  <Select.Trigger>
    {selectedLabel ? selectedLabel : placeholder}
  </Select.Trigger>
  <Select.Portal>
    <Select.Content {...contentProps}>
      <Select.ScrollUpButton>up</Select.ScrollUpButton>
      <Select.Viewport>
        {#each items as { value, label, disabled } (value)}
          <Select.Item {value} {label} {disabled}>
            {#snippet children({ selected })}
              {selected ? "✅" : ""}
              {label}
            {/snippet}
          </Select.Item>
        {/each}
      </Select.Viewport>
      <Select.ScrollDownButton>down</Select.ScrollDownButton>
    </Select.Content>
  </Select.Portal>
</Select.Root>

You can then use the MySelect component throughout your application like so:

	<script lang="ts">
  import MySelect from "$lib/components/MySelect.svelte";
 
  const items = [
    { value: "apple", label: "Apple" },
    { value: "banana", label: "Banana" },
    { value: "cherry", label: "Cherry" },
  ];
 
  let fruit = $state("apple");
</script>
 
<MySelect {items} bind:value={fruit} />

Managing Value State

This section covers how to manage the value state of the component.

Two-Way Binding

Use bind:value for simple, automatic state synchronization:

	<script lang="ts">
  import { Select } from "bits-ui";
  let myValue = $state("");
</script>
 
<button onclick={() => (myValue = "A")}> Select A </button>
 
<Select.Root type="single" bind:value={myValue}>
  <!-- ... -->
</Select.Root>

Fully Controlled

Use a Function Binding for complete control over the state's reads and writes.

	<script lang="ts">
  import { Select } from "bits-ui";
  let myValue = $state("");
 
  function getValue() {
    return myValue;
  }
 
  function setValue(newValue: string) {
    myValue = newValue;
  }
</script>
 
<Select.Root type="single" bind:value={getValue, setValue}>
  <!-- ... -->
</Select.Root>

Managing Open State

This section covers how to manage the open state of the component.

Two-Way Binding

Use bind:open for simple, automatic state synchronization:

	<script lang="ts">
  import { Select } from "bits-ui";
  let myOpen = $state(false);
</script>
 
<button onclick={() => (myOpen = true)}> Open </button>
 
<Select.Root bind:open={myOpen}>
  <!-- ... -->
</Select.Root>

Fully Controlled

Use a Function Binding for complete control over the state's reads and writes.

	<script lang="ts">
  import { Select } from "bits-ui";
  let myOpen = $state(false);
 
  function getOpen() {
    return myOpen;
  }
 
  function setOpen(newOpen: boolean) {
    myOpen = newOpen;
  }
</script>
 
<Select.Root bind:open={getOpen, setOpen}>
  <!-- ... -->
</Select.Root>

Multiple Selection

The type prop can be set to 'multiple' to allow multiple items to be selected at a time.

	<script lang="ts">
  import { Select } from "bits-ui";
 
  let value = $state<string[]>([]);
</script>
 
<Select.Root type="multiple" bind:value>
  <!-- ... -->
</Select.Root>
	<script lang="ts">
  import { Select } from "bits-ui";
  import CaretDoubleDown from "phosphor-svelte/lib/CaretDoubleDown";
  import CaretDoubleUp from "phosphor-svelte/lib/CaretDoubleUp";
  import CaretUpDown from "phosphor-svelte/lib/CaretUpDown";
  import Check from "phosphor-svelte/lib/Check";
  import Palette from "phosphor-svelte/lib/Palette";
 
  const themes = [
    { value: "light-monochrome", label: "Light Monochrome" },
    { value: "dark-green", label: "Dark Green" },
    { value: "svelte-orange", label: "Svelte Orange" },
    { value: "punk-pink", label: "Punk Pink" },
    { value: "ocean-blue", label: "Ocean Blue" },
    { value: "sunset-red", label: "Sunset Red" },
    { value: "forest-green", label: "Forest Green" },
    { value: "lavender-purple", label: "Lavender Purple" },
    { value: "mustard-yellow", label: "Mustard Yellow" },
    { value: "slate-gray", label: "Slate Gray" },
    { value: "neon-green", label: "Neon Green" },
    { value: "coral-reef", label: "Coral Reef" },
    { value: "midnight-blue", label: "Midnight Blue" },
    { value: "crimson-red", label: "Crimson Red" },
    { value: "mint-green", label: "Mint Green" },
    { value: "pastel-pink", label: "Pastel Pink" },
    { value: "golden-yellow", label: "Golden Yellow" },
    { value: "deep-purple", label: "Deep Purple" },
    { value: "turquoise-blue", label: "Turquoise Blue" },
    { value: "burnt-orange", label: "Burnt Orange" }
  ];
 
  let value = $state<string[]>([]);
  const selectedLabel = $derived(
    value.length
      ? themes
          .filter((theme) => value.includes(theme.value))
          .map((theme) => theme.label)
          .join(", ")
      : "Select your favorite themes"
  );
</script>
 
<Select.Root type="multiple" bind:value>
  <Select.Trigger
    class="h-input rounded-9px border-border-input bg-background data-placeholder:text-foreground-alt/50 inline-flex w-[296px] touch-none select-none items-center border px-[11px] text-sm transition-colors"
    aria-label="Select a theme"
  >
    <Palette class="text-muted-foreground mr-[9px] size-6" />
    <span class="w-[calc(296px-11px-11px-9px)] truncate text-start">
      {selectedLabel}
    </span>
    <CaretUpDown class="text-muted-foreground ml-auto size-6" />
  </Select.Trigger>
  <Select.Portal>
    <Select.Content
      class="focus-override border-muted bg-background shadow-popover data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 outline-hidden z-50 h-96 max-h-[var(--bits-select-content-available-height)] w-[var(--bits-select-anchor-width)] min-w-[var(--bits-select-anchor-width)] select-none rounded-xl border px-1 py-3 data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1"
      sideOffset={10}
    >
      <Select.ScrollUpButton class="flex w-full items-center justify-center">
        <CaretDoubleUp class="size-3" />
      </Select.ScrollUpButton>
      <Select.Viewport class="p-1">
        {#each themes as theme, i (i + theme.value)}
          <Select.Item
            class="rounded-button data-highlighted:bg-muted outline-hidden flex h-10 w-full select-none items-center py-3 pl-5 pr-1.5 text-sm capitalize"
            value={theme.value}
            label={theme.label}
          >
            {#snippet children({ selected })}
              {theme.label}
              {#if selected}
                <div class="ml-auto">
                  <Check />
                </div>
              {/if}
            {/snippet}
          </Select.Item>
        {/each}
      </Select.Viewport>
      <Select.ScrollDownButton class="flex w-full items-center justify-center">
        <CaretDoubleDown class="size-3" />
      </Select.ScrollDownButton>
    </Select.Content>
  </Select.Portal>
</Select.Root>

Opt-out of Floating UI

When you use the Select.Content component, Bits UI uses Floating UI to position the content relative to the trigger, similar to other popover-like components.

You can opt-out of this behavior by instead using the Select.ContentStatic component.

	<Select.Root>
  <Select.Trigger />
  <Select.Portal>
    <Select.ContentStatic>
      <Select.ScrollUpButton />
      <Select.Viewport>
        <Select.Item />
        <Select.Group>
          <Select.GroupHeading />
          <Select.Item />
        </Select.Group>
        <Select.ScrollDownButton />
      </Select.Viewport>
    </Select.ContentStatic>
  </Select.Portal>
</Select.Root>

When using this component, you'll need to handle the positioning of the content yourself. Keep in mind that using Select.Portal alongside Select.ContentStatic may result in some unexpected positioning behavior, feel free to not use the portal or work around it.

Custom Anchor

By default, the Select.Content is anchored to the Select.Trigger component, which determines where the content is positioned.

If you wish to instead anchor the content to a different element, you can pass either a selector string or an HTMLElement to the customAnchor prop of the Select.Content component.

	<script lang="ts">
  import { Select } from "bits-ui";
 
  let customAnchor = $state<HTMLElement>(null!);
</script>
 
<div bind:this={customAnchor}></div>
 
<Select.Root>
  <Select.Trigger />
  <Select.Content {customAnchor}>
    <!-- ... -->
  </Select.Content>
</Select.Root>
Custom Anchor

What is the Viewport?

The Select.Viewport component is used to determine the size of the content in order to determine whether or not the scroll up and down buttons should be rendered.

If you wish to set a minimum/maximum height for the select content, you should apply it to the Select.Viewport component.

Scroll Up/Down Buttons

The Select.ScrollUpButton and Select.ScrollDownButton components are used to render the scroll up and down buttons when the select content is larger than the viewport.

You must use the Select.Viewport component when using the scroll buttons.

Custom Scroll Delay

The initial and subsequent scroll delays can be controlled using the delay prop on the buttons.

For example, we can use the cubicOut easing function from Svelte to create a smooth scrolling effect that speeds up over time.

	<script lang="ts">
  import { Select } from "bits-ui";
  import Check from "phosphor-svelte/lib/Check";
  import Palette from "phosphor-svelte/lib/Palette";
  import CaretUpDown from "phosphor-svelte/lib/CaretUpDown";
  import CaretDoubleUp from "phosphor-svelte/lib/CaretDoubleUp";
  import CaretDoubleDown from "phosphor-svelte/lib/CaretDoubleDown";
  import { cubicOut } from "svelte/easing";
 
  const themes = [
    { value: "light-monochrome", label: "Light Monochrome" },
    { value: "dark-green", label: "Dark Green" },
    { value: "svelte-orange", label: "Svelte Orange" },
    { value: "punk-pink", label: "Punk Pink" },
    { value: "ocean-blue", label: "Ocean Blue", disabled: true },
    { value: "sunset-orange", label: "Sunset Orange" },
    { value: "sunset-red", label: "Sunset Red" },
    { value: "forest-green", label: "Forest Green" },
    { value: "lavender-purple", label: "Lavender Purple", disabled: true },
    { value: "mustard-yellow", label: "Mustard Yellow" },
    { value: "slate-gray", label: "Slate Gray" },
    { value: "neon-green", label: "Neon Green" },
    { value: "coral-reef", label: "Coral Reef" },
    { value: "midnight-blue", label: "Midnight Blue" },
    { value: "crimson-red", label: "Crimson Red" },
    { value: "mint-green", label: "Mint Green" },
    { value: "pastel-pink", label: "Pastel Pink" },
    { value: "golden-yellow", label: "Golden Yellow" },
    { value: "deep-purple", label: "Deep Purple" },
    { value: "turquoise-blue", label: "Turquoise Blue" },
    { value: "burnt-orange", label: "Burnt Orange" }
  ];
 
  // Duplicate the menu items a couple of times to show off scrolling a big list
  const baseThemes = [...themes];
  for (let i = 0; i < 10; i++) {
    for (let baseTheme of baseThemes) {
      themes.push({ ...baseTheme, value: baseTheme.value + i });
    }
  }
 
  let value = $state<string>("");
  const selectedLabel = $derived(
    value
      ? themes.find((theme) => theme.value === value)?.label
      : "Select a theme"
  );
 
  function autoScrollDelay(tick: number) {
    const maxDelay = 200;
    const minDelay = 25;
    const steps = 30;
 
    const progress = Math.min(tick / steps, 1);
    // Use the cubicOut easing function from svelte/easing
    return maxDelay - (maxDelay - minDelay) * cubicOut(progress);
  }
</script>
 
<Select.Root type="single" onValueChange={(v) => (value = v)} items={themes}>
  <Select.Trigger
    class="h-input rounded-9px border-border-input bg-background data-placeholder:text-foreground-alt/50 inline-flex w-[296px] touch-none select-none items-center border px-[11px] text-sm transition-colors"
    aria-label="Select a theme"
  >
    <Palette class="text-muted-foreground mr-[9px] size-6" />
    {selectedLabel}
    <CaretUpDown class="text-muted-foreground ml-auto size-6" />
  </Select.Trigger>
  <Select.Portal>
    <Select.Content
      class="focus-override border-muted bg-background shadow-popover data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 outline-hidden z-50 h-96 max-h-[var(--bits-select-content-available-height)] w-[var(--bits-select-anchor-width)] min-w-[var(--bits-select-anchor-width)] select-none rounded-xl border px-1 py-3 data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1"
      sideOffset={10}
    >
      <Select.ScrollUpButton
        class="flex w-full items-center justify-center"
        delay={autoScrollDelay}
      >
        <CaretDoubleUp class="size-3" />
      </Select.ScrollUpButton>
      <Select.Viewport class="p-1">
        {#each themes as theme, i (i + theme.value)}
          <Select.Item
            class="rounded-button data-highlighted:bg-muted outline-hidden data-disabled:opacity-50 flex h-10 w-full select-none items-center py-3 pl-5 pr-1.5 text-sm  capitalize"
            value={theme.value}
            label={theme.label}
            disabled={theme.disabled}
          >
            {#snippet children({ selected })}
              {theme.label}
              {#if selected}
                <div class="ml-auto">
                  <Check />
                </div>
              {/if}
            {/snippet}
          </Select.Item>
        {/each}
      </Select.Viewport>
      <Select.ScrollDownButton
        class="flex w-full items-center justify-center"
        delay={autoScrollDelay}
      >
        <CaretDoubleDown class="size-3" />
      </Select.ScrollDownButton>
    </Select.Content>
  </Select.Portal>
</Select.Root>

Native Scrolling/Overflow

If you don't want to use the scroll buttons and prefer to use the standard scrollbar/overflow behavior, you can omit the Select.Scroll[Up|Down]Button components and the Select.Viewport component.

You'll need to set a height on the Select.Content component and appropriate overflow styles to enable scrolling.

Scroll Lock

By default, when a user opens the select, scrolling outside the content will not be disabled. You can override this behavior by setting the preventScroll prop to true.

	<Select.Content preventScroll={true}>
  <!-- ... -->
</Select.Content>

Highlighted Items

The Select component follows the WAI-ARIA descendant pattern for highlighting items. This means that the Select.Trigger retains focus the entire time, even when navigating with the keyboard, and items are highlighted as the user navigates them.

Styling Highlighted Items

You can use the data-highlighted attribute on the Select.Item component to style the item differently when it is highlighted.

onHighlight / onUnhighlight

To trigger side effects when an item is highlighted or unhighlighted, you can use the onHighlight and onUnhighlight props.

	<Select.Item onHighlight={() => console.log('I am highlighted!')} onUnhighlight={() => console.log('I am unhighlighted!')} />
<!-- ... -->
</Select.Item>

Svelte Transitions

You can use the forceMount prop along with the child snippet to forcefully mount the Select.Content component to use Svelte Transitions or another animation library that requires more control.

	<script lang="ts">
  import { Select } from "bits-ui";
  import { fly } from "svelte/transition";
</script>
 
<Select.Content forceMount>
  {#snippet child({ wrapperProps, props, open })}
    {#if open}
      <div {...wrapperProps}>
        <div {...props} transition:fly>
          <!-- ... -->
        </div>
      </div>
    {/if}
  {/snippet}
</Select.Content>

Of course, this isn't the prettiest syntax, so it's recommended to create your own reusable content component that handles this logic if you intend to use this approach. For more information on using transitions with Bits UI components, see the Transitions documentation.

	<script lang="ts">
  import { Select } from "bits-ui";
  import Check from "phosphor-svelte/lib/Check";
  import Palette from "phosphor-svelte/lib/Palette";
  import CaretUpDown from "phosphor-svelte/lib/CaretUpDown";
  import CaretDoubleUp from "phosphor-svelte/lib/CaretDoubleUp";
  import CaretDoubleDown from "phosphor-svelte/lib/CaretDoubleDown";
  import { fly } from "svelte/transition";
 
  const themes = [
    { value: "light-monochrome", label: "Light Monochrome" },
    { value: "dark-green", label: "Dark Green" },
    { value: "svelte-orange", label: "Svelte Orange" },
    { value: "punk-pink", label: "Punk Pink" },
    { value: "ocean-blue", label: "Ocean Blue" },
    { value: "sunset-red", label: "Sunset Red" },
    { value: "forest-green", label: "Forest Green" },
    { value: "lavender-purple", label: "Lavender Purple" },
    { value: "mustard-yellow", label: "Mustard Yellow" },
    { value: "slate-gray", label: "Slate Gray" },
    { value: "neon-green", label: "Neon Green" },
    { value: "coral-reef", label: "Coral Reef" },
    { value: "midnight-blue", label: "Midnight Blue" },
    { value: "crimson-red", label: "Crimson Red" },
    { value: "mint-green", label: "Mint Green" },
    { value: "pastel-pink", label: "Pastel Pink" },
    { value: "golden-yellow", label: "Golden Yellow" },
    { value: "deep-purple", label: "Deep Purple" },
    { value: "turquoise-blue", label: "Turquoise Blue" },
    { value: "burnt-orange", label: "Burnt Orange" }
  ];
 
  let value = $state<string>("");
  const selectedLabel = $derived(
    value
      ? themes.find((theme) => theme.value === value)?.label
      : "Select a theme"
  );
</script>
 
<Select.Root type="single" bind:value items={themes}>
  <Select.Trigger
    class="h-input rounded-9px border-border-input bg-background placeholder:text-foreground-alt/50 inline-flex w-[296px] touch-none select-none items-center border px-[11px] text-sm transition-colors"
    aria-label="Select a theme"
  >
    <Palette class="text-muted-foreground mr-[9px] size-6" />
    {selectedLabel}
    <CaretUpDown class="text-muted-foreground ml-auto size-6" />
  </Select.Trigger>
  <Select.Portal>
    <Select.Content
      class="focus-override border-muted bg-background shadow-popover outline-hidden z-50 h-96 max-h-[var(--bits-select-content-available-height)] w-[var(--bits-select-anchor-width)] min-w-[var(--bits-select-anchor-width)] select-none rounded-xl border px-1 py-3"
      sideOffset={10}
      forceMount
    >
      {#snippet child({ wrapperProps, props, open })}
        {#if open}
          <div {...wrapperProps}>
            <div {...props} transition:fly={{ duration: 300 }}>
              <Select.ScrollUpButton
                class="flex w-full items-center justify-center"
              >
                <CaretDoubleUp class="size-3" />
              </Select.ScrollUpButton>
              <Select.Viewport class="p-1">
                {#each themes as theme, i (i + theme.value)}
                  <Select.Item
                    class="rounded-button data-highlighted:bg-muted outline-hidden flex h-10 w-full select-none items-center py-3 pl-5 pr-1.5 text-sm capitalize duration-75"
                    value={theme.value}
                    label={theme.label}
                  >
                    {#snippet children({ selected })}
                      {theme.label}
                      {#if selected}
                        <div class="ml-auto">
                          <Check />
                        </div>
                      {/if}
                    {/snippet}
                  </Select.Item>
                {/each}
              </Select.Viewport>
              <Select.ScrollDownButton
                class="flex w-full items-center justify-center"
              >
                <CaretDoubleDown class="size-3" />
              </Select.ScrollDownButton>
            </div>
          </div>
        {/if}
      {/snippet}
    </Select.Content>
  </Select.Portal>
</Select.Root>

API Reference

Select.Root

The root select component which manages & scopes the state of the select.

Property Details
type
value
onValueChange
open
onOpenChange
onOpenChangeComplete
disabled
name
required
scrollAlignment
loop
allowDeselect
items
autocomplete
children
Data Attribute Details

Select.Trigger

A button which toggles the select's open state.

Property Details
ref
children
child
Data Attribute Details
data-state
data-placeholder
data-disabled
data-select-trigger

Select.Content

The element which contains the select's items.

Property Details
side
sideOffset
align
alignOffset
arrowPadding
avoidCollisions
collisionBoundary
collisionPadding
sticky
hideWhenDetached
updatePositionStrategy
strategy
preventScroll
customAnchor
onEscapeKeydown
escapeKeydownBehavior
onInteractOutside
onFocusOutside
interactOutsideBehavior
preventOverflowTextSelection
dir
loop
forceMount
ref
children
child
Data Attribute Details
data-state
data-select-content
CSS Variable Details
--bits-select-content-transform-origin
--bits-select-content-available-width
--bits-select-content-available-height
--bits-select-anchor-width
--bits-select-anchor-height

Select.ContentStatic

The element which contains the select's items. (Static/No Floating UI)

Property Details
onEscapeKeydown
escapeKeydownBehavior
onInteractOutside
onFocusOutside
interactOutsideBehavior
onOpenAutoFocus
onCloseAutoFocus
trapFocus
preventScroll
preventOverflowTextSelection
dir
loop
forceMount
ref
children
child
Data Attribute Details
data-state
data-select-content

Select.Portal

When used, will render the select content into the body or custom to element when open

Property Details
to
disabled
children
Data Attribute Details

Select.Item

A select item, which must be a child of the select.Content component.

Property Details
value
label
disabled
onHighlight
onUnhighlight
ref
children
child
Data Attribute Details
data-value
data-label
data-disabled
data-highlighted
data-selected
data-select-item

Select.Viewport

An optional element to track the scroll position of the select for rendering the scroll up/down buttons.

Property Details
ref
children
child
Data Attribute Details
data-select-viewport

Select.ScrollUpButton

An optional scroll up button element to improve the scroll experience within the select. Should be used in conjunction with the select.Viewport component.

Property Details
delay
ref
children
child
Data Attribute Details
data-select-scroll-up-button

Select.ScrollDownButton

An optional scroll down button element to improve the scroll experience within the select. Should be used in conjunction with the select.Viewport component.

Property Details
delay
ref
children
child
Data Attribute Details
data-select-scroll-down-button

Select.Group

A group of related select items.

Property Details
ref
children
child
Data Attribute Details
data-select-group

Select.GroupHeading

A heading for the parent select group. This is used to describe a group of related select items.

Property Details
ref
children
child
Data Attribute Details
data-select-group-heading

Select.Arrow

An optional arrow element which points to the content when open.

Property Details
width
height
ref
children
child
Data Attribute Details
data-arrow