brumate/ui

Select

A field that picks one item from a list, which opens from a button.

Pick the one you like best.

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>

Opening and choosing

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

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.