brumate/ui

ListBox

A list of options that the user selects from, with the keyboard, a pointer or a screen reader.

Mammals
Aardvark
Cat
Dog
Kangaroo
Birds
Kiwi
Penguin
Robin

Selected: cat.

<script>
  import { $bind, $signal } from 'brumate/signals';
  import Header from '@brumate/ui/Header.brm';
  import ListBox from '@brumate/ui/ListBox.brm';
  import ListBoxItem from '@brumate/ui/ListBoxItem.brm';
  import ListBoxSection from '@brumate/ui/ListBoxSection.brm';
  import type { Key } from '@brumate/ui/collection.ts';

  let animals = $signal<Key[]>(['cat']);
  const groups = [
    { name: 'Mammals', items: ['Aardvark', 'Cat', 'Dog', 'Kangaroo'] },
    { name: 'Birds', items: ['Kiwi', 'Penguin', 'Robin'] },
  ];
</script>

<ListBox class="ui-listbox" aria-label="Animals" selectionMode="multiple" selectedKeys=$bind(animals)>
  #for (group of groups by group.name) {
    <ListBoxSection>
      <Header class="ui-listbox-header">{group.name}</Header>
      #for (animal of group.items by animal) {
        <ListBoxItem id=(animal.toLowerCase()) class="ui-option">{animal}</ListBoxItem>
      }
    </ListBoxSection>
  }
</ListBox>
<p>Selected: {animals.join(', ') || 'none'}.</p>

Selection

Actions

With selectionMode="none", a press or Enter acts on the item: onAction gets its key, and onAction on the item runs too. With a selection, Enter acts when the ListBox or the item has onAction, and selects when neither has.

Keyboard

Sections

ListBoxSection groups items, and a Header inside names the group. A section with no Header needs aria-label.

<ListBoxSection>
  <Header>Birds</Header>
  <ListBoxItem id="kiwi">Kiwi</ListBoxItem>
</ListBoxSection>

Items

How it reads the items

React Aria reads the items from the children of the list. A part of brumate cannot read its children, so the ListBox reads its items from the page: each item writes its key in data-key, and the order of the items is the order of their elements. This is why items can come from any markup, a #for or sections, with no items prop. A list that renders only the items in view has no page to read, so it takes items and reads their data.