---
type: doc
title: Select
description: Displays a list of options for the user to pick from, triggered by a button.
---

> Fulldev UI for Astro. The `@fulldev` registry in `components.json` points at `https://ui.full.dev/r/styles/{style}/{name}.json`; see [installation](/astro/docs/installation.md).

```astro title="src/components/examples/select/select-demo.astro"
---
import {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectLabel,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"

const items = [
  { label: "Select a fruit", value: null },
  { label: "Apple", value: "apple" },
  { label: "Banana", value: "banana" },
  { label: "Blueberry", value: "blueberry" },
  { label: "Grapes", value: "grapes" },
  { label: "Pineapple", value: "pineapple" },
]
---

<Select placeholder="Select a fruit" class="w-full max-w-48">
  <SelectTrigger class="w-full max-w-48">
    <SelectValue />
  </SelectTrigger>
  <SelectContent>
    <SelectGroup>
      <SelectLabel>Fruits</SelectLabel>
      {items.map((item) => (
        <SelectItem value={item.value ?? ""}>{item.label}</SelectItem>
      ))}
    </SelectGroup>
  </SelectContent>
</Select>
```

## Installation

```bash
npx shadcn@latest add @fulldev/select
```

## Usage

```ts
import {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"
```

```astro
<Select placeholder="Theme">
  <SelectTrigger class="w-[180px]">
    <SelectValue />
  </SelectTrigger>
  <SelectContent>
    <SelectGroup>
      <SelectItem value="light">Light</SelectItem>
      <SelectItem value="dark">Dark</SelectItem>
      <SelectItem value="system">System</SelectItem>
    </SelectGroup>
  </SelectContent>
</Select>
```

`SelectValue` shows the label of the selected item, or the `placeholder` of
`Select` when nothing is selected. Pass `name` to submit the value with a form,
and `required` for native validation. The `Select` root is an `inline-flex
w-fit` wrapper, so give it the trigger's width classes when the trigger uses
`w-full`.

## Composition

```text
Select
├── SelectTrigger
│   └── SelectValue
└── SelectContent
    ├── SelectGroup
    │   ├── SelectLabel
    │   ├── SelectItem
    │   └── SelectItem
    ├── SelectSeparator
    └── SelectGroup
        ├── SelectLabel
        ├── SelectItem
        └── SelectItem
```

## Align Item With Trigger

Use `position` on `Select` or `SelectContent`. With `"item-aligned"` (default),
the popup positions so the selected item appears over the trigger. With
`"popper"`, it opens below the trigger and takes `side`, `align`,
`sideOffset`, and `alignOffset`. Data Slot reads the position when it binds, so
the example rebinds the select when the switch changes.

```astro title="src/components/examples/select/select-align-item.astro"
---
import {
  Field,
  FieldContent,
  FieldDescription,
  FieldGroup,
  FieldLabel,
} from "@/components/ui/field"
import {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"
import { Switch } from "@/components/ui/switch"

const items = [
  { label: "Select a fruit", value: null },
  { label: "Apple", value: "apple" },
  { label: "Banana", value: "banana" },
  { label: "Blueberry", value: "blueberry" },
  { label: "Grapes", value: "grapes" },
  { label: "Pineapple", value: "pineapple" },
]
---

<FieldGroup class="w-full max-w-xs" data-select-align-item>
  <Field orientation="horizontal">
    <FieldContent>
      <FieldLabel for="align-item">Align Item</FieldLabel>
      <FieldDescription>
        Toggle to align the item with the trigger.
      </FieldDescription>
    </FieldContent>
    <Switch id="align-item" defaultChecked />
  </Field>
  <Field>
    <Select placeholder="Select a fruit" defaultValue="banana">
      <SelectTrigger>
        <SelectValue />
      </SelectTrigger>
      <SelectContent>
        <SelectGroup>
          {items.map((item) => (
            <SelectItem value={item.value ?? ""}>{item.label}</SelectItem>
          ))}
        </SelectGroup>
      </SelectContent>
    </Select>
  </Field>
</FieldGroup>

<script>
  import { createSelect } from "@data-slot/select"

  // Data Slot reads the position when it binds, so rebind on a toggle.
  const initialize = () => {
    for (const root of document.querySelectorAll("[data-select-align-item]")) {
      if (root.hasAttribute("data-bound")) continue
      root.setAttribute("data-bound", "")
      const select = root.querySelector('[data-slot="select"]')
      root
        .querySelector('[data-slot="switch"]')
        ?.addEventListener("switch:change", (event) => {
          if (!select) return
          const { checked } = (event as CustomEvent<{ checked: boolean }>)
            .detail
          const current = createSelect(select)
          const value = current.value ?? undefined
          current.destroy()
          createSelect(select, {
            defaultValue: value,
            position: checked ? "item-aligned" : "popper",
          })
        })
    }
  }

  initialize()
  document.addEventListener("astro:page-load", initialize)
</script>
```

