Skip to content

Commit ce5e53e

Browse files
janewmanCopilot
andauthored
Update focusgroup examples to follow the latest version of the API. (#121)
* Update focusgroup examples to follow the latest version of the API. * focusgroup: phase 12 consensus fixes - tablist: vertical tablist missing wrap (block overrides inline wrap default) - focusgroup="tablist block nomemory" → "tablist block wrap nomemory" - Updated attr-label, live HTML, snippet, try-it, and notice - tablist: add click handler so re-click on already-focused tab selects it - tablist: improve Demo 1/2 snippet class attrs and try-it instructions - accordion: fix remaining Tab→Shift+Tab in intro paragraph - accordion: add class="accordion" and class="accordion accordion-focus-into" to Demo 1/2 source snippets; fix JS selector to "h3 button[aria-controls]" - menu: add e.preventDefault() to Escape handler; rename triggerId→popoverId - menu: correct backwards child-role-inference claim in notice - menu: rewrite "Add JS" notice to direct voice - listbox: rewrite Home/End parenthetical (remove misleading fallback note) - toolbar: "Click the first button" → "Tab to the toolbar"; improve nomemory try-it to mention focusgroupstart explicitly - additional-concepts: fix embedded newlines in aria-label in two snippets - additional-concepts: add opacity:0.6 to opted-out buttons in snippet - additional-concepts: simplify nested-focusgroup attr-label text - index + additional-concepts: add aria-hidden="true" to all div.attr-label - index: fix role= → role in prose - README: Menu → Menu & Menubar Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Additional fixup. * additional fixup. * update snippet to match. * remove inline style for easier to read HTML. * remove explicit role where it isn't needed. * Remove redundant aria explanation. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 1dd6f8c commit ce5e53e

File tree

15 files changed

+645
-540
lines changed

15 files changed

+645
-540
lines changed

focusgroup/README.md

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,20 @@
22

33
➡️ **[Open the demo](https://microsoftedge.github.io/Demos/focusgroup/)** ⬅️
44

5-
Interactive demos for the HTML `focusgroup` attribute, which provides declarative
6-
arrow-key keyboard navigation for composite widgets by implementing the _roving tabindex_ pattern natively without JavaScript.
5+
Interactive demos for the HTML `focusgroup` attribute, which lets you add arrow-key navigation to composite widgets (the roving tabindex pattern) without JavaScript.
76

87
## Demos
98

10-
- [Toolbar](https://microsoftedge.github.io/Demos/focusgroup/toolbar.html) — Horizontal/vertical toolbar with arrow-key navigation
11-
- [Tablist](https://microsoftedge.github.io/Demos/focusgroup/tablist.html) — Tab control with inline wrapping and no-memory
12-
- [Menu](https://microsoftedge.github.io/Demos/focusgroup/menu.html) — Vertical menu and menubar with nested submenus
13-
- [Radio Group](https://microsoftedge.github.io/Demos/focusgroup/radiogroup.html) — Radio button group navigation
14-
- [Listbox](https://microsoftedge.github.io/Demos/focusgroup/listbox.html) — Selectable list navigation
15-
- [Accordion](https://microsoftedge.github.io/Demos/focusgroup/accordion.html) — Accordion with block-axis navigation and opt-out panels
16-
- [Additional Concepts](https://microsoftedge.github.io/Demos/focusgroup/additional-concepts.html) — Nested focusgroups, opt-out, shadow DOM, reading-flow
9+
> **Note:** The `focusgroup` behavior token maps a minimum ARIA role to generic containers (e.g., a plain `<div>`) and can infer child roles (e.g., `tab` on `<button>` inside a `tablist`). These demos rely on that automatic mapping. Explicit `role` attributes are only used for intentional overrides (e.g., `role="group"` on the accordion to prevent the `toolbar` role).
10+
11+
- [Index](https://microsoftedge.github.io/Demos/focusgroup/index.html): Overview page with a quick-demo toolbar and navigation to all demos
12+
- [Toolbar](https://microsoftedge.github.io/Demos/focusgroup/toolbar.html): Toolbar demos using inline and block navigation
13+
- [Tablist](https://microsoftedge.github.io/Demos/focusgroup/tablist.html): Tab control using the `tablist` behavior token (which defaults to inline + wrap), with `nomemory` to reset focus position on re-entry
14+
- [Menu & Menubar](https://microsoftedge.github.io/Demos/focusgroup/menu.html): Vertical menu and horizontal menubar with nested submenus
15+
- [Radio Group](https://microsoftedge.github.io/Demos/focusgroup/radiogroup.html): Radio button group navigation
16+
- [Listbox](https://microsoftedge.github.io/Demos/focusgroup/listbox.html): Selectable list navigation
17+
- [Accordion](https://microsoftedge.github.io/Demos/focusgroup/accordion.html): Accordion with block-axis arrow key navigation using `focusgroup="toolbar block"` and `role="group"`
18+
- [Additional Concepts](https://microsoftedge.github.io/Demos/focusgroup/additional-concepts.html): Nested focusgroups, opt-out, deep descendants, CSS `reading-flow` integration, feature detection
1719

1820
## Learn more
1921

@@ -22,4 +24,4 @@ arrow-key keyboard navigation for composite widgets by implementing the _roving
2224

2325
## Requirements
2426

25-
May require enabling the **Experimental Web Platform features** flag at `about://flags` in Microsoft Edge or another Chromium-based browser.
27+
These demos use the scoped-focusgroup variant of the spec and require enabling the **Experimental Web Platform features** flag at `about://flags` in Microsoft Edge or another Chromium-based browser. The feature is experimental and not yet enabled by default in stable builds. See the [Open UI explainer](https://open-ui.org/components/scoped-focusgroup.explainer/) for the spec status.

focusgroup/accordion.html

Lines changed: 99 additions & 96 deletions
Large diffs are not rendered by default.

focusgroup/accordion.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* ============================================================
2-
Focusgroup Demo Accordion
2+
Focusgroup Demo - Accordion
33
============================================================
44
focusgroup handles arrow-key navigation between headers.
55
This file handles the remaining application logic:
@@ -12,7 +12,7 @@
1212
focusable scrollable region (tabindex="0") with
1313
focusgroup="none", so arrow keys scroll its content.
1414
15-
Requires shared.js to be loaded first.
15+
Load after shared.js for the focusgroup-support warning banner.
1616
============================================================ */
1717

1818
(function () {

focusgroup/additional-concepts.html

Lines changed: 61 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
<head>
55
<meta charset="UTF-8">
6-
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
76
<meta name="viewport" content="width=device-width, initial-scale=1.0">
87
<title>Focusgroup: Additional Concepts</title>
98
<link rel="icon" type="image/png" href="https://edgestatic.azureedge.net/welcome/static/favicon.png">
@@ -29,24 +28,23 @@ <h2 id="nested-focusgroups">Nested Focusgroups</h2>
2928
A nested focusgroup creates an independent navigation scope
3029
within an outer focusgroup.
3130
Each group has its own axis, wrapping, memory, and entry point.
32-
<kbd>Tab</kbd> moves between groups;
33-
arrows navigate within.
31+
Each focusgroup is its own tab stop; arrows navigate within the active group.
3432
</p>
35-
<div class="attr-label">focusgroup="toolbar inline" (outer) +
36-
focusgroup="toolbar inline wrap" (inner)</div>
33+
<div class="attr-label" aria-hidden="true">focusgroup="toolbar" (outer) + focusgroup="toolbar" + role="group" (inner)</div>
3734

3835
<div class="try-it">
39-
<strong>Try it:</strong> Use <kbd></kbd> in the outer toolbar.
40-
When focus reaches the nested group, press <kbd>Tab</kbd>
41-
to enter it. Use arrows inside, then <kbd>Tab</kbd> to exit
42-
back to the outer toolbar.
36+
<strong>Try it:</strong> Use <kbd></kbd> to navigate the outer
37+
toolbar; the nested group counts as a <strong>single stop</strong>;
38+
arrows land on it as a whole. Press <kbd>Tab</kbd>
39+
to move focus inside the nested group; once inside, use
40+
arrows. Press <kbd>Tab</kbd> again to exit.
4341
</div>
4442

4543
<div class="demo-container">
46-
<div focusgroup="toolbar inline" aria-label="Main toolbar" class="toolbar-horizontal">
44+
<div focusgroup="toolbar" aria-label="Main toolbar" class="toolbar-horizontal">
4745
<button type="button">Save</button>
48-
<button type="button" focusgroupstart>Print</button>
49-
<div focusgroup="toolbar inline wrap" aria-label="Text formatting" class="nested-toolbar">
46+
<button type="button">Print</button>
47+
<div role="group" focusgroup="toolbar" tabindex="0" aria-label="Text formatting" class="nested-toolbar">
5048
<button type="button">Bold</button>
5149
<button type="button">Italic</button>
5250
<button type="button">Underline</button>
@@ -57,23 +55,24 @@ <h2 id="nested-focusgroups">Nested Focusgroups</h2>
5755
</div>
5856

5957
<ul class="notice-list">
60-
<li>The nested toolbar is an <strong>independent scope</strong>
61-
inside the outer toolbar</li>
62-
<li><kbd>Tab</kbd> moves between the outer and inner
63-
focusgroups</li>
58+
<li>Each focusgroup is a separate tab stop in the page's tab order</li>
6459
<li>Arrow keys navigate within whichever group currently has
6560
focus</li>
66-
<li>Each group can have its own wrapping/memory/axis
67-
settings</li>
61+
<li>Sub-groups within a toolbar use <code>role="group"</code>
62+
rather than another <code>role="toolbar"</code>, as nesting
63+
toolbars is architecturally unsound. Arrow-key navigation
64+
works on the inner group regardless of which
65+
explicit <code>role</code> it carries.</li>
6866
</ul>
6967

7068
<details class="source-code">
7169
<summary>View source</summary>
72-
<pre><code>&lt;div focusgroup="toolbar inline" aria-label="Main
73-
toolbar"&gt;
70+
<pre><code>&lt;div focusgroup="toolbar" aria-label="Main toolbar"
71+
class="toolbar-horizontal"&gt;
7472
&lt;button type="button"&gt;Save&lt;/button&gt;
75-
&lt;button type="button" focusgroupstart&gt;Print&lt;/button&gt;
76-
&lt;div focusgroup="toolbar inline wrap" aria-label="Text formatting"&gt;
73+
&lt;button type="button"&gt;Print&lt;/button&gt;
74+
&lt;div role="group" focusgroup="toolbar" tabindex="0" aria-label="Text formatting"
75+
class="nested-toolbar"&gt;
7776
&lt;button type="button"&gt;Bold&lt;/button&gt;
7877
&lt;button type="button"&gt;Italic&lt;/button&gt;
7978
&lt;button type="button"&gt;Underline&lt;/button&gt;
@@ -90,11 +89,11 @@ <h2 id="nested-focusgroups">Nested Focusgroups</h2>
9089
<section class="demo-section">
9190
<h2 id="opt-out-segments-with-focusgroup-none">Opt-Out Segments with <code>focusgroup="none"</code></h2>
9291
<p>
93-
Use <code>focusgroup="none"</code> to exclude specific elements
94-
from arrow navigation
95-
while keeping them tabbable. Arrow keys skip right over them.
92+
Use <code>focusgroup="none"</code> to exclude a subtree's focusable
93+
descendants from arrow navigation while keeping them reachable
94+
via <kbd>Tab</kbd>.
9695
</p>
97-
<div class="attr-label">focusgroup="toolbar inline"</div>
96+
<div class="attr-label" aria-hidden="true">focusgroup="toolbar"</div>
9897

9998
<div class="try-it">
10099
<strong>Try it:</strong> Use <kbd></kbd> to navigate through
@@ -104,13 +103,13 @@ <h2 id="opt-out-segments-with-focusgroup-none">Opt-Out Segments with <code>focus
104103
</div>
105104

106105
<div class="demo-container">
107-
<div focusgroup="toolbar inline" aria-label="Segmented toolbar" class="toolbar-horizontal">
106+
<div focusgroup="toolbar" aria-label="Segmented toolbar" class="toolbar-horizontal">
108107
<button type="button">New</button>
109108
<button type="button">Open</button>
110109
<button type="button">Save</button>
111-
<span focusgroup="none" style="display: contents;">
112-
<button type="button" style="opacity: 0.6;">Help</button>
113-
<button type="button" style="opacity: 0.6;">Shortcuts</button>
110+
<span focusgroup="none">
111+
<button type="button" class="opted-out-button">Help</button>
112+
<button type="button" class="opted-out-button">Shortcuts</button>
114113
</span>
115114
<button type="button">Close</button>
116115
<button type="button">Exit</button>
@@ -128,14 +127,14 @@ <h2 id="opt-out-segments-with-focusgroup-none">Opt-Out Segments with <code>focus
128127

129128
<details class="source-code">
130129
<summary>View source</summary>
131-
<pre><code>&lt;div focusgroup="toolbar inline"
132-
aria-label="Segmented toolbar"&gt;
130+
<pre><code>&lt;div focusgroup="toolbar"
131+
aria-label="Segmented toolbar" class="toolbar-horizontal"&gt;
133132
&lt;button type="button"&gt;New&lt;/button&gt;
134133
&lt;button type="button"&gt;Open&lt;/button&gt;
135134
&lt;button type="button"&gt;Save&lt;/button&gt;
136135
&lt;span focusgroup="none"&gt;
137-
&lt;button type="button"&gt;Help&lt;/button&gt;
138-
&lt;button type="button"&gt;Shortcuts&lt;/button&gt;
136+
&lt;button type="button" class="opted-out-button"&gt;Help&lt;/button&gt;
137+
&lt;button type="button" class="opted-out-button"&gt;Shortcuts&lt;/button&gt;
139138
&lt;/span&gt;
140139
&lt;button type="button"&gt;Close&lt;/button&gt;
141140
&lt;button type="button"&gt;Exit&lt;/button&gt;
@@ -144,27 +143,27 @@ <h2 id="opt-out-segments-with-focusgroup-none">Opt-Out Segments with <code>focus
144143
</section>
145144

146145
<!-- ============================================================ -->
147-
<!-- Demo 3: Deep Descendant Discovery -->
146+
<!-- Demo 3: Deep Descendants -->
148147
<!-- ============================================================ -->
149148
<section class="demo-section">
150-
<h2 id="deep-descendant-discovery">Deep Descendant Discovery</h2>
149+
<h2 id="deep-descendant-discovery">Deep Descendants</h2>
151150
<p>
152151
Focusgroup items don't need to be direct children. The browser
153152
discovers focusable
154153
descendants at any depth (unless they are inside a nested
155154
focusgroup or <code>focusgroup="none"</code>).
156155
</p>
157-
<div class="attr-label">focusgroup="toolbar inline"</div>
156+
<div class="attr-label" aria-hidden="true">focusgroup="toolbar"</div>
158157

159158
<div class="try-it">
160-
<strong>Try it:</strong> Use <kbd></kbd> <kbd></kbd> — arrow
161-
navigation works even though buttons are deeply nested
159+
<strong>Try it:</strong> Use <kbd></kbd> <kbd></kbd> to
160+
navigate. Arrow navigation works even though buttons are deeply nested
162161
inside <code>&lt;div&gt;</code> and
163162
<code>&lt;span&gt;</code> wrappers.
164163
</div>
165164

166165
<div class="demo-container">
167-
<div focusgroup="toolbar inline" aria-label="Nested wrappers" class="toolbar-horizontal">
166+
<div focusgroup="toolbar" aria-label="Nested wrappers" class="toolbar-horizontal">
168167
<div>
169168
<span><button type="button">Alpha</button></span>
170169
<span><button type="button">Beta</button></span>
@@ -174,24 +173,18 @@ <h2 id="deep-descendant-discovery">Deep Descendant Discovery</h2>
174173
</div>
175174

176175
<ul class="notice-list">
177-
<li>Buttons are nested inside
178-
<code>&lt;div&gt;&lt;span&gt;</code> wrappers
179-
</li>
180-
<li>Focusgroup discovers them at any depth in the DOM tree</li>
181-
<li>No flat list requirement — wrapper elements for styling are
176+
<li>No flat list requirement; wrapper elements for styling are
182177
fine</li>
183178
</ul>
184179

185180
<details class="source-code">
186181
<summary>View source</summary>
187-
<pre><code>&lt;div focusgroup="toolbar inline"
188-
aria-label="Nested wrappers"&gt;
182+
<pre><code>&lt;div focusgroup="toolbar"
183+
aria-label="Nested wrappers" class="toolbar-horizontal"&gt;
189184
&lt;div&gt;
190-
&lt;span&gt;&lt;button
191-
type="button"&gt;Alpha&lt;/button&gt;&lt;/span&gt;
185+
&lt;span&gt;&lt;button type="button"&gt;Alpha&lt;/button&gt;&lt;/span&gt;
192186
&lt;span&gt;&lt;button type="button"&gt;Beta&lt;/button&gt;&lt;/span&gt;
193-
&lt;span&gt;&lt;button
194-
type="button"&gt;Gamma&lt;/button&gt;&lt;/span&gt;
187+
&lt;span&gt;&lt;button type="button"&gt;Gamma&lt;/button&gt;&lt;/span&gt;
195188
&lt;/div&gt;
196189
&lt;/div&gt;</code></pre>
197190
</details>
@@ -203,14 +196,12 @@ <h2 id="deep-descendant-discovery">Deep Descendant Discovery</h2>
203196
<section class="demo-section">
204197
<h2 id="css-reading-flow-integration">CSS <code>reading-flow</code> Integration</h2>
205198
<p>
206-
When CSS changes the visual order (e.g., <code>flex-direction:
207-
row-reverse</code>),
208-
<code>reading-flow: flex-visual</code> tells the focusgroup to
209-
follow the <strong>visual</strong>
210-
order rather than the DOM source order.
199+
When <code>reading-flow: flex-visual</code> is set, the browser
200+
uses the visual flex order for all focus navigation,
201+
including <code>focusgroup</code> arrow keys, rather than
202+
DOM source order.
211203
</p>
212-
<div class="attr-label">focusgroup="toolbar" + reading-flow:
213-
flex-visual</div>
204+
<div class="attr-label" aria-hidden="true">focusgroup="toolbar" + reading-flow: flex-visual</div>
214205

215206
<div class="try-it">
216207
<strong>Try it:</strong> Use <kbd></kbd> to navigate. With
@@ -220,31 +211,26 @@ <h2 id="css-reading-flow-integration">CSS <code>reading-flow</code> Integration<
220211
</div>
221212

222213
<div class="demo-container">
223-
<div focusgroup="toolbar" aria-label="Visual order" style="display: flex; flex-direction: row-reverse;
224-
reading-flow: flex-visual; gap: 0.35rem;">
214+
<div focusgroup="toolbar" aria-label="Visual order" class="reading-flow-toolbar">
225215
<button type="button">A (DOM first)</button>
226216
<button type="button">B (DOM second)</button>
227217
<button type="button">C (DOM third)</button>
228218
</div>
229219
</div>
230220

231221
<ul class="notice-list">
232-
<li>DOM order: A, B, C — but <code>flex-direction:
233-
row-reverse</code> visually renders C, B, A</li>
234-
<li><code>reading-flow: flex-visual</code> tells focusgroup to
235-
follow visual order</li>
222+
<li><code>reading-flow: flex-visual</code> makes the browser use
223+
visual flex order for focus navigation (Tab and arrow keys)</li>
236224
<li>Right arrow from C goes to B, then A (visual
237225
left-to-right)</li>
238-
<li>Without <code>reading-flow</code>, arrow keys would follow
239-
DOM order (A → B → C), which is visually backwards</li>
226+
<li>Without <code>reading-flow</code>, arrow keys follow
227+
DOM order (A → B → C)</li>
240228
</ul>
241229

242230
<details class="source-code">
243231
<summary>View source</summary>
244-
<pre><code>&lt;div focusgroup="toolbar" aria-label="Visual
245-
order"
246-
style="display: flex; flex-direction: row-reverse;
247-
reading-flow: flex-visual;"&gt;
232+
<pre><code>&lt;div focusgroup="toolbar" aria-label="Visual order"
233+
class="reading-flow-toolbar"&gt;
248234
&lt;button type="button"&gt;A (DOM first)&lt;/button&gt;
249235
&lt;button type="button"&gt;B (DOM second)&lt;/button&gt;
250236
&lt;button type="button"&gt;C (DOM third)&lt;/button&gt;
@@ -265,17 +251,17 @@ <h2 id="feature-detection">Feature Detection</h2>
265251
</p>
266252

267253
<div class="demo-container">
268-
<div id="feature-detect-result" style="padding: 0.75rem; border-radius: 4px;"></div>
254+
<div id="feature-detect-result"></div>
269255
</div>
270256

271257
<details class="source-code">
272258
<summary>View source</summary>
273259
<pre><code>&lt;script&gt;
274260
if ('focusgroup' in HTMLElement.prototype) {
275-
// focusgroup is supported use it!
261+
// focusgroup is supported - use it!
276262
console.log('focusgroup is supported');
277263
} else {
278-
// Not supported fall back to JS roving tabindex
264+
// Not supported - fall back to JS roving tabindex
279265
console.log('focusgroup is NOT supported');
280266
}
281267
&lt;/script&gt;</code></pre>
@@ -291,13 +277,11 @@ <h2 id="feature-detection">Feature Detection</h2>
291277
var result = document.getElementById("feature-detect-result");
292278
if (!result) return;
293279
if ("focusgroup" in HTMLElement.prototype) {
294-
result.textContent = "✅ focusgroup IS supported in this
295-
browser.";
280+
result.textContent = "✅ focusgroup IS supported in this browser.";
296281
result.style.background = "var(--accent-light)";
297282
result.style.color = "var(--success)";
298283
} else {
299-
result.textContent = "❌ focusgroup is NOT supported in this
300-
browser.Enable experimental flags to try these demos.";
284+
result.textContent = "❌ focusgroup is NOT supported in this browser. Enable experimental flags to try these demos.";
301285
result.style.background = "var(--warning-bg)";
302286
result.style.color = "var(--warning-text)";
303287
}

0 commit comments

Comments
 (0)