# Select

A dropdown selection control with typeahead search, grouped options, and custom placement.

```slint
import { Select, SelectItem } from "@glint/components/select.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 560px;
    height: 320px;
    background: Tokens.color-background;

    in-out property <int> plan-index: 1;

    VerticalLayout {
        alignment: center;
        spacing: 16px;
        padding: 24px;

        HorizontalLayout {
            alignment: center;

            Select {
                accessible-label: "Select Subscription Plan";
                placeholder: "Choose a plan…";
                selected-index <=> root.plan-index;
                items: [
                    { value: "free", label: "Free Plan ($0/mo)" },
                    { value: "pro", label: "Pro Plan ($20/mo)" },
                    { value: "team", label: "Team Plan ($50/mo)" },
                    { value: "enterprise", label: "Enterprise Plan", disabled: true },
                ];
            }
        }

        Text {
            text: "Selected plan index: " + root.plan-index;
            color: Tokens.color-muted-foreground;
            font-size: Tokens.typography-body-sm-size;
            horizontal-alignment: center;
        }
    }
}
```

## Usage

```slint
import { Select, SelectItem } from "@glint/components/select.slint";

export component AppWindow inherits Window {
    in-out property <int> fruit-index: -1;

    VerticalLayout {
        alignment: center;

        Select {
            placeholder: "Pick a fruit…";
            selected-index <=> root.fruit-index;
            items: [
                { value: "apple", label: "Apple" },
                { value: "banana", label: "Banana" },
                { value: "cherry", label: "Cherry" },
            ];
            changed(index) => {
                // handle pick
            }
        }
    }
}
```

`Select` displays a button trigger that opens a floating options list built on Glint’s `Panel` overlay primitive. Selecting an option sets `selected-index` and fires `changed(index)`.

Keyboard users can navigate with `↑`/`↓`, jump to list boundaries with `Home`/`End`, and type alphanumeric characters for instant prefix matching via type-to-select.

## Examples

### Placement strategies

`placement` controls where the dropdown popup opens relative to the trigger. `SelectPlacement.below` places the popup’s top edge below the trigger. `SelectPlacement.selected-item` lifts the popup so that the currently selected item aligns directly over the trigger, mimicking desktop native select controls.

```slint
import { Select, SelectPlacement } from "@glint/components/select.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 560px;
    height: 320px;
    background: Tokens.color-background;

    in-out property <int> item-idx: 2;

    VerticalLayout {
        alignment: center;
        spacing: 16px;
        padding: 24px;

        HorizontalLayout {
            alignment: center;

            Select {
                placement: SelectPlacement.selected-item;
                selected-index <=> root.item-idx;
                items: [
                    { value: "us", label: "United States (UTC-5)" },
                    { value: "eu", label: "European Union (UTC+1)" },
                    { value: "br", label: "Brazil (UTC-3)" },
                    { value: "jp", label: "Japan (UTC+9)" },
                ];
            }
        }

        Text {
            text: "Placement: SelectPlacement.selected-item";
            color: Tokens.color-muted-foreground;
            font-size: Tokens.typography-body-sm-size;
            horizontal-alignment: center;
        }
    }
}
```

### Grouped rows

Glint components that render lists (`Select`, `Combobox`, `Command`, `MenuPanel`) share a unified grouped-row model. Groups are not separate items in the data array; they ride on the first item of each group:

- `separator-before: true` draws a hairline divider above that row.
- `heading-before: "..."` displays a non-selectable category title above that row.

This architecture preserves exact 1-to-1 array indexing (`0`, `1`, `2`…) for actionable options and ensures screen readers do not count headers as selectable choices.

```slint
import { Select } from "@glint/components/select.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 560px;
    height: 320px;
    background: Tokens.color-background;

    VerticalLayout {
        alignment: center;
        padding: 24px;

        HorizontalLayout {
            alignment: center;

            Select {
                placeholder: "Select food…";
                items: [
                    { value: "apple", label: "Apple", heading-before: "Fruits" },
                    { value: "banana", label: "Banana" },
                    { value: "orange", label: "Orange" },
                    { value: "carrot", label: "Carrot", separator-before: true, heading-before: "Vegetables" },
                    { value: "broccoli", label: "Broccoli" },
                    { value: "spinach", label: "Spinach" },
                ];
            }
        }
    }
}
```

### Disabled options and validation error state

Set `disabled: true` on a `SelectItem` to prevent its selection while keeping its place in the list. Set `invalid: true` on the `Select` component to display a destructive error border and announce an invalid state.