## Groups

Use `SelectGroup`, `SelectLabel`, and `SelectSeparator` to organize items.

```astro title="src/components/examples/select/select-groups.astro"
---
import {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectLabel,
  SelectSeparator,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"

const fruits = [
  { label: "Apple", value: "apple" },
  { label: "Banana", value: "banana" },
  { label: "Blueberry", value: "blueberry" },
]
const vegetables = [
  { label: "Carrot", value: "carrot" },
  { label: "Broccoli", value: "broccoli" },
  { label: "Spinach", value: "spinach" },
]
---

<Select placeholder="Select a fruit" class="w-full max-w-48">
  <SelectTrigger class="w-full max-w-48">
    <SelectValue />
  </SelectTrigger>
  <SelectContent>
    <SelectGroup>
      <SelectLabel>Fruits</SelectLabel>
      {fruits.map((item) => (
        <SelectItem value={item.value}>{item.label}</SelectItem>
      ))}
    </SelectGroup>
    <SelectSeparator />
    <SelectGroup>
      <SelectLabel>Vegetables</SelectLabel>
      {vegetables.map((item) => (
        <SelectItem value={item.value}>{item.label}</SelectItem>
      ))}
    </SelectGroup>
  </SelectContent>
</Select>
```

## Scrollable

A select with many items that scrolls.

```astro title="src/components/examples/select/select-scrollable.astro"
---
import {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectLabel,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"

const northAmerica = [
  { label: "Eastern Standard Time", value: "est" },
  { label: "Central Standard Time", value: "cst" },
  { label: "Mountain Standard Time", value: "mst" },
  { label: "Pacific Standard Time", value: "pst" },
  { label: "Alaska Standard Time", value: "akst" },
  { label: "Hawaii Standard Time", value: "hst" },
]

const europeAfrica = [
  { label: "Greenwich Mean Time", value: "gmt" },
  { label: "Central European Time", value: "cet" },
  { label: "Eastern European Time", value: "eet" },
  { label: "Western European Summer Time", value: "west" },
  { label: "Central Africa Time", value: "cat" },
  { label: "East Africa Time", value: "eat" },
]

const asia = [
  { label: "Moscow Time", value: "msk" },
  { label: "India Standard Time", value: "ist" },
  { label: "China Standard Time", value: "cst_china" },
  { label: "Japan Standard Time", value: "jst" },
  { label: "Korea Standard Time", value: "kst" },
  { label: "Indonesia Central Standard Time", value: "ist_indonesia" },
]

const australiaPacific = [
  { label: "Australian Western Standard Time", value: "awst" },
  { label: "Australian Central Standard Time", value: "acst" },
  { label: "Australian Eastern Standard Time", value: "aest" },
  { label: "New Zealand Standard Time", value: "nzst" },
  { label: "Fiji Time", value: "fjt" },
]

const southAmerica = [
  { label: "Argentina Time", value: "art" },
  { label: "Bolivia Time", value: "bot" },
  { label: "Brasilia Time", value: "brt" },
  { label: "Chile Standard Time", value: "clt" },
]
---

<Select placeholder="Select a timezone" class="w-full max-w-64">
  <SelectTrigger class="w-full max-w-64">
    <SelectValue />
  </SelectTrigger>
  <SelectContent>
    <SelectGroup>
      <SelectLabel>North America</SelectLabel>
      {northAmerica.map((item) => (
        <SelectItem value={item.value}>{item.label}</SelectItem>
      ))}
    </SelectGroup>
    <SelectGroup>
      <SelectLabel>Europe & Africa</SelectLabel>
      {europeAfrica.map((item) => (
        <SelectItem value={item.value}>{item.label}</SelectItem>
      ))}
    </SelectGroup>
    <SelectGroup>
      <SelectLabel>Asia</SelectLabel>
      {asia.map((item) => (
        <SelectItem value={item.value}>{item.label}</SelectItem>
      ))}
    </SelectGroup>
    <SelectGroup>
      <SelectLabel>Australia & Pacific</SelectLabel>
      {australiaPacific.map((item) => (
        <SelectItem value={item.value}>{item.label}</SelectItem>
      ))}
    </SelectGroup>
    <SelectGroup>
      <SelectLabel>South America</SelectLabel>
      {southAmerica.map((item) => (
        <SelectItem value={item.value}>{item.label}</SelectItem>
      ))}
    </SelectGroup>
  </SelectContent>
</Select>
```

