Skip to content
Open UI

Focusgroup (Explainer)

Table of Contents

Show / Hide Table of Contents

Introduction

Authors routinely hand-code “roving tabindex” logic for composite widgets like toolbars, tablists, menus, listboxes and grids. In practice, this means providing a single tab stop to enter the control, then using directional navigation (arrow keys / D-pad) to move focus between items.

Authors may use the proposed focusgroup HTML attribute to declare that a subtree of focusable elements will get:

  1. Focus navigation (not selection) using directional navigation (arrow key, D-pads, etc.).
  2. A guaranteed tab stop (when at least one focusable element is present) (see Guaranteed tab stop).
  3. Automatic return to the last focused focusable element (unless no-memory is set).
  4. Optional limited-axis arrow key navigation and optional wrap-around semantics.

By standardizing focusgroup, authors can leverage these behaviors in control patterns to provide users with keyboard consistency, default accessibility, and interoperability over existing solutions.

While this document emphasizes the usage of the keyboard arrow keys for accessibility navigation, we acknowledge that there are other input modalities that work (or can be adapted to work) equally well for focusgroup navigation behavior (e.g., game controllers,D-pads, remote controls, gesture recognizers, touch-based assistive technologies (AT), etc.).

Benefits over ad-hoc scripts (FocusZone, Tabster, bespoke roving tabindex): less boilerplate, standardized axis + wrap behavior (including RTL / vertical), reduced misapplication, and a consistent, testable baseline for AT and UA interoperability.

Keyboard Navigation Modes

Two complementary navigation paradigms are often used on the web today:

1. Sequential focus navigation

(Tab / Shift+Tab): The browser-managed order of tabbable elements (tabindex / native semantics). Used to enter or leave a composite. This navigation respects the CSS reading-flow property when present, following the visual reading order rather than DOM order; if reading-flow is not set it follows DOM source order.

2. Directional navigation

(Arrow keys, D-pad, some AT commands): Moves focus among a logically related set of items inside a composite without leaving it, the “roving tabindex” pattern when hand-authored. Directional movement aligns with the visual order established by reading-flow so that an arrow key press moves focus in the expected visual direction.

Before / After at a glance:

Before (manual roving tabindex JS):

<div role="toolbar" aria-label="Text formatting" id="fmtToolbar">
  <button type="button">Bold</button>
  <button type="button">Italic</button>
  <button type="button">Underline</button>
</div>
<script>
  // Without focusgroup an author script must implement:
  //  - Focus management (roving tabindex: keep exactly one item tabbable, skip disabled/hidden).
  //  - Keyboard navigation (Arrow keys, Home/End, optional wrap, writing mode/RTL handling).
  //  - Memory (restore last focused item on re-entry).
  // Plus any domain logic (selection state, toggles, menus, tooltips, custom commands, etc.).
  // focusgroup makes the first three bullets declarative; authors keep only selection & feature logic.
</script>

After (single declarative attribute):

<div focusgroup="toolbar wrap" aria-label="Text formatting">
  <button type="button">Bold</button>
  <button type="button">Italic</button>
  <button type="button">Underline</button>
</div>
<script>
  // Now with focusgroup, the author only needs to implement selection & feature logic.
</script>

What changed:

  • focusgroup="toolbar wrap" supplies the role (first token) + wrap behavior.
  • Roving tabindex + arrow key handling + memory are native (no JS for core movement).
  • Selection / pressed state (e.g., toggling Bold) and any advanced commands remain author logic.

Focusgroup Tokens

Simple usage (space-separated tokens):

<div focusgroup="<behavior> [inline|block] [wrap] [no-memory]">
  <button>One</button>
  <button>Two</button>
  <button>Three</button>
</div>

Opting an element and its subtree out of an ancestor focusgroup, see: Opting-out:

<div focusgroup="none"></div>

Specifying a specific item as the entry point for focus when entering a focusgroup:

<div focusgroup="<behavior> [inline|block] [wrap] [no-memory]">
  <button>One</button>
  <button>Two</button>
  <button>Three</button>
</div>
TokenRequired?What it does
<behavior>Yes (unless using none)Token describing the intended behavior. See: Supported Behaviors.
inline / blockOptionalRestrict arrow navigation to that logical axis. Omit to allow both axes.
wrapOptionalLoops end→start and start→end.
no-memoryOptionalDisable restoring last-focused item on re-entry (memory is on by default).
noneStandaloneOpt-out: exclude element and its subtree from ancestor focusgroup. Cannot be combined with any other token.

Major Differences from the original explainer

Original focusgroup explainer.

CSS support is now a future consideration

Applying focusgroup (and related behaviors) via CSS is out of scope for this explainer and deferred for a possible, backwards-compatible future addition.

Grid support is now a future consideration

Applying focusgroup to grid-like structures (2-D navigation) is out of scope for this explainer and deferred for a possible, backwards-compatible future addition.

Focusgroup is now scoped to specific scenarios

Earlier drafts explored applying focusgroup to any container. Broad, role-agnostic usage risks normalizing arrow navigation in semantically neutral wrappers (“div soup”), masking missing semantics and creating unexpected keyboard loops. The proposal now:

  • Limits activation to a concise, enumerated set of behavior tokens associated with recognized widget patterns.
  • Allows (when omitted) safe child role inference for common item types to reduce boilerplate and steer toward APG-aligned structures.
  • Keeps ARIA roles semantic-only: the focusgroup attribute supplies behavior intent; an explicit role attribute remains optional unless the author needs a different role than the first token.

Benefits of this scoped, behavior-first approach:

  • Guardrails: Prevents accidental application to arbitrary layout groupings without a recognized composite role.
  • Ergonomics: One attribute encodes both composite intent and navigation modifiers (wrap, inline, etc.).
  • Extensibility: Additional composite roles (or a role-agnostic mode) can be considered later based on demonstrated accessible patterns; narrowing later would be impractical.

Open aspects still under discussion appear in the Open Questions (e.g., set of supported behaviors, child role inference).

Supported Behaviors

The first token in a focusgroup attribute is a behavior token. A behavior token declares an interaction pattern (e.g., toolbar behavior). User agents MUST expose a corresponding minimum ARIA role for accessibility if the author does not supply an explicit, compatible role or if the author doesn’t use a native element with a recognized role. This separates interaction (behavior) from semantics (role mapping) while keeping authoring terse. Minimum role application (container & children): User agents apply the minimum container role for a behavior only when (a) the author has not provided an explicit role AND (b) the element would otherwise expose a generic role (e.g., a plain <div>/<span>). If the host already has non-generic native semantics (e.g., <ul>, <nav>, <table>) or an explicit role, the mapping is skipped; navigation behavior still applies. Child role inference likewise only occurs when: (1) the container role was supplied via the behavior’s minimum role mapping (not explicit/native), (2) the child lacks an explicit role, and (3) the child itself is otherwise generic or less specific than the inferred role. Native interactive elements (buttons, links, inputs, etc.) or anything with an explicit/non-generic role are never overwritten.

Current behavior tokens (APG alignment & minimum roles). All listed (except the future grid token) produce the same linear navigation semantics—only semantic expectations & child inference differ:

BehaviorAPG PatternMinimum container role (when applied)Minimum child role(s) (when applied)APG Pattern Link
toolbarToolbartoolbar(none)APG Toolbar
tablistTabstablisttabAPG Tabs
radiogroupRadio GroupradiogroupradioAPG Radio Group
listboxListboxlistboxoptionAPG Listbox
menuMenumenumenuitemAPG Menu / Menubar
menubarMenubarmenubarmenuitemAPG Menu / Menubar
grid (future)Grid (2-D)grid(TBD)APG Grid

Behavior → role mapping & precedence

  1. No explicit container role: UA maps behavior token to its minimum role (subject to minimum-role conditions described above).
  2. Explicit container role identical to minimum role: keep as-is.
  3. Explicit compatible composite role: keep explicit role; behavior navigation still activates.
  4. Explicit incompatible role (e.g., button): UA MAY activate a developer warning.
  5. Child role inference only runs when: (a) container role came from behavior mapping, (b) descendant is managed (not opted-out), (c) behavior defines an inferred child role, (d) descendant lacks explicit role, (e) inference would not replace richer native semantics. This endures an inferred, minimum role will never override an explicit role.
  6. Inference never upgrades variant types (e.g., menuitemcheckbox vs menuitemradio)—authors must specify those explicitly.

This approach separates behavioral intent (token) from accessibility semantics (minimum role mapping) and addresses concerns about reusing ARIA role strings directly as activation tokens.

