ShardsUI is in beta. APIs may change before 1.0.

Select

A single-select listbox.

Level
<script lang="ts">
  import { Select } from '@shardsui/svelte/select'

  const levels = [
    { label: 'Beginner', value: 'beginner' },
    { label: 'Intermediate', value: 'intermediate' },
    { label: 'Advanced', value: 'advanced' },
    { label: 'Expert', value: 'expert' }
  ]
</script>

<div class="flex flex-col gap-1">
  <Select.Root items={levels}>
    <Select.Label class="text-sm/5 font-semibold text-gray-900">Level</Select.Label>
    <Select.Trigger
      class="flex h-8 min-w-40 items-center justify-between gap-3 rounded-md border border-gray-200 bg-gray-50 pr-2 pl-3 text-sm font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-gray-950 data-popup-open:bg-gray-100"
    >
      <Select.Value class="data-placeholder:opacity-60" placeholder="Select level" />
      <Select.Icon class="flex">
        {@render chevronGrabberIcon()}
      </Select.Icon>
    </Select.Trigger>
    <Select.Portal>
      <Select.Positioner class="z-10 outline-hidden select-none" sideOffset={8}>
        <Select.Popup
          class="group min-w-(--anchor-width) origin-(--transform-origin) rounded-md bg-gray-50 bg-clip-padding text-gray-900 shadow-lg outline-1 outline-gray-200 transition-[transform,scale,opacity] duration-100 ease-out data-ending-style:scale-95 data-ending-style:opacity-0 data-starting-style:scale-95 data-starting-style:opacity-0"
        >
          <Select.List class="max-h-(--available-height) overflow-y-auto py-1">
            {#each levels as { label, value } (value)}
              <Select.Item
                {value}
                class="grid grid-cols-[1rem_1fr] items-center gap-2 py-1.5 pr-4 pl-2.5 text-sm/4 outline-hidden select-none data-highlighted:relative data-highlighted:z-0 data-highlighted:text-gray-50 data-highlighted:before:absolute data-highlighted:before:inset-x-1 data-highlighted:before:inset-y-0 data-highlighted:before:z-[-1] data-highlighted:before:rounded-sm data-highlighted:before:bg-gray-900"
              >
                <Select.ItemIndicator class="col-start-1">
                  {@render checkIcon()}
                </Select.ItemIndicator>
                <div class="col-start-2">{label}</div>
              </Select.Item>
            {/each}
          </Select.List>
        </Select.Popup>
      </Select.Positioner>
    </Select.Portal>
  </Select.Root>
</div>

{#snippet chevronGrabberIcon()}
  <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="size-4">
    <path
      d="M8 8.99981L11.1161 5.88369C11.6043 5.39554 12.3957 5.39554 12.8839 5.8837L16 8.99981"
      stroke="currentColor"
      stroke-width="1.5"
      stroke-linecap="round"
      stroke-linejoin="round"
    />
    <path
      d="M16 15L12.8839 18.1161C12.3957 18.6043 11.6043 18.6043 11.1161 18.1161L8 15"
      stroke="currentColor"
      stroke-width="1.5"
      stroke-linecap="round"
      stroke-linejoin="round"
    />
  </svg>
{/snippet}

{#snippet checkIcon()}
  <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="size-4">
    <path
      d="M6 14.15L10.0321 18L18 7"
      stroke="currentColor"
      stroke-width="1.5"
      stroke-linecap="round"
      stroke-linejoin="round"
    />
  </svg>
{/snippet}

Anatomy

<script>
  import { Select } from '@shardsui/svelte/select'
</script>

<Select.Root>
  <Select.Label />
  <Select.Trigger>
    <Select.Value />
    <Select.Icon />
  </Select.Trigger>
  <Select.Portal>
    <Select.Backdrop />
    <Select.Positioner>
      <Select.Popup>
        <Select.ScrollUpArrow />
        <Select.Arrow />

        <Select.List>
          <Select.Item>
            <Select.ItemIndicator />
          </Select.Item>

          <Select.Separator />

          <Select.Group>
            <Select.GroupLabel />
          </Select.Group>
        </Select.List>
        <Select.ScrollDownArrow />
      </Select.Popup>
    </Select.Positioner>
  </Select.Portal>
</Select.Root>

Usage guidelines

  • Prefer Combobox for large lists: Select has no filtering beyond typeahead (typing jumps to the matching item). Once the list grows long enough to need filtering, switch to Combobox.
  • Positioning: the popup anchors to the trigger through <Select.Positioner> — set side, align and the offsets there, and size the popup against the anchor CSS variables it publishes (see Styling).
  • Give the control an accessible name: add a <Select.Label>, or set an aria-label on <Select.Trigger> when there's no visible label. See the forms guide.

TypeScript

<Select.Root> infers its item type from the value prop, so every <Select.Item>'s value (and each entry in the items array) must share that type. Adding multiple flips that type to an array.

See the TypeScript guide for generic roots, typed wrappers, and bind:ref patterns.

Examples

Formatting the value

With no items, <Select.Value> stringifies the selected value. Give <Select.Root> an items prop — an array of { value, label } entries, groups of those, or a record mapping value to label — and it renders the matching label instead:

<script lang="ts">
  const items = [
    { value: null, label: 'Select theme' },
    { value: 'system', label: 'System default' },
    { value: 'light', label: 'Light' },
    { value: 'dark', label: 'Dark' }
  ]
</script>

<Select.Root {items}>
  <Select.Value />
</Select.Root>

For richer output, pass a children snippet to <Select.Value> and format the value yourself:

<script lang="ts">
  const items = {
    monospace: 'Monospace',
    serif: 'Serif',
    'sans-serif': 'Sans-serif'
  }
</script>

<Select.Value>
  {#snippet children(value)}
    <span style="font-family: {value as string}">
      {items[value as keyof typeof items]}
    </span>
  {/snippet}
</Select.Value>

You can skip the lookup entirely by giving each item an object value.

Labeling a select

Add a visible label for the trigger with <Select.Label>:

<Select.Root>
  <Select.Label>Theme</Select.Label>
  <!-- ... -->
</Select.Root>

Clicking the label moves focus to the trigger without opening the popup.

Placeholder values

Show prompt text before anything is chosen with the placeholder prop on <Select.Value>:

<script lang="ts">
  const items = [
    { value: 'system', label: 'System default' },
    { value: 'light', label: 'Light' },
    { value: 'dark', label: 'Dark' }
  ]
</script>

<Select.Root {items}>
  <Select.Value placeholder="Select theme" />
</Select.Root>

A placeholder alone gives no way back to the empty state from the select itself. To make it clearable from the popup rather than a separate reset button, add a null item to the list:

<script lang="ts">
  const items = [
    { value: null, label: 'Select theme' },
    { value: 'system', label: 'System default' },
    { value: 'light', label: 'Light' },
    { value: 'dark', label: 'Dark' }
  ]
</script>

<Select.Root {items}>
  <Select.Value />
</Select.Root>

An entry that labels null doubles as the empty-state text, so <Select.Value> renders it in place of any placeholder.

Multiple selection

Set the multiple prop on <Select.Root> and the value becomes an array of every chosen item. Render that array however you like through the <Select.Value> children snippet.

Topics
<script lang="ts">
  import { Select } from '@shardsui/svelte/select'

  const topics = {
    kerning: 'Kerning',
    contrast: 'Contrast ratio',
    flexbox: 'Flexbox',
    easing: 'Easing'
  }

  type Topic = keyof typeof topics
  const values = Object.keys(topics) as Topic[]

  let value = $state<Topic[]>(['kerning', 'contrast'])
</script>

<div class="flex flex-col gap-1">
  <Select.Root multiple bind:value items={topics}>
    <Select.Label class="text-sm/5 font-semibold text-gray-900">Topics</Select.Label>
    <Select.Trigger
      class="flex h-8 min-w-56 items-center justify-between gap-3 rounded-md border border-gray-200 bg-gray-50 pr-2 pl-3 text-sm font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-gray-950 data-popup-open:bg-gray-100"
    >
      <Select.Value class="data-placeholder:opacity-60">
        {#snippet children(current)}
          {@const selected = current as Topic[]}
          {#if selected.length === 0}
            Select topics
          {:else}
            {topics[selected[0]]}{selected.length > 1 ? ` (+${selected.length - 1} more)` : ''}
          {/if}
        {/snippet}
      </Select.Value>
      <Select.Icon class="flex">
        {@render chevronGrabberIcon()}
      </Select.Icon>
    </Select.Trigger>
    <Select.Portal>
      <Select.Positioner class="z-10 outline-hidden select-none" sideOffset={8}>
        <Select.Popup
          class="group min-w-(--anchor-width) origin-(--transform-origin) rounded-md bg-gray-50 bg-clip-padding text-gray-900 shadow-lg outline-1 outline-gray-200 transition-[transform,scale,opacity] duration-100 ease-out data-ending-style:scale-95 data-ending-style:opacity-0 data-starting-style:scale-95 data-starting-style:opacity-0"
        >
          <Select.List class="max-h-(--available-height) overflow-y-auto py-1">
            {#each values as v (v)}
              <Select.Item
                value={v}
                class="grid grid-cols-[1rem_1fr] items-center gap-2 py-1.5 pr-4 pl-2.5 text-sm/4 outline-hidden select-none data-highlighted:relative data-highlighted:z-0 data-highlighted:text-gray-50 data-highlighted:before:absolute data-highlighted:before:inset-x-1 data-highlighted:before:inset-y-0 data-highlighted:before:z-[-1] data-highlighted:before:rounded-sm data-highlighted:before:bg-gray-900"
              >
                <Select.ItemIndicator class="col-start-1">
                  {@render checkIcon()}
                </Select.ItemIndicator>
                <div class="col-start-2">{topics[v]}</div>
              </Select.Item>
            {/each}
          </Select.List>
        </Select.Popup>
      </Select.Positioner>
    </Select.Portal>
  </Select.Root>
</div>

{#snippet chevronGrabberIcon()}
  <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="size-4">
    <path
      d="M8 8.99981L11.1161 5.88369C11.6043 5.39554 12.3957 5.39554 12.8839 5.8837L16 8.99981"
      stroke="currentColor"
      stroke-width="1.5"
      stroke-linecap="round"
      stroke-linejoin="round"
    />
    <path
      d="M16 15L12.8839 18.1161C12.3957 18.6043 11.6043 18.6043 11.1161 18.1161L8 15"
      stroke="currentColor"
      stroke-width="1.5"
      stroke-linecap="round"
      stroke-linejoin="round"
    />
  </svg>
{/snippet}

{#snippet checkIcon()}
  <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="size-4">
    <path
      d="M6 14.15L10.0321 18L18 7"
      stroke="currentColor"
      stroke-width="1.5"
      stroke-linecap="round"
      stroke-linejoin="round"
    />
  </svg>
{/snippet}

Object values

Item values can be objects, not just primitives. The <Select.Value> children snippet then receives the full object, so you can format the display from any of its fields without an items lookup. Pass isItemEqualToValue so the select matches the selected object against the list by a stable field like id.

Assignee
<script lang="ts">
  import { Select } from '@shardsui/svelte/select'

  type Person = {
    id: string
    name: string
    role: string
  }

  const people: Person[] = [
    { id: 'i1', name: 'Paula Scher', role: 'Identity' },
    { id: 'i2', name: 'Massimo Vignelli', role: 'Typography' },
    { id: 'i3', name: 'Saul Bass', role: 'Motion' }
  ]

  let value = $state<Person>({ id: 'i1', name: 'Paula Scher', role: 'Identity' })
</script>

<div class="flex flex-col gap-1">
  <Select.Root
    bind:value
    itemToStringValue={(item) => item.id}
    isItemEqualToValue={(item, val) => item.id === val.id}
  >
    <Select.Label class="text-sm/5 font-semibold text-gray-900">Assignee</Select.Label>
    <Select.Trigger
      class="flex min-h-8 min-w-56 items-center justify-between gap-3 rounded-md border border-gray-200 bg-gray-50 py-1.5 pr-2 pl-3 text-sm text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-gray-950 data-popup-open:bg-gray-100"
    >
      <Select.Value>
        {#snippet children(current)}
          {@const assignee = current as Person | null}
          {#if assignee}
            <span class="flex flex-col items-start gap-0.5">
              <span class="text-sm/6">{assignee.name}</span>
              <span class="text-xs/4 text-gray-600">{assignee.role}</span>
            </span>
          {/if}
        {/snippet}
      </Select.Value>
      <Select.Icon class="flex items-center self-center">
        {@render chevronGrabberIcon()}
      </Select.Icon>
    </Select.Trigger>
    <Select.Portal>
      <Select.Positioner class="z-10 outline-hidden select-none" sideOffset={8}>
        <Select.Popup
          class="group min-w-(--anchor-width) origin-(--transform-origin) rounded-md bg-gray-50 bg-clip-padding text-gray-900 shadow-lg outline-1 outline-gray-200 transition-[transform,scale,opacity] duration-100 ease-out data-ending-style:scale-95 data-ending-style:opacity-0 data-starting-style:scale-95 data-starting-style:opacity-0"
        >
          <Select.List class="max-h-(--available-height) overflow-y-auto py-1">
            {#each people as person (person.id)}
              <Select.Item
                value={person}
                class="grid grid-cols-[1rem_1fr] items-start gap-2 py-1.5 pr-4 pl-2.5 text-sm/4 outline-hidden select-none data-highlighted:relative data-highlighted:z-0 data-highlighted:text-gray-50 data-highlighted:before:absolute data-highlighted:before:inset-x-1 data-highlighted:before:inset-y-0 data-highlighted:before:z-[-1] data-highlighted:before:rounded-sm data-highlighted:before:bg-gray-900"
              >
                <Select.ItemIndicator
                  class="relative top-[0.4em] col-start-1 flex items-center self-start"
                >
                  {@render checkIcon()}
                </Select.ItemIndicator>
                <div class="col-start-2 flex flex-col items-start gap-0.5">
                  <span class="text-sm/6">{person.name}</span>
                  <span class="text-xs/4 opacity-80">{person.role}</span>
                </div>
              </Select.Item>
            {/each}
          </Select.List>
        </Select.Popup>
      </Select.Positioner>
    </Select.Portal>
  </Select.Root>
</div>

{#snippet chevronGrabberIcon()}
  <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="size-4">
    <path
      d="M8 8.99981L11.1161 5.88369C11.6043 5.39554 12.3957 5.39554 12.8839 5.8837L16 8.99981"
      stroke="currentColor"
      stroke-width="1.5"
      stroke-linecap="round"
      stroke-linejoin="round"
    />
    <path
      d="M16 15L12.8839 18.1161C12.3957 18.6043 11.6043 18.6043 11.1161 18.1161L8 15"
      stroke="currentColor"
      stroke-width="1.5"
      stroke-linecap="round"
      stroke-linejoin="round"
    />
  </svg>
{/snippet}

{#snippet checkIcon()}
  <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="size-4">
    <path
      d="M6 14.15L10.0321 18L18 7"
      stroke="currentColor"
      stroke-width="1.5"
      stroke-linecap="round"
      stroke-linejoin="round"
    />
  </svg>
{/snippet}

Grouped

Break a long list into labeled sections with <Select.Group> and a <Select.GroupLabel> for each heading. Model the data as an array of group objects, each with its own items array plus a field such as label for the heading text, and render one <Select.Group> per entry.

Topic
<script lang="ts">
  import { Select } from '@shardsui/svelte/select'

  const topicGroups = [
    {
      label: 'Design',
      items: [
        { value: 'kerning', label: 'Kerning' },
        { value: 'type-scale', label: 'Type scale' }
      ]
    },
    {
      label: 'Frontend',
      items: [
        { value: 'html', label: 'HTML' },
        { value: 'css', label: 'CSS' }
      ]
    },
    {
      label: 'Accessibility',
      items: [
        { value: 'aria-label', label: 'aria-label' },
        { value: 'focus-state', label: 'Focus state' }
      ]
    }
  ]
</script>

<div class="flex flex-col gap-1">
  <Select.Root items={topicGroups}>
    <Select.Label class="text-sm/5 font-semibold text-gray-900">Topic</Select.Label>
    <Select.Trigger
      class="flex h-8 min-w-44 items-center justify-between gap-3 rounded-md border border-gray-200 bg-gray-50 pr-2 pl-3 text-sm font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-gray-950 data-popup-open:bg-gray-100"
    >
      <Select.Value class="data-placeholder:opacity-60" placeholder="Select topic" />
      <Select.Icon class="flex">
        {@render chevronGrabberIcon()}
      </Select.Icon>
    </Select.Trigger>
    <Select.Portal>
      <Select.Positioner class="z-10 outline-hidden select-none" sideOffset={8}>
        <Select.Popup
          class="group min-w-(--anchor-width) origin-(--transform-origin) rounded-md bg-gray-50 bg-clip-padding text-gray-900 shadow-lg outline-1 outline-gray-200 transition-[transform,scale,opacity] duration-100 ease-out data-ending-style:scale-95 data-ending-style:opacity-0 data-starting-style:scale-95 data-starting-style:opacity-0"
        >
          <Select.List class="max-h-(--available-height) scroll-pt-9 overflow-y-auto py-1">
            {#each topicGroups as group, index (group.label)}
              <Select.Group class="block pb-0.5">
                <Select.GroupLabel
                  class="sticky top-0 z-1 bg-gray-50 py-1.5 pr-4 pl-8.5 text-xs font-semibold tracking-wider text-gray-700 uppercase"
                >
                  {group.label}
                </Select.GroupLabel>
                {#each group.items as item (item.value)}
                  <Select.Item
                    value={item.value}
                    class="grid grid-cols-[1rem_1fr] items-center gap-2 py-1.5 pr-4 pl-2.5 text-sm/4 outline-hidden select-none data-highlighted:relative data-highlighted:z-0 data-highlighted:text-gray-50 data-highlighted:before:absolute data-highlighted:before:inset-x-1 data-highlighted:before:inset-y-0 data-highlighted:before:z-[-1] data-highlighted:before:rounded-sm data-highlighted:before:bg-gray-900"
                  >
                    <Select.ItemIndicator class="col-start-1">
                      {@render checkIcon()}
                    </Select.ItemIndicator>
                    <div class="col-start-2">{item.label}</div>
                  </Select.Item>
                {/each}
              </Select.Group>
              {#if index < topicGroups.length - 1}
                <Select.Separator class="mx-4 my-1 h-px bg-gray-200" />
              {/if}
            {/each}
          </Select.List>
        </Select.Popup>
      </Select.Positioner>
    </Select.Portal>
  </Select.Root>
</div>

{#snippet chevronGrabberIcon()}
  <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="size-4">
    <path
      d="M8 8.99981L11.1161 5.88369C11.6043 5.39554 12.3957 5.39554 12.8839 5.8837L16 8.99981"
      stroke="currentColor"
      stroke-width="1.5"
      stroke-linecap="round"
      stroke-linejoin="round"
    />
    <path
      d="M16 15L12.8839 18.1161C12.3957 18.6043 11.6043 18.6043 11.1161 18.1161L8 15"
      stroke="currentColor"
      stroke-width="1.5"
      stroke-linecap="round"
      stroke-linejoin="round"
    />
  </svg>
{/snippet}

{#snippet checkIcon()}
  <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="size-4">
    <path
      d="M6 14.15L10.0321 18L18 7"
      stroke="currentColor"
      stroke-width="1.5"
      stroke-linecap="round"
      stroke-linejoin="round"
    />
  </svg>
{/snippet}

Scrolling a long list

The scrolling container is <Select.List>, or <Select.Popup> when no list is rendered. Cap it with --available-height so it never outgrows the viewport:

.select-list {
  max-height: var(--available-height);
  overflow-y: auto;
}

<Select.ScrollUpArrow> and <Select.ScrollDownArrow> become visible when there is more to scroll in that direction, and scroll the list while the pointer rests on them. Both render with position: absolute, so give the popup a positioning context and place them against its edges:

<Select.Popup class="select-popup">
  <Select.ScrollUpArrow class="select-scroll-arrow-up" />
  <Select.List class="select-list">
    <!-- ... -->
  </Select.List>
  <Select.ScrollDownArrow class="select-scroll-arrow-down" />
</Select.Popup>

Mounting either arrow hides the list's scrollbar. Neither arrow becomes visible when the popup was opened by touch, where the scrollbar stays.

API reference

Root

Groups all parts of the select. Doesn't render its own HTML element, but renders a hidden <input> beside.

PropTypeDefault

Label

An accessible label that is automatically associated with the select trigger. Renders a <div> element.

PropTypeDefault
AttributeDescription
data-validPresent when the field is valid (when wrapped in Field.Root).
data-invalidPresent when the field is invalid (when wrapped in Field.Root).
data-touchedPresent when the field has been touched (when wrapped in Field.Root).
data-dirtyPresent when the field's value has changed (when wrapped in Field.Root).
data-filledPresent when the select has a value (when wrapped in Field.Root).
data-focusedPresent when the trigger is focused (when wrapped in Field.Root).

Trigger

A button that opens the select popup. Renders a <button> element. Typing while it is focused and closed selects the first item whose label matches, unless multiple is set.

PropTypeDefault
AttributeDescription
data-popup-openPresent when the select is open.
data-popup-sideIndicates which side the corresponding popup is positioned relative to its anchor.
data-pressedPresent while the popup is open, so the trigger can render as held down.
data-disabledPresent when disabled.
data-readonlyPresent when the select is readonly.
data-validPresent when the select is in a valid state (when wrapped in Field.Root).
data-invalidPresent when the select is in an invalid state (when wrapped in Field.Root).
data-touchedPresent when the select has been touched (when wrapped in Field.Root).
data-dirtyPresent when the select's value has changed (when wrapped in Field.Root).
data-filledPresent when the select has a value (when wrapped in Field.Root).
data-focusedPresent when the select trigger is focused (when wrapped in Field.Root).
data-placeholderPresent when the select doesn't have a value.

Value

A text label of the currently selected item. Renders a <span> element.

PropTypeDefault
AttributeDescription
data-placeholderPresent when no value is selected (placeholder shown).

Icon

An icon that indicates that the trigger button opens a select popup. Renders a <span> element, containing a ▼ glyph when given no children.

PropTypeDefault
AttributeDescription
data-popup-openPresent when the corresponding popup is open.

Backdrop

An overlay displayed beneath the popup. Renders a <div> element.

PropTypeDefault
AttributeDescription
data-openPresent when the select is open.
data-closedPresent when the select is closed.
data-starting-stylePresent when the backdrop is animating in.
data-ending-stylePresent when the backdrop is animating out.

Portal

A portal that moves the popup out to <body>, clear of ancestor clipping and stacking. Renders a <div> element. The portal renders while the popup is mounted, and stays rendered from the first time the trigger is focused, so the items exist for closed-trigger typeahead. keepMounted keeps it rendered beyond that.

PropTypeDefault

Positioner

Positions the select popup. Renders a <div> element.

PropTypeDefault
AttributeDescription
data-openPresent when the popup is open.
data-closedPresent when the popup is closed.
data-sideWhich side of the anchor the popup is on.
data-alignHow the popup is aligned relative to the side.
data-anchor-hiddenPresent when the anchor is hidden.
CSS VariableDescription
--available-widthAvailable width between the anchor and the viewport edge.
--available-heightAvailable height between the anchor and the viewport edge.
--anchor-widthWidth of the anchor element.
--anchor-heightHeight of the anchor element.
--transform-originTransform origin for scale animations.

Popup

A container for the select list. Renders a <div> element. It carries the listbox role itself when no <Select.List> is rendered.

PropTypeDefault
AttributeDescription
data-openPresent when the select is open.
data-closedPresent when the select is closed.
data-sideWhich side of the anchor the popup is on.
data-alignHow the popup is aligned relative to the side.
data-starting-stylePresent when the popup is animating in.
data-ending-stylePresent when the popup is animating out.

List

The listbox and the element that scrolls the items. Optional — without it the popup takes both roles. Renders a <div> element.

PropTypeDefault

Arrow

Displays an element positioned against the anchor. Renders a <div> element.

PropTypeDefault
AttributeDescription
data-openPresent when the popup is open.
data-closedPresent when the popup is closed.
data-sideWhich side of the anchor the popup is on.
data-alignHow the popup is aligned relative to the side.
data-uncenteredPresent when the arrow cannot be centered.

Item

An individual item in the select popup. Renders a <div> element.

PropTypeDefault
AttributeDescription
data-selectedPresent when this item is the selected value.
data-highlightedPresent when the item is highlighted.
data-disabledPresent when disabled.

ItemIndicator

Indicates whether the select item is selected. Renders a <span> element, containing a ✔️ glyph when given no children.

PropTypeDefault
AttributeDescription
data-selectedPresent when the item is selected.
data-starting-stylePresent when the indicator is animating in.
data-ending-stylePresent when the indicator is animating out.

Group

Groups related select items with the corresponding label. Renders a <div> element.

PropTypeDefault

GroupLabel

An accessible label that is automatically associated with its parent group. Renders a <div> element.

PropTypeDefault

ScrollUpArrow

An element that scrolls the list up while hovered. Never visible when the popup was opened by touch. Renders an absolutely positioned <div> element, containing a ▲ glyph when given no children.

PropTypeDefault
AttributeDescription
data-directionIndicates the direction of the scroll arrow ('up').
data-sideWhich side of the anchor the popup is on.
data-visiblePresent when the scroll arrow is visible.
data-starting-stylePresent when the scroll arrow is animating in.
data-ending-stylePresent when the scroll arrow is animating out.

ScrollDownArrow

An element that scrolls the list down while hovered. Never visible when the popup was opened by touch. Renders an absolutely positioned <div> element, containing a ▼ glyph when given no children.

PropTypeDefault
AttributeDescription
data-directionIndicates the direction of the scroll arrow ('down').
data-sideWhich side of the anchor the popup is on.
data-visiblePresent when the scroll arrow is visible.
data-starting-stylePresent when the scroll arrow is animating in.
data-ending-stylePresent when the scroll arrow is animating out.

Separator

A visual divider between groups of items. Rendered as role="presentation", because role="separator" is not valid inside a listbox. Renders a <div> element.

PropTypeDefault
AttributeDescription
data-orientationIndicates the orientation of the separator.