Select
A field that picks one item from a list, which opens from a button.
Selected: none.
<script>
import { $bind, $signal } from 'brumate/signals';
import Button from '@brumate/ui/Button.brm';
import Label from '@brumate/ui/Label.brm';
import ListBox from '@brumate/ui/ListBox.brm';
import ListBoxItem from '@brumate/ui/ListBoxItem.brm';
import Popover from '@brumate/ui/Popover.brm';
import Select from '@brumate/ui/Select.brm';
import SelectValue from '@brumate/ui/SelectValue.brm';
import Text from '@brumate/ui/Text.brm';
import type { Key } from '@brumate/ui/collection.ts';
let animal = $signal<Key | null>(null);
const animals = ['Aardvark', 'Cat', 'Dog', 'Kangaroo', 'Panda', 'Snake'];
</script>
<Select class="ui-select" name="animal" selectedKey=$bind(animal)>
<Label>Favorite animal</Label>
<Button class="ui-button ui-select-button"><SelectValue class="ui-select-value" /></Button>
<Text slot="description" class="ui-help">Pick the one you like best.</Text>
<Popover class="ui-popover ui-select-popover">
<ListBox class="ui-listbox">
#for (name of animals by name) {
<ListBoxItem id=(name.toLowerCase()) class="ui-option">{name}</ListBoxItem>
}
</ListBox>
</Popover>
</Select>
<p>Selected: {animal ?? 'none'}.</p>
<Select name="animal" selectedKey=$bind(animal)>
<Label>Favorite animal</Label>
<Button><SelectValue /></Button>
<Popover>
<ListBox>
<ListBoxItem id="cat">Cat</ListBoxItem>
<ListBoxItem id="dog">Dog</ListBoxItem>
</ListBox>
</Popover>
</Select>
- A
Selectholds aLabel, aButtonwith aSelectValue, aPopoverwith a ListBox, and can hold aTextwithslot="description"and aFieldError. selectedKeyis the key of the selected item, ornull.selectedKey=(key)starts its own selection, andselectedKey=$bind(signal)writes the caller's. See State.onSelectionChangegets each new key.isOpenalone starts the list open, andisOpen=$bind(signal)writes the caller's.onOpenChangegets each change.isDisabledkeeps the list closed, anddisabledKeyson the ListBox, orisDisabledon an item, keeps items from selection.- The Select shows
data-open,data-disabled,data-invalidanddata-required.
Opening and choosing
- A press of the Button opens the list. So do ArrowDown and ArrowUp on the Button. Focus goes to the selected item, or else to the first item for ArrowDown and the last for ArrowUp.
- In the list, the arrow keys, Home, End and typing move focus, as in a ListBox.
- A press, Enter or Space on an item selects it and closes the list. Focus goes back to the Button.
- Escape, Tab and a press outside close the list with no change.
- While the list is closed, ArrowLeft and ArrowRight on the Button select the item before or after the selected one. With nothing selected, both select the first item. Typing selects the item that starts with the text. The list stays closed.
- The Popover keeps the items in the page while it is closed, out of sight, so the Select can read them.
The value in the Button
SelectValue shows the text of the selected item, or placeholder of the Select, Select an item by default. It shows data-placeholder while nothing is selected.
The server does not know the text of a key that the Select starts with, so the HTML shows the key until the code loads. render, a #template, names the selection in the HTML too. It gets selectedKey, selectedText and isPlaceholder.
<Button><SelectValue render=sizeName /></Button>
#template sizeName(state: { selectedKey: Key | null }) {
{sizes.find((size) => size.id === state.selectedKey)?.name ?? 'None'}
}
A reset of the form gives back the key that the Select started with.
Forms
namesends the key of the selected item with its form, in an input out of sight. It goes with the form also before the code of the page loads.isRequiredasks the browser to check the field when its form is sent. The FieldError shows the message of the browser, and focus moves to the Button.- A server error for the
namein a Form shows in the FieldError until the selection changes. isInvalidmarks the field invalid.
Names
The Button is named by the value and the Label, as in React Aria, so a screen reader reads "Cat, Favorite animal". The ListBox is named by the Label. The Button gets aria-haspopup="listbox", aria-expanded and aria-controls, and aria-describedby names the description and the error.