Why not just add new native elements to cover these patterns?

There are already proposals to add native versions of several of the patterns focusgroup would help with, rather than introduce this new attribute, why shouldn’t we instead focus on building these patterns directly into HTML?

Reasons to pursue focusgroup in parallel:

  1. Author choice (custom elements / design systems): Many teams intentionally wrap primitives in web components or framework components and may not adopt new native container elements even if available. focusgroup lets them keep existing markup patterns while still standardizing navigation and reducing JS.
  2. Incremental flexibility: Adding a new eligible behavior token (and optional child inference) is a far smaller spec and implementation change than introducing a new HTML element with parsing, styling, accessibility mapping, and legacy considerations.
  3. Low friction upgrade path: Existing ARIA patterns (toolbar of buttons, listbox of options) become declarative with a single attribute rather than refactoring to new tags.
  4. Progressive expansion: We can start with a minimal, consensus set (toolbar, tablist, radiogroup, listbox, menu/menubar) and add more patterns as needed.
  5. Minimal surface risk: An attribute opt-in is easier to ship, iterate, or adjust (including potential deprecation of an unused token) than an element baked into the content model.
  6. Immediate boilerplate win: Solves today’s repetitive roving tabindex logic without waiting for multiple element proposals to mature and achieve cross-browser implementation.

focusgroup doesn’t replace, but complements native elements. It standardizes a widely re-implemented behavioral layer and leaves room for richer, purpose-built elements to integrate or rely on it later.

Quickstart

The following examples start with the most common composite: a formatting toolbar.

In this example, the author is using a tab control pattern where the tab activation behavior is decoupled from selection (“manual tab activation”):

<div focusgroup="tablist inline wrap no-memory" aria-label="Common Operating Systems">
  <button id="tab-1" aria-selected="false" aria-controls="tabpanel-1">macOS</button>
  <button id="tab-2" aria-selected="true" aria-controls="tabpanel-2" focusgroup-entry-priority>Windows</button>
  <button id="tab-3" aria-selected="false" aria-controls="tabpanel-3">Linux</button>
</div>
<div id="tabpanel-1" role="tabpanel" tabindex="0" aria-labelledby="tab-1" hidden></div>
<div id="tabpanel-2" role="tabpanel" tabindex="0" aria-labelledby="tab-2"></div>
<div id="tabpanel-3" role="tabpanel" tabindex="0" aria-labelledby="tab-3" hidden></div>

What to notice:

  • The focusgroup-entry-priority attribute on the selected tab determines which tab receives focus when entering the focusgroup. The no-memory value prevents the focusgroup from remembering the last focused tab so that focus will always go to the tab with focusgroup-entry-priority on re-entry regardless of which element was focused last.
  • If focus is moved via left arrow key to tab-1, then pressing the tab key moves focus to tabpanel-2 which is next in sequential focus navigation order (because the other role=tabpanels are hidden).
  • focus will wrap from one end of the tablist to the other because of focusgroup=wrap attribute value.
  • the up and down arrow keys will not move the focus because of focusgroup="inline" which restricts the axis of movement to keyboard directional arrow keys in the role="tablist"’s inline direction (assuming the <div>’s writing-mode is horizontal-tb).
  • The author code required to manage the selection of a tab is omitted for brevity. Such code on tab selection change would update aria-selected values, the hidden state of the controlled role="tabpanel" and move the focusgroup-entry-priority attribute to the newly selected tab.

In a third example, the author is creating a navigation menubar. Both menuitems in the menubar (“About” and “Admissions”) have popover menus. The “Admissions” menu has an additional submenu under “Tuition”.

<ul focusgroup="menubar inline wrap" aria-label="Mythical University">
  <li role="none">
    <a role="menuitem" popovertarget="aboutpop" href="">About</a>
    <ul focusgroup="menu block wrap" autofocus id="aboutpop" aria-label="About" popover>
      <li role="none"><a role="menuitem" href="">Overview</a></li>
      <li role="none"><a role="menuitem" href="">Administration</a></li>
    </ul>
  </li>
  <li role="none">
    <a role="menuitem" popovertarget="admitpop" href="">Admissions</a>
    <ul focusgroup="menu block wrap" autofocus id="admitpop" aria-label="Admissions" popover>
      <li role="none"><a role="menuitem" href="">Apply</a></li>
      <li role="none">
        <a role="menuitem" popovertarget="tuitpop" href="">Tuition</a>
        <ul focusgroup="menu block wrap" autofocus id="tuitpop" aria-label="Tuition" popover>
          <li role="none"><a role="menuitem" href="">Undergraduate</a></li>
          <li role="none"><a role="menuitem" href="">Graduate</a></li>
        </ul>
      </li>
      <li role="none"><a role="menuitem" href="">Visit</a></li>
    </ul>
  </li>
</ul>

What to notice:

  • focusgroup declarations can be nested inside of other focusgroups. When a nested focusgroup is declared on an element, it creates a new focusgroup and opts-out of its ancestor focusgroup.
  • menuitems in role=menubar are limited to inline-direction arrow keys (e.g., left and right), while menuitems in role=menu are limited to block-direction arrow keys (e.g., up and down). This allows the orthogonal arrow keys (e.g., up and down on the menubar, left and right on the menus) to be used for activation purposes (extra code that is not shown in the example).
  • Placement of focus on the menus (the nested focusgroups) from the menubar is not a feature of focusgroup (nested focusgroups are completely independent of their ancestor focusgroup). In this case, the focus placement is handled by built-in popover and autofocus attribute behaviors.
  • the “memory” of the nested focusgroups is reset when the content is hidden/shown—this allows the autofocus attribute to pick the first focusable element each time a menu is shown—the desired behavior in this case.

Opt-out subtree (explicit example):

<div focusgroup="toolbar inline wrap">
  <button>A</button>
  <span focusgroup="none">
    <button>(Not arrow reachable)</button>
  </span>
  <button>B</button>
</div>

Items inside the focusgroup="none" span are skipped by arrow navigation.

What to notice:

  • The opt-out subtree removes its focusable descendants from arrow traversal (ancestor toolbar is eligible) but they remain reachable via Tab.
  • Arrow navigation treats the subtree as a single gap—focus jumps from the item before to the item after.
  • Useful for excluding infrequent/disruptive controls (e.g., help buttons) from high-frequency arrow flows.

Empirical misuse in uncontrolled contexts; correct usage clusters around APG-backed widget roles. Scoping lowers ambiguity and accelerates interoperable implementation.

Interactions with explicit role vs behavior token mapping; also see open question around child role inference

<nav id="one" focusgroup="menu inline wrap" aria-label="Formatting">
  <button focusgroup-entry-priority>Bold</button>
  <button role="menuitemcheckbox" aria-checked="true">Italics</button>
  <button>Underline</button>
</nav>
<div id="two" role="toolbar" focusgroup="radiogroup wrap">
  <button type="button" focusgroup-entry-priority>Bold</button>
  <button type="button">Italic</button>
  <button type="button">Underline</button>
</div>

What to notice:

  • In #one, descendants without explicit roles (Bold, Underline) are inferred as menuitem; the explicit menuitemcheckbox (Italics) is unchanged.
  • Inference skips ambiguity: it does not guess a variant (menuitemradio vs menuitemcheckbox).
  • In #two, the explicit container role="toolbar" overrides the first token radiogroup; no radio inference occurs (tokens only supply navigation behavior).
  • Arrow key navigation, wrap, and memory behaviors apply in both #one and #two regardless of whether roles were inferred.
  • Authors can obtain behavior without pattern role inference by supplying an explicit conflicting container role when needed.

Goal

The goal of this feature is to “pave the cow path” of an existing authoring practice (and accessibility best practice) implemented in nearly every Web UI library: the roving tabindex [react, angular, fluent, elix]]. Note however, that certain design choices have been made to generalize the behavior so that additional scenarios are possible. See comparing roving tabindex and focusgroup for further details.

To achieve this goal, we believe the solution must be available in declarative markup. If JavaScript is required, then there seems little advantage to using a built-in feature over what can be implemented completely in author code. Furthermore, a declarative solution provides the key signal that allows the platform’s accessibility infrastructure to make the focusgroup accessible by default by:

  • providing a consistent and reliable navigation usage pattern for users with no extra author code required.
  • requiring no new screen reader features: the behaviors (roving tabindex, arrow + Home/End movement, optional wrap, last-focused memory) already function today when authored via JavaScript, focusgroup standardizes intent without introducing novel interaction semantics.
    • Because focusgroup requires authors to specify a pattern, and the aria-role of the element and controlled descendants are set appropriately, there should be no need for user agents to signal the AT to switch to a “Focus mode” by default (the user has entered a control group).

