Skip to content

Select

Picks one value, or several, from a fixed list the user cannot type into.

View source View as Markdown

Select is a listbox behind a trigger, for picking one value or several from a fixed set. Use it where you would reach for a native <select>; once the list is long enough to scroll, use Combobox.

The example switches the teleport off, so the popup renders in place and scrolls with the frame around it rather than with the page.

App.vue
<template>
  <Select.Root v-model="framework" :options="options">
    <Select.Trigger
      class="group border-surface rounded-component-lg focus:focus-ring data-[content-open=true]:focus-ring data-[disabled=true]:border-disabled-subtle flex h-12 w-64 cursor-pointer items-center justify-between gap-1.5 border-2 px-3.5 text-left outline-4 outline-transparent transition-all duration-100 ease-linear select-none data-[disabled=true]:cursor-not-allowed"
    >
      <span
        class="pointer-events-none flex max-h-full w-full flex-col-reverse justify-center gap-0 px-1 transition-all duration-100 ease-linear group-data-[filled=true]:gap-1"
      >
        <Select.Value
          class="text-primary-solid type-component-lg group-data-[disabled=true]:text-disabled-on-subtle flex h-0 w-full items-center overflow-hidden [line-height:normal]! opacity-0 transition-all duration-100 ease-linear group-data-[filled=true]:h-[0.9375rem] group-data-[filled=true]:opacity-100"
        />
        <span
          class="text-primary-muted type-component-lg group-data-[filled=true]:type-component-2xs group-data-[disabled=true]:text-disabled-muted flex w-full items-center [line-height:normal]! transition-all duration-100 ease-linear"
        >
          Framework
        </span>
      </span>

      <Select.Icon
        class="text-primary-muted group-data-[filled=true]:text-primary-solid group-data-[disabled=true]:text-disabled-muted flex shrink-0 items-center transition-all duration-100 ease-linear"
      >
        <svg class="size-4.5" viewBox="0 0 16 16" aria-hidden="true">
          <path
            d="m4 6.5 4 4 4-4"
            fill="none"
            stroke="currentColor"
            stroke-width="1.5"
            stroke-linecap="round"
            stroke-linejoin="round"
          />
        </svg>
      </Select.Icon>
    </Select.Trigger>

    <Select.Content
      class="rounded-component-2xl bg-primary-inverted shadow-component-high overflow-y-auto p-1.5 backdrop-blur-[8rem] [&_[data-scope=list]]:outline-none"
    >
      <Select.Item
        v-for="name in frameworks"
        :key="name"
        class="group/item rounded-component-md type-component-lg text-primary-on-subtle hover:bg-primary-subtle active:bg-primary-subtle-hover data-[highlighted=true]:bg-primary-subtle data-[disabled=true]:text-disabled-on-subtle flex h-12 w-full items-center justify-start gap-4 px-1 transition-colors duration-100 ease-linear select-none [--mirror-select-item-cursor:pointer]"
        :value="name"
      >
        <Select.ItemText
          class="inline-flex h-full min-w-0 flex-1 items-center overflow-hidden pr-1.5 pl-5.5"
        >
          <span class="truncate">{{ name }}</span>
        </Select.ItemText>

        <Select.ItemIndicator
          class="text-primary-muted group-hover/item:text-primary-solid group-data-[highlighted=true]/item:text-primary-solid group-data-[disabled=true]/item:text-disabled-muted flex shrink-0 items-center pr-4 leading-none transition-colors duration-100 ease-linear"
        >
          <svg class="size-4.5" viewBox="0 0 16 16" aria-hidden="true">
            <path
              d="m3.5 8.5 3 3 6-6.5"
              fill="none"
              stroke="currentColor"
              stroke-width="1.5"
              stroke-linecap="round"
              stroke-linejoin="round"
            />
          </svg>
        </Select.ItemIndicator>
      </Select.Item>
    </Select.Content>
  </Select.Root>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { Select } from '@maas/mirror/vue/Select'

const options = {
  modal: false,
  teleport: { disabled: true },
}

const frameworks = ['Nuxt', 'Astro', 'Remix', 'SvelteKit']
const framework = ref<string | null>('Nuxt')
</script>

Usage guidelines

  • Make sure the select has an accessible name, either through a Label inside a Field or an aria-label on the trigger.must
  • Select.Content teleports to the end of <body> unless you turn that off with options.teleport.disabled. Leave it on where the trigger sits inside anything with overflow: hidden.should
  • The content resolves everything but side and align once, during setup, so change the rest before the popup first renders or key the root to remount it.must
  • Once the list is long enough to scroll, reach for Combobox instead, so the user can narrow it by typing.should
  • Where the OS picker is the better control, on a phone or a tablet, set options.native and keep the same markup.may