Beta, expect API change, and bugs. Hit one? Tell us.

Skip to content

Select

A single-select listbox.

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

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

<template>
  <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">
          <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>
        </Select.Icon>
      </Select.Trigger>
      <Select.Portal>
        <Select.Positioner class="z-10 outline-hidden select-none" :side-offset="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">
              <Select.Item
                v-for="level in levels"
                :key="level.value"
                :value="level.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">
                  <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>
                </Select.ItemIndicator>
                <div class="col-start-2">{{ level.label }}</div>
              </Select.Item>
            </Select.List>
          </Select.Popup>
        </Select.Positioner>
      </Select.Portal>
    </Select.Root>
  </div>
</template>

Anatomy

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

<template>
  <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>
</template>

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 template ref patterns.

Examples

Formatting the value

With no items, <Select.Value> stringifies the selected value. Give <Select.Root> an items prop and it renders the matching label instead:

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

<template>
  <Select.Root :items="items">
    <Select.Value />
  </Select.Root>
</template>

For richer output, take the default slot of <Select.Value> and format the value yourself:

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

<template>
  <Select.Value v-slot="{ value }">
    <span :style="{ fontFamily: value as string }">
      {{ items[value as keyof typeof items] }}
    </span>
  </Select.Value>
</template>

Giving each item an object value skips the lookup.

Labeling a select

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

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

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 setup lang="ts">
const items = [
  { value: 'system', label: 'System default' },
  { value: 'light', label: 'Light' },
  { value: 'dark', label: 'Dark' }
]
</script>

<template>
  <Select.Root :items="items">
    <Select.Value placeholder="Select theme" />
  </Select.Root>
</template>

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

<template>
  <Select.Root :items="items">
    <Select.Value />
  </Select.Root>
</template>

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 through the <Select.Value> default slot.

Topics
<script setup lang="ts">
import { Select } from '@shardsui/vue/select'
import { computed, shallowRef } from 'vue'

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

const value = shallowRef(['kerning', 'contrast'])

const label = computed(() => topics.find((topic) => topic.value === value.value[0])?.label)
</script>

<template>
  <div class="flex flex-col gap-1">
    <Select.Root multiple v-model:value="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">
          <template v-if="value.length === 0">Select topics</template>
          <template v-else>
            {{ label }}{{ value.length > 1 ? ` (+${value.length - 1} more)` : '' }}
          </template>
        </Select.Value>
        <Select.Icon class="flex">
          <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>
        </Select.Icon>
      </Select.Trigger>
      <Select.Portal>
        <Select.Positioner class="z-10 outline-hidden select-none" :side-offset="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">
              <Select.Item
                v-for="topic in topics"
                :key="topic.value"
                :value="topic.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">
                  <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>
                </Select.ItemIndicator>
                <div class="col-start-2">{{ topic.label }}</div>
              </Select.Item>
            </Select.List>
          </Select.Popup>
        </Select.Positioner>
      </Select.Portal>
    </Select.Root>
  </div>
</template>

Object values

Item values can be objects, not just primitives. The <Select.Value> default slot 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 setup lang="ts">
import { Select } from '@shardsui/vue/select'
import { shallowRef } from 'vue'

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' }
]

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

<template>
  <div class="flex flex-col gap-1">
    <Select.Root
      v-model:value="value"
      :item-to-string-value="(item: Person) => item.id"
      :is-item-equal-to-value="(item: Person, val: Person) => 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 v-slot="{ value: current }">
          <span v-if="current" class="flex flex-col items-start gap-0.5">
            <span class="text-sm/6">{{ (current as Person).name }}</span>
            <span class="text-xs/4 text-gray-600">{{ (current as Person).role }}</span>
          </span>
        </Select.Value>
        <Select.Icon class="flex items-center self-center">
          <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>
        </Select.Icon>
      </Select.Trigger>
      <Select.Portal>
        <Select.Positioner class="z-10 outline-hidden select-none" :side-offset="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">
              <Select.Item
                v-for="person in people"
                :key="person.id"
                :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"
                >
                  <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>
                </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>
            </Select.List>
          </Select.Popup>
        </Select.Positioner>
      </Select.Portal>
    </Select.Root>
  </div>
</template>

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 setup lang="ts">
import { Select } from '@shardsui/vue/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>

<template>
  <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">
          <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>
        </Select.Icon>
      </Select.Trigger>
      <Select.Portal>
        <Select.Positioner class="z-10 outline-hidden select-none" :side-offset="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">
              <template v-for="(group, index) in topicGroups" :key="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>
                  <Select.Item
                    v-for="item in group.items"
                    :key="item.value"
                    :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">
                      <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>
                    </Select.ItemIndicator>
                    <div class="col-start-2">{{ item.label }}</div>
                  </Select.Item>
                </Select.Group>
                <Select.Separator
                  v-if="index < topicGroups.length - 1"
                  class="mx-4 my-1 h-px bg-gray-200"
                />
              </template>
            </Select.List>
          </Select.Popup>
        </Select.Positioner>
      </Select.Portal>
    </Select.Root>
  </div>
</template>

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:

<template>
  <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>
</template>

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.