Non-Goals

Selection Management In some control patterns (such as radio groups or tablists) moving the focus to an element also toggles its selection state. While some use cases will require the selection state to follow the focus, in others these need to be decoupled. focusgroup is decoupled from selection. Tracking and changing selection based on focus will require author code. Note that a related proposal for tracking selection state, CSS Toggles, is no longer being pursued.

Visual Indicators Implementations are welcome to experiment with additional UI (e.g., a “focusgroup focus ring”) in order to help make users aware of focusgroups, however this proposal does not include any specific guidelines or recommendations.

Generic Container Navigation This explainer proposes that focusgroup should be limited to a specific set of roles to ensure we don’t encourage confusing or inaccessible behavior.

Principles

  1. Intuitive use in declarative scenarios. Focusgroups
    • are easy to reason about (self-documenting) in the source markup.
    • provide a rational behavior when nested.
    • integrate well with other related platform semantics (e.g., tabindex).
  2. Focusgroups are easy to maintain and configure.
    • Configuration is managed in one place.
    • Provide easy to understand usage into HTML patterns.
    • Avoid “spidery connections” e.g., using IDRefs or custom names that are hard to maintain.
  3. Complimentary declarative representations in HTML
    • HTML attributes offers focusgroup usage directly with impacted content and provide for the most straightforward scenarios.

Use Cases

  1. (Element and subtree opt-in) A focusable element with a supported role and its entire subtree can participate in a single focusgroup.
  2. (Cross Shadow DOM) Focusable elements contained inside a Shadow DOM are discoverable and focusable when their Shadow Host or an ancestor element declares a focusgroup.
  3. (Wrap) A focusgroup can be configured to have wrap-around focus semantics.
  4. (Limit directional arrow keys) A focusgroup can be configured to respond to either the logical inline-axis navigation keys (e.g., left and right arrow keys when the focusgroup is in a horizontal-tb writing mode) or block-axis navigation keys or both (to trivially reserve one axis of arrow key behavior for supplementary actions, such as opening nodes in a tree view control). See CSS Logical Properties and Values for more about logical directions.
  5. (Focus movement arrow keys follow content direction) The user’s arrow key presses move the focus forward or backward in the DOM according to the writing mode and directionality of the content. E.g., in RTL, an Arrow-Left key moves the focus forward according to the content direction.
  6. (Opt-out) Individual elements can opt-out of focusgroup participation.
  7. (Grid) A focusgroup can be used for grid-type navigation (<table>-structured content or other grid-like structured content).

A use case we are evaluating:

  • (Grid) A focusgroup can be used on elements with display: grid to provide 2d grid navigation.

Focusgroup Concepts

A focusgroup is a group of related elements that can be navigated by directional arrow keys and home/end keys and for which the web platform provides the navigation behavior by default. No JavaScript event handlers needed in many cases! The behavior of arrow keys depends on the content’s writing mode. Keys pointing toward the block-end or inline-end navigate forward, while keys pointing toward block-start or inline-start navigate backward.

There are two kinds of focusgroups: linear focusgroups and grid focusgroups. Linear focusgroups provide arrow key navigation among a list of elements. Grid focusgroups provide arrow key navigation behavior for tabular (or 2-dimensional) data structures.

  • focusgroup or focusgroup="<behavior>" (where <behavior> is one of the non-grid behaviors) defines a linear focusgroup.
  • focusgroup="grid" defines a grid focusgroup (2-D navigation).

Focusgroups consist of a focusgroup definition that establishes focusgroup candidates and focusgroup items. Focusgroup definitions manage the desired behavior for the associated focusgroup items. Focusgroup items are the elements that actually participate in the focusgroup (from the set of focusgroup candidates). Focusgroup candidates are all the elements under the scope of a focusgroup definition. The focusgroup scope consists of the element with the focusgroup definition and its shadow-inclusive descendants, excluding elements that have opted out.

The minimal focusgroup below demonstrates that the element declaring a focusgroup is also a focusgroup candidate and (in this case) the single focusgroup item.

<div focusgroup="toolbar">
  <button type="button">Only control</button>
</div>

Focusgroup candidates become focusgroup items if they are focusable and do not have a negative tabindex value, e.g., implicitly focusable elements like <button> or explicitly made focusable via non-negative tabindex values (e.g., a custom element or contenteditable). Elements with tabindex="-1" are excluded from focusgroup management entirely.

An element can only have one focusgroup definition added via the focusgroup attribute:

Example (toolbar with multiple items and one roving tab stop):

<div id="ancestor" focusgroup="toolbar">
  <button id="one" type="button">Bold</button>
  <button id="two" type="button">Italic</button>
  <button id="three" type="button">Underline</button>
  <button id="four" type="button" tabindex="-1">Help</button>
</div>

The ancestor element has the focusgroup definition. The elements with id=one, two, and three (and any other shadow-inclusive descendants of ancestor that may be added) are focusgroup candidates. Because candidates one through three are keyboard focusable, they are considered focusgroup items. When one of the focusgroup items becomes focused, the user can move focus sequentially among all the focusgroup items using the arrow keys (up/right moves focus forward, down/left moves focus backwards assuming the <p> element has writing-mode horizontal-tb and direction ltr).

Note that only the elements with id=one, id=two and id=three can be focused via arrow keys. The element with id=four has tabindex="-1", which takes it out of both sequential(tab/shift+tab) and directional(arrow keys) navigation via focusgroup.

Focusgroup segments

A focusgroup segment is a contiguous group of focusgroup items that can be navigated without crossing any opted-out elements that are participating in sequential focus navigation. An opted-out element is considered as participating in sequential focus navigation if:

  1. The element is opting out of a focusgroup via focusgroup="none" or is a descendant of such an element.
  2. The element is focusable, i.e., it is either natively focusable or has a non-negative tabindex.
  3. If the element has a negative tabindex, but currently has focus.

When one of these opted-out elements is between two focusgroup items, it divides the focusgroup into two segments, one segment on each side of the opted-out element.

Each segment operates independently for certain behaviors:

  • Guaranteed tab stop: Each segment ensures exactly one tab-accessible element.
  • Sequential navigation: Tab and Shift+Tab treat each segment as a separate focusgroup entry point

For example, if a focusgroup contains items A, B, C, D, E and item C opts-out and would participate in sequential focus navigation, the focusgroup would be split into two segments: [A, B] and [D, E]. Arrow key navigation can move freely across segments, skipping over the opted-out element C, but sequential focus navigation (Tab/Shift+Tab) will treat these as two separate tab stops.

See Impact on sequential focus navigation for detailed behavior and examples.

Last-focused memory

By default, focusgroups will remember the last-focused element, and for sequential focus navigation, will restore focus to that element when a focusgroup is re-entered, if it is present in the focusgroup segment being entered. This is important for large lists or tables so that users are returned the context they previously left without having to navigate from the start or end sequentially.

The focusgroup’s memory is initially empty. In that state, sequential focus navigation will pick the next element to focus using existing platform behavior with the exception noted below.

The focusgroup’s memory is cleared if the last-focused item becomes non-focusable or if its relationship to the focusgroup container changes (e.g., it is removed from the DOM). See additional details below.

Guaranteed tab stop

Focusgroups provide a special behavior when used in conjunction with sequential focus navigation (“tab navigation”). Focusgroups ensure that exactly one focusgroup item will participate in sequential focus navigation per segment, unless there are no focusable items. This behavior ensures that a focusgroup can always be entered via sequential focus navigation. See below for further details.

Focusgroups can therefore be used to provide a roving tabindex among a set of related focusable controls such as this toolbar:

Example:

<div focusgroup="toolbar" aria-label="Text Formatting" aria-controls="">
  <div>
    <button type="button" aria-pressed="false" value="bold"><span>Bold</span></button>
    <button type="button" aria-pressed="false" value="italic"><span>Italic</span></button>
    <button type="button" aria-pressed="false" value="underline"><span>Underline</span></button>
  </div>
</div>

When pressing tab to enter this “toolbar” focusgroup from an element before it, focus will go to the first <button> because:

  • There is no other element within the focusgroup with a tabindex >= 0 or that is sequentially focusable by default (these <button>s are taken out of sequential focus navigation with tabindex=-1).
  • This focusgroup has no “memory” of a last-focused element within (e.g., it has not been entered before).
  • Since neither of the above cases resulted in focusing an alternate element, then the first focusgroup item in the group is focused (in DOM order, or if the items are also being managed by reading-flow, in reading order).