```slint
import { Select } from "@glint/components/select.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 560px;
    height: 320px;
    background: Tokens.color-background;

    VerticalLayout {
        alignment: center;
        spacing: 16px;
        padding: 24px;

        HorizontalLayout {
            alignment: center;

            Select {
                invalid: true;
                placeholder: "Required selection…";
                items: [
                    { value: "active", label: "Active Project" },
                    { value: "archived", label: "Archived Project (Disabled)", disabled: true },
                    { value: "draft", label: "Draft Project" },
                ];
            }
        }

        Text {
            text: "Select invalid: true";
            color: Tokens.color-destructive;
            font-size: Tokens.typography-body-sm-size;
            horizontal-alignment: center;
        }
    }
}
```

### Type-to-select and search labels

Typeahead search matches keystrokes against option labels. When dealing with accents or diacritics, provide an unaccented `search-label` so readers can type standard ASCII keys to match localized names.

```slint
import { Select } from "@glint/components/select.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 560px;
    height: 320px;
    background: Tokens.color-background;

    VerticalLayout {
        alignment: center;
        padding: 24px;

        HorizontalLayout {
            alignment: center;

            Select {
                placeholder: "Choose a city…";
                items: [
                    { value: "sp", label: "São Paulo", search-label: "sao paulo" },
                    { value: "mch", label: "München", search-label: "munchen" },
                    { value: "tyo", label: "Tokyo", search-label: "tokyo" },
                    { value: "prs", label: "Paris", search-label: "paris" },
                ];
            }
        }
    }
}
```

## API Reference

### Properties

| Property            | Type                 | Default                 | Description                                                                                                                                                                                                                                                                              |
| ------------------- | -------------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `items`             | `in [SelectItem]`    | no default              | Options shown in the dropdown.                                                                                                                                                                                                                                                           |
| `selected-index`    | `in-out int`         | `-1`                    | Two-way; index of the chosen item, or -1 for no selection.                                                                                                                                                                                                                               |
| `placeholder`       | `in string`          | `@tr("Select…")`        | Shown when nothing is selected.                                                                                                                                                                                                                                                          |
| `disabled`          | `in bool`            | `false`                 | When true, the trigger dims and stops opening.                                                                                                                                                                                                                                           |
| `invalid`           | `in bool`            | `false`                 | When true, the trigger wears the destructive border and the control announces itself invalid. The message that explains the error belongs to the surrounding `Field` — bind this to that Field's `invalid`.                                                                              |
| `placement`         | `in SelectPlacement` | `SelectPlacement.below` | Where the dropdown opens — edge-aligned below the trigger, or lifted so the selected row lands over it.                                                                                                                                                                                  |
| `highlighted-index` | `in-out int`         | `0`                     | Internal — which item is highlighted by arrow keys inside the popup.                                                                                                                                                                                                                     |
| `is-open`           | `out bool`           | no default              | True while the dropdown popup is on screen; mirrors `popup.is-open`.                                                                                                                                                                                                                     |
| `focus-visible`     | `out bool`           | no default              | Whether this control holds the keyboard \*and\* got it from the keyboard — the `focus-visible` a hover surface opens on. Published because Slint reports focus only to the element holding it, so a `Tooltip` wrapping this control cannot read it off the scope inside (tooltip.slint). |
| `focus-held`        | `out bool`           | no default              | The same focus, still true while a popup has borrowed the window's — what a hover surface opened by this control has to gate on, since showing itself is what takes `focus-visible` away. See `Tooltip`.                                                                                 |

### Callbacks

| Callback       | Description                                                      |
| -------------- | ---------------------------------------------------------------- |
| `changed(int)` | Fired with the new `selected-index` when the user picks an item. |

### Functions

| Function                | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `focus-from-keyboard()` | Hand the control the keyboard the way a key press does, ring and all. A host that moves the focus onto a control because the user pressed something — `Questionnaire` stepping to the next question — cannot use `focus()`: Slint reports that as `programmatic`, which is how a host parking the keyboard looks, and the scope drops the ring for it. Published by every control that publishes `focus-visible`, for the same reason: the scope inside cannot be reached from outside the component. |

### Enums

| Enum              | Values                   |
| ----------------- | ------------------------ |
| `SelectPlacement` | `below`, `selected-item` |

## Accessibility

- **Combobox role.** The trigger element acts as an accessible `combobox` node carrying `accessible-expandable`, `accessible-expanded`, and the currently selected value.
- **List semantics.** The dropdown popup is exposed as a `list` where each option is a `list-item` carrying its index and selection state.
- **Group headings.** Headings and separator lines are non-interactive structural elements and never receive keyboard focus.
- **Disabled options.** Disabled options are reported as unavailable to screen readers and skipped by arrow keys and type-to-select matching.
- **Keyboard navigation.** `Enter`, `Space`, or `↓` on the trigger opens the dropdown. `↑`/`↓` navigate items, and alphanumeric typing highlights matching items. `Enter` commits the selection and `Escape` dismisses without changes.
