You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
| <kbd>Tab</kbd> | When focus is outside of the `tablist` moves focus to the active tab. If focus is on the active tab moves focus to the next element in the keyboard focus order, ideally the active tab's associated `tabpanel`. |
64
-
| <kbd>→</kbd> | Focuses and optionally activates the next tab in the tab list. If the current tab is the last tab in the tab list it activates the first tab. |
65
-
| <kbd>←</kbd> | Focuses and optionally activates the previous tab in the tab list. If the current tab is the first tab in the tab list it activates the last tab. |
66
-
| <kbd>Delete</kbd> | When allowed removes the currently selected tab from the tab list. |
| <kbd>Tab</kbd> | When focus is outside of the `tablist` moves focus to the active tab. If focus is on the active tab moves focus to the next element in the keyboard focus order, ideally the active tab's associated `tabpanel`. |
64
+
| <kbd>→</kbd> | Focuses and optionally activates the next tab in the tab list. If the current tab is the last tab in the tab list it activates the first tab. |
65
+
| <kbd>←</kbd> | Focuses and optionally activates the previous tab in the tab list. If the current tab is the first tab in the tab list it activates the last tab. |
66
+
| <kbd>Enter</kbd>/<kbd>Space</kbd> | When a tab has focus, activates the tab, causing its associated panel to be displayed. |
67
+
| <kbd>Home</kbd> | Focuses and optionally activates the first tab in the tab list. |
68
+
| <kbd>End</kbd> | Focuses and optionally activates the last tab in the tab list. |
69
+
| <kbd>Delete</kbd> | When allowed removes the currently selected tab from the tab list. |
67
70
68
71
### Required JavaScript features
69
72
@@ -74,48 +77,65 @@ From the assistive technology user's perspective, the heading does not exist sin
74
77
75
78
This example combines the role `tab` with `tablist` and elements with `tabpanel` to create an interactive group of tabbed content. Here we are enclosing our group of content in a `div`, with our `tablist` having an `aria-label` which labels it for assistive technology. Each `tab` is a `button` with the attributes previously mentioned. The first `tab` has both `tabindex="0"` and `aria-selected="true"` applied. These two attributes must always be coordinated as such—so when another tab is selected, it will then have `tabindex="0"` and `aria-selected="true"` applied. All unselected tabs must have `aria-selected="false"` and `tabindex="-1"`.
76
79
77
-
All of the `tabpanel` elements have `tabindex="0"` to make them tabbable, and all but the currently active one have the `hidden` attribute. The `hidden` attribute will be removed when a `tabpanel` becomes visible with JavaScript. There is some basic styling applied that restyles the buttons and changes the [`z-index`](/en-US/docs/Web/CSS/z-index) of `tab` elements to give the illusion of it connecting to the `tabpanel` for active elements, and the illusion that inactive elements are behind the active `tabpanel`.
80
+
All of the `tabpanel` elements have `tabindex="0"` to make them tabbable, and all but the currently active one have the `hidden` attribute. The `hidden` attribute will be removed when a `tabpanel` becomes visible with JavaScript.
81
+
82
+
> [!NOTE]
83
+
> Setting `tabindex` on the tab panel is unnecessary if the first element in the tab panel is focusable (such as a link), because tabbing to the link will also start reading the panel's content. However, if there are any panels in the set whose first content element is not focusable, then all tabpanel elements in a tab set should be focusable, so that screen reader users can navigate to the panel content consistently.
78
84
79
85
```html
80
86
<divclass="tabs">
81
-
<divrole="tablist"aria-label="Sample Tabs">
87
+
<divrole="tablist"aria-label="Select your operating system">
There is some basic styling applied that restyles the buttons and changes the [`z-index`](/en-US/docs/Web/CSS/z-index) of `tab` elements to give the illusion of it connecting to the `tabpanel` for active elements, and the illusion that inactive elements are behind the active `tabpanel`. You need to clearly distinguish the active tab from the inactive tabs, such as thicker borders or larger size.
138
+
119
139
```css hidden
120
140
.tabs {
121
141
padding: 1em;
@@ -137,6 +157,7 @@ All of the `tabpanel` elements have `tabindex="0"` to make them tabbable, and al
137
157
138
158
[role="tab"][aria-selected="true"] {
139
159
z-index: 3;
160
+
border-top-width: 4px;
140
161
}
141
162
142
163
[role="tabpanel"] {
@@ -154,73 +175,90 @@ All of the `tabpanel` elements have `tabindex="0"` to make them tabbable, and al
154
175
}
155
176
```
156
177
157
-
There are two things we need to do with JavaScript: we need to change focus and tab index of our `tab` elements with the right and left arrows, and we need to change the active `tab` and `tabpanel` when we click on a `tab`.
158
-
159
-
To accomplish the first, we listen for the [`keydown`](/en-US/docs/Web/API/Element/keydown_event) event on the `tablist`. If the event's [`key`](/en-US/docs/Web/API/KeyboardEvent/key) is `ArrowRight` or `ArrowLeft`, we react to the event. We start by setting the `tabindex` of the current `tab` element to -1, making it no longer tabbable. Then, if the right arrow is being pressed, we increase our tab focus counter by one. If the counter is greater than the number of `tab` elements we have, we circle back to the first tab by setting that counter to 0. If the left arrow is being pressed, we decrease our tab focus counter by one, and if it is then less than 0, we set it to the number of `tab` elements minus one (to get to the last element). Finally, we set `focus` to the `tab` element whose index is equal to the tab focus counter, and set its `tabindex` to 0 to make it tabbable.
160
-
161
-
To handle changing the active `tab` and `tabpanel`, we have a function that takes in the event, gets the element that triggered the event, the triggering element's parent element, and its grandparent element. We then find all tabs with `aria-selected="true"` inside the parent element and sets it to `false`, then sets the triggering element's `aria-selected` to `true`. After that, we find all `tabpanel` elements in the grandparent element, make them all `hidden`, and finally select the element whose `id` is equal to the triggering `tab`'s `aria-controls` and removes the `hidden` attribute, making it visible.
178
+
The user interaction is handled with JavaScript. We first get references to our `tablist`, all the `tab` elements inside it, the container of our `tabpanel` elements, and all the `tabpanel` elements inside that container. This is based on some assumptions about the structure of our HTML, so if you change the structure, you will need to change this code. If you have multiple tabbed interfaces on a page, you can wrap this code in a function and pass `tabsContainer` as an argument.
162
179
163
180
```js
164
-
// Only handle one particular tablist; if you have multiple tab
165
-
// lists (might even be nested), you have to apply this code for each one
// Enable arrow navigation between tabs in the tab list
175
-
let tabFocus =0;
190
+
For keyboard interactions, we listen for the [`keydown`](/en-US/docs/Web/API/Element/keydown_event) event on the `tablist`. In this demo, we chose to not activate the `tab` when the user navigates with the arrow keys, but instead only move focus. If you want to display the `tab` when it receives focus, you can call the `showTab()` function (defined later) instead of just calling `focus()` on the new tab.
176
191
192
+
```js
177
193
tabList.addEventListener("keydown", (e) => {
178
-
// Move right
179
-
if (e.key==="ArrowRight"||e.key==="ArrowLeft") {
180
-
tabs[tabFocus].setAttribute("tabindex", -1);
181
-
if (e.key==="ArrowRight") {
182
-
tabFocus++;
183
-
// If we're at the end, go to the start
184
-
if (tabFocus >=tabs.length) {
185
-
tabFocus =0;
186
-
}
187
-
// Move left
188
-
} elseif (e.key==="ArrowLeft") {
189
-
tabFocus--;
190
-
// If we're at the start, move to the end
191
-
if (tabFocus <0) {
192
-
tabFocus =tabs.length-1;
193
-
}
194
-
}
195
-
196
-
tabs[tabFocus].setAttribute("tabindex", 0);
197
-
tabs[tabFocus].focus();
194
+
constcurrentTab=e.target;
195
+
constcurrentIndex=tabs.indexOf(currentTab);
196
+
if (currentIndex ===-1)return; // Exit if the focused element is not a tab
The tab panel is only activated either by pressing <kbd>Enter</kbd> or <kbd>Space</kbd> while a `tab` has focus, or by clicking on a `tab`. We first define a function `showTab()` that takes in the `tab` element to be shown.
210
223
211
-
// Set this tab as selected
224
+
```js
225
+
functionshowTab(targetTab) {
226
+
// Unselect other tabs and set this tab as selected
227
+
for (consttabof tabs) {
228
+
if (tab === targetTab) continue;
229
+
tab.setAttribute("aria-selected", false);
230
+
tab.tabIndex=-1;
231
+
}
212
232
targetTab.setAttribute("aria-selected", true);
233
+
targetTab.tabIndex=0;
234
+
235
+
// Hide other tab panels and show the selected panel
236
+
consttargetTabPanel=document.getElementById(
237
+
targetTab.getAttribute("aria-controls"),
238
+
);
239
+
for (constpanelof tabPanels) {
240
+
if (panel === targetTabPanel) continue;
241
+
panel.hidden=true;
242
+
}
243
+
targetTabPanel.hidden=false;
244
+
}
245
+
```
213
246
214
-
// Hide all tab panels
215
-
tabGroup
216
-
.querySelectorAll(':scope > [role="tabpanel"]')
217
-
.forEach((p) =>p.setAttribute("hidden", true));
247
+
Now we can call this function either on a `click` event or on a `keydown` event.
0 commit comments