At this point, the user can use the arrow keys to move from the beginning of the toolbar to the end, or press tab again to move outside of the focusgroup.

Note that even if multiple descendants are naturally tabbable (e.g., several <button>s without tabindex), focusgroup still collapses the group to a single sequential entry point per segment. Authors do NOT need to manually set tabindex="-1" on every non-active item to gain roving behavior.

Example:

<div focusgroup="toolbar" aria-label="Text Formatting" aria-controls="">
  <div>
    <button type="button" aria-pressed="false" value="bold"><span>Bold</span></button>
    <button type="button" aria-pressed="false" value="italic"><span>Italic</span></button>
    <button type="button" aria-pressed="false" value="underline"><span>Underline</span></button>
  </div>
</div>

Shadow DOM boundaries

Focusgroup definitions apply across Shadow DOM boundaries in order to make it easy for component developers to support focusgroup behavior across component boundaries. (Component authors that want to opt-out of this behavior can do so.)

Example:

<list-component focusgroup="listbox" role="listbox" aria-label="Cute dogs">
  <template shadowrootmode="open">
    <my-listitem role="option" focusgroup-entry-priority aria-selected="true">Terrier</my-listitem>
    <my-listitem role="option" aria-selected="false">Dalmatian</my-listitem>
    <my-listitem role="option" aria-selected="false">Saint Bernard</my-listitem>
  </template>
</list-component>

Key conflicts

The focusgroup is a default handler for certain keystrokes (keydown events for arrow keys, home/end, etc.) that will cause focus to move among focusgroup items. This default keyboard handling could interfere with other actions the application would like to take. A common pattern is to limit focusgroup directionality so that certain cross-axis keystrokes won’t trigger focusgroup behavior. However, if this doesn’t address the use case, then authors may cancel the focusgroup’s default behavior at any time by canceling (preventDefault()) the specific keydown event. Keydown events are dispatched by the currently focused element, and bubble through the focusgroup ancestor element in most cases.

Interactive content inside focusgroups

Some built-in controls like <input type=text> provide keyboard behaviors that “trap” nearly all keys that would be handled by the focusgroup. Others such as <input type=number> trap only certain keys like the arrow keys that are also used for focusgroup navigation. This proposal does not provide a built-in workaround to prevent this from happening. Instead, authors are advised to be sure users can “escape” these elements. Built-in elements provide this via the tab key. Other strategies might include requiring an “activation” step before putting focus into the interactive control (and an Esc key exit to leave).

Key conflict elements

When there is a conflict between the arrow keys consumed by the interactive element and the focusgroup’s navigation, focusgroup will not interfere with the interactive element’s behavior. This means that the normal way to move focus between focusgroup items (arrow keys) will not work when focus is within such an element.

Examples:

  • <input> elements (most, but not all types use arrow keys)
  • <textarea> elements
  • <select> elements when the arrow-key axis used by the focusgroup is the same axis used by the select
  • Elements with contenteditable
  • Focusable scrollable regions, when the scroll direction is in the same axis as the focusgroup
  • Custom elements with arrow key handlers
  • Elements with preventDefault() on arrow keys
  • Audio and video elements with visible controls
  • Iframes and object tags with focusable elements inside

For native elements that conflict, the user agent will provide an escape behavior.

For authors that add scripted key handlers that consume arrow keys, they should consider the following:

Escape behavior for native key conflict elements

When there is a conflict between the arrow keys consumed by the interactive element and the focusgroup’s navigation, focusgroup will not interfere with the interactive element’s behavior. Instead, focusgroup will provide a way to “escape” the interactive element via the tab key (and Shift+Tab). This means that when focus is within such an element, pressing Tab will move focus to the next focusable element in the focusgroup (if any), and Shift+Tab will move focus to the previous focusable element in the focusgroup (if any).

Important: These special behaviors only apply when there is an actual conflict between the arrow keys consumed by the interactive element and the focusgroup’s navigation. For example, a focusable scroll container that only uses up/down arrows for scrolling in a focusgroup with inline restriction (left/right arrows only) would not be considered a key conflict element for up/down arrow keys, since those keys don’t conflict with the focusgroup’s navigation.

There are two categories with different behaviors:

1. Native key conflict elements

For elements with built-in arrow key behaviors, user agents automatically provide tab-escape functionality. When focus is within such elements, the immediate neighboring focusgroup items become available in the normal tab order. Since focus is already within the focusgroup, memory is not taken into consideration, though the author has control over which element is focused next via tabindex ordering, using the same considerations as sequential focus navigation.

<div focusgroup="toolbar">
  <button>Bold</button>
  <button>Italic</button>
  <div>
    <input type="text" placeholder="Search" /> <!-- Native key conflict -->
    <button>Go</button>
  </div>
  <button>Save</button>
  <button>Print</button>
</div>

In this example:

  • The input field is a focusgroup item reachable via arrow keys.
  • Arrow keys are consumed for text cursor navigation once focus is within the input.
  • To ensure the user can still reach the other elements in the focusgroup, pressing Tab moves focus to the “Save” button, and Shift+Tab moves focus to the “Italic” button.
<div focusgroup="toolbar">
  <button>Bold</button>
  <button>Italic</button>
  <div>
    <input type="text" placeholder="Search" /> <!-- Native key conflict -->
    <button>Go</button>
  </div>
  <button>Save</button>
  <button>Print</button>
</div>

In this example:

  • If focus was within the “Search” input, tab will invoke the escape behavior, moving focus to “Go”.

Scrolling interactions

Focusgroups must coexist with scrolling behavior, as arrow keys are commonly used for both focus navigation and scrolling. The priority and interaction between these behaviors depends on the context:

1. Focusgroup within a scrollable region

This is the most common scenario—a focusgroup contained within a page or region that can scroll.

For focusgroups with wrap behavior: Focus navigation takes priority over scrolling. Arrow keys will move focus between focusgroup items, wrapping from end to start as configured. Scrolling will only occur as needed to bring the focused element into view.

For focusgroups without wrap behavior: Focus navigation takes priority until the focus reaches a boundary (first or last item). Once at a boundary, continuing to press arrow keys in the direction will allow normal scrolling behavior to resume.

For focusgroups with axis restrictions: If a focusgroup limits arrow keys to a specific axis (using inline or block tokens), then arrow keys in the cross-axis will be available for scrolling.

Example:

<div style="height: 200px; overflow: auto;">
  <div focusgroup="listbox block">
    <div aria-selected="true">Item 1</div>
    <div aria-selected="false">Item 2</div>
    <div aria-selected="false">Item 3</div>
    <!-- Items 4-97 would be here, creating a long scrollable list -->
    <div aria-selected="false">Item 98</div>
    <div aria-selected="false">Item 99</div>
    <div aria-selected="false">Item 100</div>
  </div>
</div>

In this example:

  • Up/down arrows navigate between list items in the focusgroup
  • Left/right arrows scroll the container horizontally (if needed) since the focusgroup is limited to block
  • Focus is automatically scrolled into view when navigating between items that are off-screen
  • Authors should be aware that since focusgroup navigation takes priority over scrolling, they should take care when constructing large focusgroups to avoid a situation where content can be missed when jumping from one item to another.

Accessibility considerations: When focusgroup items are separated by large amounts of content, arrow key navigation can skip over intermediate content that users might need to read. See the open question about scrolling behavior for potential solutions being considered.

2. Scrollable region within a focusgroup

When a scrollable element is contained within a focusgroup, the behavior depends on whether there’s a conflict between the focusgroup’s arrow key handling and the scrollable element’s needs.

No conflict scenarios:

  • Focusgroup limited to one axis, scrollable element scrolls on the cross-axis.
  • Scrollable element that scrolls via different keys (Page Up/Down, etc.)

Conflict scenarios: When both the focusgroup and the scrollable element want to handle the same arrow keys, the scrollable element is treated as a key conflict element. This means:

  • Arrow keys are consumed by the scrollable element for scrolling.
  • If this is a native element, and not custom script, then Tab/Shift+Tab can be used to move to adjacent focusgroup items following the escape behavior for native key conflict elements.

Focusgroup interacts with two web platform features related to navigation and orientation: the CSS reading-flow property and the ARIA aria-orientation attribute.

Reading flow

The reading-flow CSS property modifies the reading order of elements for sequential navigation (Tab key) and assistive technologies. Focusgroup’s directional navigation (arrow keys) respects reading-flow, ensuring that arrow keys move focus in the expected visual direction.

