Select
A select is a closed list: the trigger shows the current value, and the options live in a popover that opens on demand. The collection is passed as data — items plus item-text-value — so the component can do typeahead and announce a count without walking the DOM.
import { Select, SelectPopover, SelectTrigger, SelectValue } from "ropav";<Select
class="w-64"
:items='[{"id":"lisbon","name":"Lisbon"},{"id":"osaka","name":"Osaka"}]'
placeholder="Pick a city"
>
<Label>Destination</Label>
<SelectTrigger>
<SelectValue />
<SelectIndicator />
</SelectTrigger>
<SelectPopover>
<ListBox>
<ListBoxItem id="lisbon" text-value="Lisbon">
Lisbon
<ListBoxItemIndicator />
</ListBoxItem>
<ListBoxItem id="osaka" text-value="Osaka">
Osaka
<ListBoxItemIndicator />
</ListBoxItem>
</ListBox>
</SelectPopover>
</Select>The parts
SelectTrigger holds SelectValue and SelectIndicator. The popover holds a ListBox, which is a component in its own right — a select is a trigger with a list box inside it, not a different kind of list.
select-basic.vue
<script setup lang="ts">
import {
Label,
ListBox,
ListBoxItem,
ListBoxItemIndicator,
Select,
SelectIndicator,
SelectPopover,
SelectTrigger,
SelectValue,
} from "ropav";
const items = [
{ id: "lisbon", name: "Lisbon" },
{ id: "osaka", name: "Osaka" },
{ id: "quito", name: "Quito" },
{ id: "tallinn", name: "Tallinn" },
];
const byName = (item: { name: string }) => item.name;
</script>
<template>
<Select class="field" :item-text-value="byName" :items="items" placeholder="Pick a city">
<Label>Destination</Label>
<SelectTrigger>
<SelectValue />
<SelectIndicator />
</SelectTrigger>
<SelectPopover>
<ListBox>
<ListBoxItem v-for="item in items" :id="item.id" :key="item.id" :text-value="item.name">
{{ item.name }}
<ListBoxItemIndicator />
</ListBoxItem>
</ListBox>
</SelectPopover>
</Select>
</template>
<style scoped>
.field {
width: calc(var(--rp-spacing) * 64);
}
</style>SelectValue renders the selected item's text, or the placeholder when nothing is chosen. Its slot gives you the selected items themselves if you want to render something richer than a string.
Clearing
Add SelectClearButton inside the trigger to let a choice be undone. It renders as a span, because the trigger it sits in is already a button, so the trigger answers Backspace and Delete on its behalf; the root emits clear either way.
Placement
SelectPopover takes placement, offset and shouldFlip. The default flips the popover to the other side when there is not room, so a select near the bottom of the window opens upwards without being told to.
Accessibility
- A hidden native
<select>carries the value into a form, so submission and autofill work the way the browser expects. - Typing while the list is open jumps to the matching option, which is why
item-text-valueis needed: it is the text being matched, not the label being rendered. - The popover is dismissed by Esc, by a click outside, and by choosing an option — and focus goes back to the trigger each time.
API
Select
| Prop | Type | Default | Description |
|---|---|---|---|
allowsEmptyCollection | boolean | — | Whether the popover may open with no options in it. |
ariaDescribedby | string | — | |
ariaLabel | string | — | |
ariaLabelledby | string | — | |
autoComplete | string | — | What the browser may offer to autofill. |
defaultOpen | boolean | — | Whether the popover starts open. |
defaultValue | SelectedValue | — | The key(s) the select starts on. |
disabledBehavior | "all" | "selection" | "all" | Whether a disabled key is also unfocusable. |
disabledKeys | Iterable<CollectionKey> | — | Keys that cannot be chosen. |
form | string | — | Id of the <form> the hidden control belongs to. |
fullWidth | boolean | — | |
isDisabled | boolean | — | |
isInvalid | boolean | — | Controlled validity. Present at all — true or false — shadows validate. |
isOpen | boolean | — | Whether the popover is open. Controlled. |
isRequired | boolean | — | |
itemDisabled | ((item: T) => boolean) | — | Whether an item cannot be chosen. Merges with disabledKeys. |
itemKey | ((item: T, index: number) => CollectionKey) | — | An item's key. Defaults to its own id, then key, then its index. |
items* | readonly T[] | — | The options, as data. **Required.** The React build reads its options out of a hidden render pass of its children. Rendering is what creates DOM in vapor, so there is no such pass — and the options only exist in the DOM while the popover is open. Everything a closed select has to answer for reads this instead: the value in the trigger, the options of the hidden native control, typing to choose, and the arrow keys stepping through choices. Required rather than optional so leaving it out is a type error instead of a select that quietly shows a placeholder over a value it holds. |
itemTextValue | ((item: T) => string | undefined) | — | Text shown for an item in the trigger, and matched on when typing. Defaults to its text. |
name | string | — | Name of the hidden native control, for form submission. |
placeholder | string | — | Text shown in the trigger when nothing is chosen. Defaults to a localized string. |
selectionMode | "single" | "multiple" | "single" | Whether one or several options can be chosen. |
shouldCloseOnSelect | boolean | true when single | Whether choosing an option closes the popover. |
size | "xs" | "sm" | "md" | "lg" | "md" | How tall the trigger stands, matching a button of the same size. Handed to the listbox in the popover, unless it declares one of its own. |
validate | ValidationFunction<SelectedValue> | — | |
validationBehavior | "aria" | "native" | "native" | |
value | SelectedValue | — | The chosen key, or keys when multiple. Controlled. |
variant | "primary" | "secondary" | — |
The default slot receives:
| Slot prop | Type |
|---|---|
isDisabled | boolean |
isFocused | boolean |
isInvalid | boolean |
isOpen | boolean |
isRequired | boolean |
| Event | Payload |
|---|---|
change | SelectedValue |
clear | — |
focusChange | boolean |
openChange | boolean |
update:isOpen | boolean |
update:value | SelectedValue |
SelectClearButton
Slots: default.
SelectIndicator
Slots: default.
SelectPopover
| Prop | Type | Default | Description |
|---|---|---|---|
arrowBoundaryOffset | number | — | |
containerPadding | number | — | |
crossOffset | number | — | Shift along the trigger's edge, in pixels. |
isKeyboardDismissDisabled | boolean | — | |
offset | number | 8 | Distance from the trigger, in pixels. |
placement | "bottom" | "bottom end" | "bottom left" | "bottom right" | "bottom start" | "end" | "end bottom" | "end top" | "left" | "left bottom" | "left top" | "right" | "right bottom" | "right top" | "start" | "start bottom" | "start top" | "top" | "top end" | "top left" | "top right" | "top start" | "bottom" | |
shouldFlip | boolean | — | Whether the popover may flip to the other side when it does not fit. |
Slots: default.
SelectTrigger
Slots: default.
SelectValue
Slots: default.