## Disabled

Pass `disabled` to `Select` or to a `SelectItem`.

```astro title="src/components/examples/select/select-disabled.astro"
---
import {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"

const items = [
  { label: "Select a fruit", value: null },
  { label: "Apple", value: "apple" },
  { label: "Banana", value: "banana" },
  { label: "Blueberry", value: "blueberry" },
  { label: "Grapes", value: "grapes", disabled: true },
  { label: "Pineapple", value: "pineapple" },
]
---

<Select placeholder="Select a fruit" disabled class="w-full max-w-48">
  <SelectTrigger class="w-full max-w-48">
    <SelectValue />
  </SelectTrigger>
  <SelectContent>
    <SelectGroup>
      {items.map((item) => (
        <SelectItem value={item.value ?? ""} disabled={item.disabled}>
          {item.label}
        </SelectItem>
      ))}
    </SelectGroup>
  </SelectContent>
</Select>
```

## Invalid

Add the `data-invalid` attribute to the `Field` component and the
`aria-invalid` attribute to the `SelectTrigger` component to show an error
state.

```astro title="src/components/examples/select/select-invalid.astro"
---
import { Field, FieldError, FieldLabel } from "@/components/ui/field"
import {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"

const items = [
  { label: "Select a fruit", value: null },
  { label: "Apple", value: "apple" },
  { label: "Banana", value: "banana" },
  { label: "Blueberry", value: "blueberry" },
]
---

<Field data-invalid class="w-full max-w-48">
  <FieldLabel>Fruit</FieldLabel>
  <Select placeholder="Select a fruit">
    <SelectTrigger aria-invalid="true">
      <SelectValue />
    </SelectTrigger>
    <SelectContent>
      <SelectGroup>
        {items.map((item) => (
          <SelectItem value={item.value ?? ""}>{item.label}</SelectItem>
        ))}
      </SelectGroup>
    </SelectContent>
  </Select>
  <FieldError>Please select a fruit.</FieldError>
</Field>
```

## RTL