Key points:

  • Both sequential focus navigation (Tab/Shift+Tab) and directional navigation (arrow keys) follow reading-flow order
  • Arrow key direction is determined by visual position in the reading-flow order
  • Right/Down arrows move to the next item in reading-flow order; Left/Up arrows move to the previous item
  • This ensures arrow key navigation matches the visual layout established by reading-flow

Example:

<div focusgroup="toolbar" style="display: flex; flex-direction: row-reverse; reading-flow: flex-visual;">
  <button>First in DOM</button>
  <button>Second in DOM</button>
  <button>Third in DOM</button>
</div>

In this example:

  • Visual order (left to right): Third → Second → First
  • Tab order follows reading-flow: Third → Second → First
  • Arrow key navigation follows the same order: Right arrow moves from Third → Second → First
  • Pressing Right Arrow moves focus visually to the right, matching user expectations

ARIA orientation

The aria-orientation attribute indicates whether a widget’s orientation is horizontal, vertical, or undefined. Focusgroup does not automatically infer or set aria-orientation from inline/block tokens.

Key points:

  • Focusgroup’s inline/block tokens control keyboard behavior (which arrow keys work)
  • aria-orientation conveys semantic structure to assistive technologies
  • These are related but distinct concerns: axis restrictions may be used to avoid key conflicts rather than to indicate orientation
  • When focusgroup applies a minimum role, that role’s default aria-orientation applies unless explicitly overridden
  • Authors should set aria-orientation explicitly when the default doesn’t match the widget’s semantic structure

Restricted elements

Because focusgroup definitions are intended for grouping related controls, it does not make sense to provide focusgroup functionality on all elements. While the focusgroup attribute may be defined as a global attribute, its applicability is limited to a subset of behaviors, requiring the author to specify the behavior their control follows when using focusgroup, see: supported behaviors

The current proposal is to limit focusgroup to only the elements whose name match the DOM’s valid shadow host names (which are the elements allowed to call attachShadow()). However, <table> and some table parts will need to be an exception in order to properly support grid focusgroups.

Feature detection

To enable feature detection, the DOM will include a focusgroup property, whose existence on elements is useful for feature detection.

partial interface HTMLElement {
  [CEReactions] attribute DOMString focusgroup;
};

Additional features

Focusgroups have the following additional features:

  • Wrap-around semantics - what to do when attempting to move past the end of a focusgroup. The default/initial value is no-wrap, which means that focus is not moved past the ends of a focusgroup with the arrow keys. wrap and other tabular wrapping behaviors are available for grid focusgroups.
  • Directional axis limits - applies to linear focusgroups only; respond to arrow keys in one axis only (either up/down or left/right when the arrow key pressed matches the corresponding flow of the content). By default, linear focusgroups respond to all four arrow keys.
  • focusgroup candidacy opt-out - prevent an element and its shadow-inclusive descendants from participating in an ancestor’s focusgroup.
  • Memory opt-out - prevent the focusgroup from remembering what the last focused element was when focus leaves a focusgroup. By default focusgroups remember that element and will restore the focus to that element when the focusgroup is re-entered via sequential focus navigation.

These feature options are applied as space-separated token values to the focusgroup attribute.

Entry Priority Control

The focusgroup-entry-priority attribute can be set on individual focusgroup items to control which element receives focus when entering a focusgroup segment via sequential focus navigation (Tab/Shift+Tab). This attribute provides explicit control over the initial focus target within a focusgroup when no previous focus memory exists.

Important: If a focusgroup has memory enabled (the default) and a last-focused element exists within the segment being entered, that element will be restored instead of the element with focusgroup-entry-priority. Entry priority only applies when focus is entering the focusgroup for the first time or when using no-memory to disable the memory feature.

<div focusgroup="toolbar">
  <button>Bold</button>
  <button focusgroup-entry-priority>Italic (recommended)</button>
  <button>Underlined</button>
</div>

When multiple elements within the same focusgroup segment have the focusgroup-entry-priority attribute, the first element with the attribute in DOM order will be prioritized, or if the items are also being managed by reading-flow, the first such item in reading order. The direction of navigation (Tab vs Shift+Tab) does not affect which element is chosen - the same element will always receive focus when entering the segment, promoting consistent user experience.

Key behaviors:

  • Focusgroup items with focusgroup-entry-priority are prioritized.
  • If no focusgroup item has focusgroup-entry-priority, the first focusgroup item in the segment is chosen (in DOM order, or if the items are also being managed by reading-flow, in reading order).
  • Memory (when enabled) takes precedence over entry priority - a previously focused element will be restored even if other elements have focusgroup-entry-priority

Memory vs Entry Priority Example

This example demonstrates how memory takes precedence over entry priority:

<div focusgroup="toolbar" aria-label="Text formatting">
  <button>Bold</button>
  <button focusgroup-entry-priority>Italic (entry priority)</button>
  <button>Underline</button>
</div>

Behavior sequence:

  1. First time entering the focusgroup: Focus goes to “Italic” (entry priority applies since no memory exists)
  2. User arrows to “Underline”: Focus moves to “Underline” and memory is updated
  3. User tabs away and then back: Focus goes to “Underline” (memory restored, entry priority ignored)
  4. Using no-memory: With focusgroup="toolbar no-memory", focus would always go to “Italic” regardless of previous focus

Multiple Items with the Entry Priority Attribute (Not Recommended)

Focusgroup will always prioritize the first element with the focusgroup-entry-priority attribute in DOM order when entering a focusgroup segment, or if the items are also being managed by reading-flow, the first such item in reading order, and will not consider any subsequent elements with the same attribute.

<div focusgroup="toolbar no-memory">
  <button>Bold</button>
  <button focusgroup-entry-priority>Italic</button>
  <button focusgroup-entry-priority>Underline</button>
  <button>Strikethrough</button>
</div>

In this example, “Italic” will receive focus when entering the focusgroup because it appears first in DOM order among elements with focusgroup-entry-priority, or if the items are also being managed by reading-flow, it would be the first in reading order among elements with focusgroup-entry-priority.

Interaction with Nested Focusgroups

When focusgroups are nested, each focusgroup manages its own entry priority independently. The focusgroup-entry-priority attribute only affects the focusgroup that directly contains the element.

<div focusgroup="toolbar" aria-label="Main toolbar">
  <button>Save</button>
  <button focusgroup-entry-priority>Print</button>

  <!-- Nested focusgroup with its own entry priority -->
  <div focusgroup="toolbar" aria-label="Text formatting">
    <button>Bold</button>
    <button focusgroup-entry-priority>Italic</button>
    <button>Underline</button>
  </div>

  <button>Close</button>
  <button focusgroup-entry-priority>Exit</button>
</div>
<button>After</button>

In this example, there are two distinct focusgroups, and three focusgroup segments:

Main toolbar: [Save, Print], [Close, Exit]

  • Entry priority: “Print” (has focusgroup-entry-priority)

Nested toolbar: [Bold, Italic, Underline]

  • Entry priority: “Italic” (has focusgroup-entry-priority)

Sequential navigation behavior:

  1. Tab into main toolbar: Focus goes to “Print” (entry priority)
  2. Tab from “Print”: Focus moves to “Italic” (entry priority in nested toolbar)
  3. Tab from nested toolbar: Focus moves to “Close” (continuing in main toolbar)
  4. Shift+Tab into main toolbar from “After”: Focus goes to “Exit” (entry priority)

Directional navigation behavior:

  • Arrow key navigation within the main toolbar moves between “Save”, “Print”, “Close”, and “Exit”, but skips over the nested toolbar
  • Arrow key navigation within the nested toolbar moves between “Bold”, “Italic”, and “Underline”
  • Each focusgroup maintains independent arrow navigation

This demonstrates how entry priority works independently for each nested focusgroup and how exiting and entering nested focusgroups respects each segment’s entry priority.

Interaction with Opted-Out Subtrees

When elements opt out of a focusgroup using focusgroup="none", they can create multiple focusgroup segments, each with their own entry priority behavior.

<div focusgroup="toolbar no-memory" aria-label="Document toolbar">
  <button>New</button>
  <button focusgroup-entry-priority>Open</button>
  <button>Save</button>

  <!-- Opted-out help section -->
  <div focusgroup="none" aria-label="Help section">
    <button>Help</button>
    <button>Shortcuts</button>
  </div>

  <button>Close</button>
  <button focusgroup-entry-priority>Exit</button>
</div>

