> For the complete documentation index, see [llms.txt](https://analog.groupthought.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://analog.groupthought.com/reference/alpine/roving-tab.md).

# x-roving-tab

Roving tabindex for groups of controls: one tab stop for the whole group, arrow keys to move between the items.

Roving tabindex for groups of controls: one tab stop for the whole group, arrow keys to move between the items.

| Directive / magic           | What it does                                                                                                                             |
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `x-roving-tab`              | The root. Holds the direction and wrap options                                                                                           |
| `x-roving-tab="expression"` | Supplies the focusable elements yourself; the expression returns a NodeList or array                                                     |
| `x-roving-tab.vertical`     | Up/down arrows move focus instead of left/right                                                                                          |
| `x-roving-tab.nowrap`       | Focus stops at the ends instead of wrapping around                                                                                       |
| `x-roving-tab:list`         | The element whose focusable descendants form the group; receives the key, click, and focus handlers                                      |
| `$rovingTab`                | Focus control from anywhere inside the root: `focus(el)`, `setTabbable(el)`, `focusNext()`, `focusPrev()`, `focusFirst()`, `focusLast()` |

The root and list usually use the same element. On init, the focused item gets `tabindex="0"`; if none is focused, the first item gets it. Every other item gets `tabindex="-1"`, so the group uses one Tab stop. Arrow keys, Home, and End move focus; clicking or focusing an item makes it the group's tab stop. Without an expression the plugin finds the focusable items with the [tabbable](https://github.com/focus-trap/tabbable) package, so items that become hidden drop out of the rotation on their own.

## Examples

### A toolbar of options

Use `x-roving-tab` with `x-roving-tab:list` on a `role="toolbar"` group so the whole set costs one Tab stop:

```html
<ul
  x-roving-tab
  x-roving-tab:list
  role="toolbar"
  aria-label="{{ option.name | escape_once }}"
>
  <li><button>S</button></li>
  <li><button>M</button></li>
  <li><button>L</button></li>
</ul>
```

The variant option buttons, sibling swatches, quick-add menu, and thumbnail picker all use this structure. A shopper can tab past the group in one press.

### Vertical lists

Use the `.vertical` modifier when the group reads top to bottom, so up/down arrows move focus:

```html
<ul x-roving-tab.vertical x-roving-tab:list>
  {% for filter_value in filter.values %}
    …
  {% endfor %}
</ul>
```

The collection filter lists use this. Left/right arrows do nothing in vertical mode, so they stay free for the browser.

### Stopping at the ends

Use the `.nowrap` modifier when wrapping from the last item to the first would disorient, such as a long scrolling list:

```html
<ul x-roving-tab.vertical.nowrap x-roving-tab:list>…</ul>
```

The listbox behind the custom select uses `.nowrap` so holding the down arrow parks on the last option instead of looping.

### Supplying the focusables

Use an expression on the root when the group isn't every focusable element in the list. For example, skip disabled options:

```html
<div x-roving-tab.vertical="__menu_getEnabledOptions" x-roving-tab:list>…</div>
```

The menu and listbox plugins pass their enabled options this way, so arrow keys step over disabled items instead of landing on them.

### Driving focus from code

Use `$rovingTab` to move focus as part of your own handlers:

```html
<button
  @keydown.down.prevent="open(); $rovingTab.focusFirst()"
  @keydown.up.prevent="open(); $rovingTab.focusLast()"
>
  Menu
</button>
```

The menu plugin opens on arrow-down and focuses the first option, or the last on arrow-up, per the ARIA menu-button pattern. `setTabbable(el)` moves the group's tab stop without focusing, for restoring state after a re-render.