```astro title="src/components/examples/select/select-rtl.astro"
---
import {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectLabel,
  SelectSeparator,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"

const translations = {
  en: {
    dir: "ltr",
    values: {
      selectFruit: "Select a fruit",
      fruits: "Fruits",
      apple: "Apple",
      banana: "Banana",
      blueberry: "Blueberry",
      grapes: "Grapes",
      pineapple: "Pineapple",
      vegetables: "Vegetables",
      carrot: "Carrot",
      broccoli: "Broccoli",
      spinach: "Spinach",
    },
  },
  ar: {
    dir: "rtl",
    values: {
      selectFruit: "اختر فاكهة",
      fruits: "الفواكه",
      apple: "تفاح",
      banana: "موز",
      blueberry: "توت أزرق",
      grapes: "عنب",
      pineapple: "أناناس",
      vegetables: "الخضروات",
      carrot: "جزر",
      broccoli: "بروكلي",
      spinach: "سبانخ",
    },
  },
  he: {
    dir: "rtl",
    values: {
      selectFruit: "בחר פרי",
      fruits: "פירות",
      apple: "תפוח",
      banana: "בננה",
      blueberry: "אוכמניה",
      grapes: "ענבים",
      pineapple: "אננס",
      vegetables: "ירקות",
      carrot: "גזר",
      broccoli: "ברוקולי",
      spinach: "תרד",
    },
  },
}

const language = "ar"
const { dir, values: t } = translations[language]

const fruits = [
  { label: t.apple, value: "apple" },
  { label: t.banana, value: "banana" },
  { label: t.blueberry, value: "blueberry" },
  { label: t.grapes, value: "grapes" },
  { label: t.pineapple, value: "pineapple" },
]

const vegetables = [
  { label: t.carrot, value: "carrot" },
  { label: t.broccoli, value: "broccoli" },
  { label: t.spinach, value: "spinach" },
]
---

<Select placeholder={t.selectFruit}>
  <SelectTrigger class="w-32" dir={dir}>
    <SelectValue />
  </SelectTrigger>
  <SelectContent dir={dir} data-lang={dir === "rtl" ? language : undefined}>
    <SelectGroup>
      <SelectLabel>{t.fruits}</SelectLabel>
      {fruits.map((item) => (
        <SelectItem value={item.value}>{item.label}</SelectItem>
      ))}
    </SelectGroup>
    <SelectSeparator />
    <SelectGroup>
      <SelectLabel>{t.vegetables}</SelectLabel>
      {vegetables.map((item) => (
        <SelectItem value={item.value}>{item.label}</SelectItem>
      ))}
    </SelectGroup>
  </SelectContent>
</Select>
```

## Content mounting

Data Slot's recommended `"lazy"` strategy is the default: closed content is
retained but detached from the document. Fulldev initializes nested Data Slot
components inside that retained content too.

Use `mountStrategy="eager"` when an integration needs to query closed content
through the document. Both strategies include content in server-rendered HTML;
neither adds a no-JavaScript fallback.

## API Reference

Source: [`src/components/ui/select`](https://github.com/fulldotdev/ui/tree/main/src/components/ui/select)

### Select

| Prop                   | Type                           | Default          |
| ---------------------- | ------------------------------ | ---------------- |
| `defaultValue`         | `string`                       |                  |
| `placeholder`          | `string`                       | `""`             |
| `disabled`             | `boolean`                      | `false`          |
| `required`             | `boolean`                      | `false`          |
| `name`                 | `string`                       |                  |
| `position`             | `"item-aligned" \| "popper"`   | `"item-aligned"` |
| `side`                 | `"top" \| "bottom"`            | `"bottom"`       |
| `align`                | `"start" \| "center" \| "end"` | `"start"`        |
| `sideOffset`           | `number`                       | `4`              |
| `alignOffset`          | `number`                       | `0`              |
| `avoidCollisions`      | `boolean`                      | `true`           |
| `collisionPadding`     | `number`                       | `8`              |
| `highlightItemOnHover` | `boolean`                      | `true`           |
| `lockScroll`           | `boolean`                      | `true`           |
| `mountStrategy`        | `"lazy" \| "eager"`            | `"lazy"`         |

`SelectContent` takes the placement props too and overrides the root.
`SelectItem` takes `value`, `disabled`, and `label` (the text shown in the
trigger when it differs from the item content). `SelectTrigger` takes `size`
(`"default" \| "sm"`).

The root emits `select:change` with `{ value }` and `select:open-change` with
`{ open }`. Dispatch `select:set` with `{ value?, open? }` to change it from
client code. See the
[Data Slot select docs](https://github.com/bejamas/data-slot/blob/main/packages/select/README.md)
for keyboard and controller details.

## Install what this page shows

```bash
npx shadcn@latest add @fulldev/select-examples
```