In this example, the opted-out help section creates two focusgroup segments:

Segment 1 (before the opted-out section): [New, Open, Save]

  • Entry priority: “Open” (has focusgroup-entry-priority)

Segment 2 (after the opted-out section): [Close, Exit]

  • Entry priority: “Exit” (has focusgroup-entry-priority)

Sequential navigation behavior:

  1. Tab into Segment 1: Focus goes to “Open”
  2. Tab from “Save”: Focus moves to “Help” (first opted-out element)
  3. Tab from “Shortcuts”: Focus moves to “Exit” (entry priority in Segment 2)
  4. Shift+Tab from “Help”: Focus returns to Segment 1, going to “Open” (entry priority)

** Directional navigation behavior:**

  • Arrow key navigation can move freely across both segments, skipping over the opted-out help section.

This demonstrates how entry priority works independently within each segment created by opted-out elements.

Enabling wrapping behaviors

By default, focusgroup traversal with arrow keys ends at boundaries of the focusgroup (the start and end of a linear focusgroup, and the start and end of both rows and columns in a grid focusgroup). The following focusgroup definition values change this behavior:

HTML (attribute value)Applies ToEffect
focusgroup="<behavior>" (where <behavior> is one of the non-grid behaviors)linearNo wrapping; edges are hard stops.
focusgroup="<behavior> wrap" (where <behavior> is one of the non-grid behaviors)linearMoving past one end wraps focus to the opposite end.
(default) focusgroup="grid"gridNo wrapping; row and column edges are hard stops.
focusgroup="grid wrap"gridRows and columns wrap within their own row/column (end → start of same row/column).
focusgroup="grid row-wrap"gridRows wrap; columns do not.
focusgroup="grid col-wrap"gridColumns wrap; rows do not.
focusgroup="grid flow"gridMoving past row end jumps to start of NEXT row (and reverse); same for columns. Last row/column flows to first.
focusgroup="grid row-flow"gridRows flow; columns are hard stops.
focusgroup="grid col-flow"gridColumns flow; rows are hard stops.
focusgroup="grid row-wrap col-flow"gridRows wrap; columns flow.
focusgroup="grid row-flow col-wrap"gridRows flow; columns wrap.

Specifying both row-wrap and row-flow in one HTML focusgroup definition is an author error. Only one declaration for row behavior is allowed. Similarly for col-wrap and col-flow.

Limiting linear focusgroup directionality

In many cases, having multi-axis directional movement (e.g., both right arrow and down arrow linked to the forward direction) is not desirable, such as when implementing a tablist control pattern, in which case it may not make sense for the up and down arrows to also move the focus left and right. Likewise, when moving up and down in a vertical menu, the author might wish to use JavaScript to provide other behavior for the left and right arrow keys such as opening or closing sub-menus. In these situations, authors can limit the linear focusgroup to one-axis traversal.

Note that the following only apply to linear focusgroup definitions. (All currently supported behaviors are linear; grid is a future consideration.)

Note: <behavior> below refers to one of the non-grid behaviors.

HTML (attribute value)Explanation
(default) focusgroup="<behavior>"Items respond to forward/backward movement via both inline and block arrow keys (where they map to forward/back).
focusgroup="<behavior> inline"Items respond only to arrow keys parallel to the inline axis (e.g., Left/Right in horizontal-tb).
focusgroup="<behavior> block"Items respond only to arrow keys parallel to the block axis (e.g., Up/Down in vertical menus or vertical writing modes).

Example:

<tab-group focusgroup="tablist inline wrap">
  <a-tab role="tab" aria-selected="true" aria-controls=""></a-tab>
  <a-tab role="tab" aria-selected="false" aria-controls=""></a-tab>
  <a-tab role="tab" aria-selected="false" aria-controls=""></a-tab>
</tab-group>

In the above example, when the focus is on the first <a-tab> element, pressing either the up or down arrow key does nothing because the focusgroup is configured to only respond to the inline (left/right in this case) arrow keys.

Because 2-axis directionality is the default, specifying both inline and block at the same time on one focusgroup is not allowed:

Example:

<!-- This is an example of what NOT TO DO -->
<radiobutton-group focusgroup="inline block wrap" role="radiogroup">
  ⚠️This `focusgroup` configuration is an error--neither constraint will be applied (which is actually
  what the author intended).
</radiobutton-group>

Opting-out

focusgroup definitions assigned to an element create focusgroup candidates that include the element itself and all its shadow-inclusive descendant elements. Any element within that focusgroup scope that is (or becomes) focusable will automatically become a focusgroup item belonging to its ancestor’s focusgroup.

With such an expansive opt-in behavior, it is important to provide an opt-out for elements or element subtrees. For example: focusable elements that wish to remain in sequential focus navigation and have arrow key navigation pass them over; or, components nested across a Shadow DOM boundary that wish to be excluded from focusgroup participation.

Opting-out applies to the element making the declaration as well as its shadow-inclusive descendants.

To opt-out:

HTML (attribute value)Explanation
focusgroup="none"Opt-out: this element and its shadow-inclusive descendants are not considered focusgroup candidates.

Impact on sequential focus navigation

When elements opt-out of a focusgroup using focusgroup="none", they can effectively divide the focusgroup for sequential focus navigation purposes, creating multiple tab stops where the focusgroup would normally have only one. This happens because opted-out elements remain in the normal sequential focus navigation order, necessitating the splitting the focusgroup into separate segments to ensure content is not skipped.

Sequential focus navigation within each resulting segment follows the visual reading order established by CSS reading-flow when applied to the container or relevant ancestors.

In the following example, a help section opts-out of focusgroup behavior so that any interactive content inside it is bypassed when arrowing among the primary formatting controls, but remains reachable via tabbing.

<div focusgroup="toolbar inline wrap" aria-label="Text formatting">
  <button type="button">Bold</button>
  <button type="button">Italic</button>
  <span focusgroup="none" aria-label="Help group">
    <button type="button">Help</button>
    <button type="button">Shortcuts</button>
  </span>
  <button type="button">Underline</button>
</div>

Sequential focus navigation behavior:

When a user navigates into a focusgroup using Tab or Shift+Tab, the user agent determines the appropriate focusgroup segment and applies the guaranteed tab stop algorithm:

  1. Entering from before: Pressing Tab to enter this focusgroup from a preceding element will focus “Bold”. The focusgroup segment includes only the items reachable without crossing opted-out elements (“Bold” and “Italic”).

  2. Tab from “Bold”: Pressing Tab moves focus to “Help” (the first opted-out element in sequential order).

  3. Tab from “Help”: Pressing Tab moves focus to “Shortcuts” (following normal sequential focus navigation within the opted-out subtree).

  4. Tab from “Shortcuts”: Pressing Tab will re-enter the focusgroup. The focusgroup segment now includes only items that follow “Shortcuts” without crossing opted-out elements (“Underline”). Since “Underline” is the only focusgroup item in the segment, the guaranteed tab stop algorithm focuses “Underline”.

  5. Shift+Tab from “Help”: Pressing Shift+Tab will move focus back to the focusgroup segment that precedes “Help” in tree order (“Bold” and “Italic”). The guaranteed tab stop algorithm is applied only to this segment, considering any memory and sequentially focusable items within this segment only.

Key behaviors:

  • Focusgroup segments: As defined above, opted-out elements create boundaries that divide the focusgroup.
  • Guaranteed tab stop per segment: Each segment follows the guaranteed tab stop algorithm.
  • Memory scope: Focusgroup memory restoration only applies if the memory item is in the segment being entered.
  • Direction-based segment selection: Tab vs Shift+Tab determines which segment is considered for entry.

Arrow key navigation is unaffected: Arrow keys skip over opted-out elements entirely, treating them as if they don’t exist for arrow navigation purposes.

Nested Focusgroups

When a focusgroup definition is applied to an element, it implicitly opts out of any ancestor’s focusgroups. This ensures that every element can only belong in one focusgroup at a time.

Example:

<ul focusgroup="menubar" aria-label="Site">
  <li role="none">
    <ul focusgroup="menu" aria-label="Products">
      <li><button role="menuitem">Item A</button></li>
      <li>
        <ul focusgroup="menu" aria-label="More">
          <li><button role="menuitem">Sub A1</button></li>
        </ul>
      </li>
    </ul>
  </li>
</ul>
  • The outer menubar ul[role="menubar"][focusgroup] defines one focusgroup (its direct menuitems would participate when present).
  • The first nested ul[role="menu"][focusgroup] creates an independent focusgroup and is implicitly not part of the menubar’s arrow navigation.
  • The innermost ul[role="menu"][focusgroup] (submenu) defines yet another independent focusgroup, likewise not part of its ancestor menu’s focusgroup; its menuitem participates only in that deepest scope.

Disabling focusgroup memory

By default, when a linear or grid focusgroup is defined it includes a “memory” of the last-focused element within its scope, initially empty. Each time the focus is changed within a focusgroup, the “memory” is updated to refer to that element. This behavior is akin to the roving tabindex in which the “memory” is the stateful tabindex="0" value assigned to the currently focused element.

In some scenarios it is not desirable to have a focusgroup maintain a memory. Usually this is because there is a more relevant element which should take focus when entering the focusgroup instead of the most-recently-focused element. For example, an active (selected) tab in a role="tablist" container, rather than the last-focused tab (when selection does not follow focus).

To disable the focusgroup’s default memory, use the value no-memory:

Note: <behavior> below refers to any valid focusgroup behavior.

HTML (attribute value)Explanation
(default) focusgroup="<behavior>"focusgroup remembers the last-focused element and redirects focus to it when entered via sequential focus navigation.
focusgroup="<behavior> no-memory"focusgroup will not remember the last-focused element.

After the focusgroup’s memory has been set, it must be cleared whenever any one of the following change:

  • The element with the focusgroup definition is hidden or un-hidden; or if the currently remembered element is hidden or un-hidden.
  • The element with the focusgroup definition has its disabled or inert status changed; or if the currently remembered element has its disabled or inert status changed.
  • The element with the focusgroup definition is removed from the shadow-inclusive tree; or if the currently remembered element is removed from the shadow-inclusive tree.
  • The currently remembered element stops being focusable (e.g., a <div> with a tabindex has its tabindex attribute removed).
  • The currently remembered element is changed to become excluded from the focusgroup (through focusgroup="none" on itself or a shadow-inclusive ancestor, or by changingfocusgroups: if a newfocusgroup` definition appears on itself or one of its shadow-inclusive ancestor elements).

Adjustments to sequential focus navigation

To ensure that a focusgroup segment has exactly one tab stop in the sequential focus navigation order, and to provide the appropriate “hook” for a focusgroup’s “memory” to redirect focus to the last-focused element in a focusgroup, a change to sequential focus navigation is needed.

Guaranteed tab stop algorithm

When a user navigates into a focusgroup using Tab or Shift+Tab, the user agent must:

1. Identify the focusgroup segment: The set of focusgroup items that are reachable without crossing any opted-out items in tree order from the entry point.

2. Apply focus priority (first match wins):

  • If the focusgroup has a last focused item and the no-memory token is not present, and the last focused item is within the current segment, focus that item.
  • Otherwise, if an item within the segment has the focusgroup-entry-priority attribute, focus the first such item in tree order, or if the items are also being managed by reading-flow, focus the first such item in reading order.
  • Otherwise, if an item within the segment is sequentially focusable (e.g., has tabindex="0" or is a natively focusable element like <button>), focus the first such item in tree order, or if the items are also being managed by reading-flow, focus the first such item in reading order.

3. Update tab navigation:

All other focusgroup items are not considered in sequential focus navigation order. Memory is updated to be on the newly focused item if the no-memory token is not present.


This algorithm ensures that each focusgroup segment has a single guaranteed tab stop, without the need for authors to manage tabindex values. Authors can use the focusgroup-entry-priority attribute to explicitly specify which element should receive focus when entering a focusgroup segment.

Additionally, when focus is within native key conflict elements, immediate neighboring focusgroup items are automatically made available in the tab order to provide an escape mechanism.

(Future Consideration) Grid focusgroups

Some focusable data is structured not as a series of nested linear groups, but as a 2-dimensional grid such as in a spreadsheet app, where focus can move logically from cell-to-cell either horizontally or vertically. In these data structures, it makes sense to support the user’s logical usage of the arrow keys to move around the data.

Grid navigation is expected to happen within well-structured content with consistent rows and columns where DOM structure reflects this organization. In focusgroup grid navigation, only the cells in the grid are focusgroup candidates and only the focusable cells become focusgroup items. It is not currently possible to use grid focusgroups to support other focusable tabular parts such as focusable row elements (see comment in issue 1018 for a possible future addition for this use case).

Applicability to tabular data

The arrow navigation in the grid (and in the previous non-grid scenarios) should reflect the accessible structure of the document, not the presentation view made possible with CSS. For example, it is easy to create views that visually appear grid-like, but do not make sense to navigate like a grid if considering that the data model is fundamentally a list, which is how users of accessibility technology would perceive it. Wrapping a list of contact cards on screen in a grid-like presentation allows for more content density on screen for sighted users. In that scenario, arrow key navigation to move linearly (left-to-right following the line-breaking across each line) through the contents makes sense (especially if these are alphabetized), but orthogonal movement through the “grid” (up/down when cards are aligned or in a masonry layout) jumps the focus to seemingly arbitrary locations. Multi-directional arrow key navigation may seem appropriate for sighted users that have the visual context only, but are not appropriate for assistive technology. In the case of the list-presented-as-a-grid scenario, a linear focusgroup will make the most sense for sighted as well as users with accessibility needs.

When considering using a grid focusgroup, be sure that the data is structured like a grid and that the structure makes semantic sense in two dimensions (and not just for a particular layout or presentation).

Tabular data can be structured using column-major or row-major organization. Given that HTML tables and ARIA attributes for grids (role="grid", role="row", role="gridcell") only exist for row-major grid types, this proposal does not define grid focusgroup organization for column-major data structures (and assumes row-major data structures throughout).

Setting up a grid focusgroup

Grid focusgroups can be created “automatically” or manually. Automatic grids use the context of existing HTML semantics for tables as the structural components necessary to provide grid-based navigation. Any elements with computed table layout are suitable for an automatic grid (e.g., display: table-row in place of using a <tr> elements). Manual grid creation requires annotating specific elements with their focusgroup grid component names.

Note: We are evaluating the suitability for CSS display: grid to create automatic grids.

The automatic grid approach will be preferable when the grid contents are uniform and consistent and when re-using semantic elements for grids (typical). The manual approach may be necessary when the grid structure is not uniform or structurally inconsistent (atypical), and involves identifying the parts of the grid on specific focusgroup candidates using CSS.

Elements with the grid focusgroup definition on the root element of the structural grid become automatic grid focusgroups. The implementation must attempt to validate the structure of the grid to ensure it has appropriate row and cell structures. In the event that the implementation cannot automatically determine the grid structure, then the definition is ignored (i.e., there is no fallback to a linear grid).

HTMLExplanation
focusgroup="grid"Establishes the root of an automatic grid focusgroup. Shadow-inclusive descendants of the automatic grid are identified and assigned focus-group-type: grid-row and focus-group-type: grid-cell focusgroup candidate status automatically.

Example:

<table aria-label="Tic tac toe grid" role="grid" focusgroup="grid">
  <tr>
    <td tabindex="0"></td>
    <td tabindex="0"></td>
    <td tabindex="0"></td>
  </tr>
  <tr>
    <td tabindex="0"></td>
    <td tabindex="0"></td>
    <td tabindex="0"></td>
  </tr>
  <tr>
    <td tabindex="0"></td>
    <td tabindex="0"></td>
    <td tabindex="0"></td>
  </tr>
</table>

The <table>’s grid focusgroup definition automatically establishes each of its descendant <tr>s as focusgroup rows (the parser-generated <tbody> is accounted for) and <td>s as focusgroup cells. All cells have tabindex="0" to make them focusable and thus eligible to be focusgroup items. The focusgroup manages which cell is actually reachable via sequential navigation while allowing arrow key navigation between all cells. Among all focusgroup cells, the left/right arrow keys navigate between cells in the table, and up/down arrow keys will compute the new target based on the DOM position of the current focusgroup candidate cell in relation to the focusgroup candidate row.

Manual grids: row and cell connections

A manual grid is declared in a focusgroup definition with the name manual-grid. With a manual grid, the rows and cells must be explicitly indicated using grid-row and grid-cell:

HTML (attribute value)Explanation
focusgroup="manual-grid"Establishes the root of a manual grid focusgroup. Shadow-inclusive descendants must be identified explicitly as grid rows and grid cells.
focusgroup="grid-row"Must be a shadow-inclusive descendant of a manual grid focusgroup root.
focusgroup="grid-cell"Must be a shadow-inclusive descendant of a manual grid focusgroup root and also a shadow-inclusive descendant of a grid row.

Cells cannot be descendants of other cells, and rows cannot be descendants of other rows.

Each focusgroup candidate will perform an ancestor search to locate its nearest grid structural component: cells will look for their nearest row, and rows will look for their nearest grid root.

In the following example, the <my-cell>s are all meant to be on the same row of the grid, and the rows are designated by <my-row> elements:

Example:

<my-root role="grid" focusgroup="manual-grid">
  <div role="none" class="presentational_wrapper"></div>
  <my-row role="row" focusgroup="grid-row">
    <first-thing role="gridcell" focusgroup="grid-cell"></first-thing>
    <cell-container role="none">
      <my-cell role="gridcell" focusgroup="grid-cell"></my-cell>
      <my-cell role="gridcell" focusgroup="grid-cell"></my-cell>
    </cell-container>
    <cell-container role="none">
      <my-cell role="gridcell" focusgroup="grid-cell"></my-cell>
      <my-cell role="gridcell" focusgroup="grid-cell"></my-cell>
    </cell-container>
  </my-row>
  <!-- repeat pattern of div/my-row pairs... -->
</my-root>

The following non-uniform structure can still have grid semantics added via manual-grid:

Example:

<div role="grid" focusgroup="manual-grid flow">
  <div role="row" focusgroup="grid-row">
    <div>
      <div role="gridcell" focusgroup="grid-cell"></div>
      <div role="gridcell" focusgroup="grid-cell"></div>
    </div>
  </div>
  <div>
    <div role="row" focusgroup="grid-row">
      <div role="gridcell" focusgroup="grid-cell"></div>
      <div role="gridcell" focusgroup="grid-cell"></div>
    </div>
  </div>
  <div>
    <div>
      <div role="row" focusgroup="grid-row">
        <div>
          <div role="gridcell" focusgroup="grid-cell"></div>
          <div role="gridcell" focusgroup="grid-cell"></div>
        </div>
      </div>
    </div>
  </div>
</div>

Grid focusgroup nesting

Unlike linear focusgroups, an automatic or manual grid focusgroup requires a small degree of DOM structure to work correctly. Unless the proper structure exists, the grid focusgroup won’t work.

Attempts to define new grid or linear focusgroups among the DOM elements that make up the structure of a grid focusgroup (such as on or between elements defining the root grid container, the grid-rows and the grid-cells) will be ignored. However new grid or linear focusgroups can be defined on elements that are shadow-inclusive descendants of grid-cell elements (e.g., that are outside the set of elements making up the grid’s DOM structure).

Empty Cell data

Like linear focusgroups, focus is only set on elements that are focusable. The arrow key navigation algorithms look for the first focusgroup item (in DOM order) of a grid focusgroup cell in the direction the arrow was pressed. Non-focusable grid focusgroup candidates of a focusgroup cell are passed over in the search.

Non-uniform cells

It is entirely possible to have rows with non-uniform numbers of cells. In these cases, focusgroup navigation behaviors may not work as visibly desired. Algorithms for navigating grid focusgroups will work based on content the grid content structure as specified. If the algorithms conclude that there is no “next candidate cell” to move to (e.g., in a grid with two rows, and the bottom row has three cells, and the top row only two, if the focus is on the 3rd cell, a request to move “up” to the prior row cannot be honored because there is no “3rd cell” in that row.

(Future Consideration) Additional Keyboard support

In addition to arrow keys, the focusgroup should also enable other navigation keys such as pageup/down for paginated movement (TBD on how this could be calculated and in what increments), as well as the home/end keys to jump to the beginning and end of groups.

It might also be interesting to add support for typeahead scenarios (though what values to look for when building an index would need to be worked out, and may ultimately prove to be too complicated).

Authoring guidance

  1. Put the behavior token first: focusgroup="tablist wrap", focusgroup="toolbar".
  2. Omit common child roles unless you need a variant (checkbox / radio menu items, mixed controls, etc.).
  3. For detailed precedence (mismatches, inference limits, overrides) see Behavior → role mapping & precedence.

Alternatives considered

When considering how to ensure that focusgroup usage is scoped to scenarios we want, the following approaches were considered.

  1. Role-required gating (original): only activates when an eligible role attribute is present. Rejected: couples behavior activation to ARIA; breaks precedent.
  2. Parent-only implicit role (earlier alternative): ensures that aria norms are followed, while still allowing authors to avoid repetition.
  3. Adopted (parent token + child inference): Same as above, but additionally sets the expected role on children that participate in the focusgroup.

Open Questions

  1. Supported Behaviors: The list of supported behaviors for focusgroup is open for discussion, and I am open to input on what should be allowed.
  • MDN documentation includes a recommendation to not use grid and listbox as composite widgets, but supporting them here would send a signal that it is OK to use them in this scenario.
  • Are there other roles not listed here that have a strong use case for focusgroup?
  1. Grid Support: The functionality for grid and other 2-D navigation has been moved to “Future Considerations” due to its complexity. I am open to discussing how to best implement this functionality in the future including if this should be moved into a separate explainer.
  2. CSS Mappings: This explainer does not currently define specific CSS mappings for focusgroup, but this is an area that could be explored in the future, or if others feel this is integral to the feature, this could be considered to be added back in.
  3. Attribute Functionality and Dependencies: Feedback requested on adopted scoping (role token + optional child role inference):
  • Exact list & phased expansion of child role inference set (which roles, staged rollout?).
  • Should mismatched explicit container role vs first token trigger a console warning or be silent?
  • Policy for console warnings vs silent ignore on token / role mismatches.
  1. Alternative approaches to scope focusgroup to specific scenarios
  • (A) Current: first token = pattern + (optional) child role inference.
  • (B) Require explicit container role; tokens only modifiers (drops role token entirely).
  • (C) Split into two attrs: pattern="tablist" focusgroup="wrap" (clearer separation, extra verbosity & API surface).
  • (D) Native elements only (e.g., future <tabs>, <toolbar>, <menubar>); attribute becomes redundant—risk: slower coverage, custom element ecosystems still need declarative navigation.
  • Criteria to decide: author error rate, implementation complexity, consistency with existing HTML token patterns, incremental ship path.
  1. Scrolling behavior when focus target is not in view: Should focusgroup navigation automatically prioritize scrolling over focus movement when the next focusable item is not currently visible? This addresses accessibility concerns where arrow key navigation can skip over intermediate content that users need to read (see GitHub issue #1008). Potential solution:
  • Temporarily disable focusgroup navigation when the target item is out of view, allowing normal scrolling until the item becomes visible.
  1. Keep or drop child role inference (or defer as future consideration): Should v1 exclude automatic child role assignment entirely to reduce complexity and perceived overreach (keeping only container pattern + navigation)? Rationale for revisiting: reviewer concern about mixing semantics & behavior; authors can still supply explicit roles; deferring would let us ship navigation sooner and gather data on real author pain before standardizing inference.

Privacy and Security Considerations

Privacy

No considerable privacy concerns are expected, but we welcome community feedback.

Security

No significant security concerns are expected.

Design decisions

Here is a short list to issue discussions that led to the current design of focusgroup.

See other open focusgroup issues on GitHub.

Index of focusgroup values

Tokens are space-separated. Order: first token MUST be a supported behavior token (eligible list below). Remaining tokens are modifiers. Unknown tokens are ignored.

Behavior tokens: See the earlier Supported Behaviors mapping table for the definitive list, minimum container roles, and child inference notes. Explicit container and child roles always override any inference.

Focusgroup directions:

DescriptionHTML syntax
both directions for a linear focusgroup(unspecified value; default value)
inline direction for a linear focusgroupinline
block direction for a linear focusgroupblock

Focusgroup wrapping:

DescriptionHTML syntax
no wrap(unspecified value; default value)
wrapwrap
flow (grid focusgroups only)flow
column specificcol-wrap, col-flow, col-none
row specificrow-wrap, row-flow, row-none

Focusgroup memory:

DescriptionHTML syntax
enable memory(unspecified value; default value)
disable memoryno-memory

Authoring shorthand examples:

PatternMarkup
Wrapping toolbar<div focusgroup="toolbar wrap">…
Horizontal manual-activation tabs (no memory)<div focusgroup="tablist inline wrap no-memory">…
Simple radiogroup<div focusgroup="radiogroup">…
Vertical listbox (block axis)<div focusgroup="listbox block">…

Acknowledgments

Thanks to everyone who spent time discussing and contributing to the focusgroup design and implementation, including the members of the OpenUI Community Group. Your insights, ideas, and contributions have been indispensable.