Challenge 2: Implement Roving Tabindex for Arrow Keys — Possible Solution ==================================================================== const tabs = Array.from(document.querySelectorAll('[role="tab"]')); function selectTab(newTab) { tabs.forEach((tab) => { tab.setAttribute("aria-selected", "false"); tab.setAttribute("tabindex", "-1"); }); newTab.setAttribute("aria-selected", "true"); newTab.setAttribute("tabindex", "0"); newTab.focus(); // (Panel-switching logic would go here too, using newTab's // associated aria-controls target — omitted since this challenge // focuses specifically on the roving-tabindex keyboard behavior.) } document.querySelector('[role="tablist"]').addEventListener("keydown", (e) => { if (e.key !== "ArrowRight" && e.key !== "ArrowLeft") return; const currentIndex = tabs.indexOf(document.activeElement); let nextIndex; if (e.key === "ArrowRight") { nextIndex = (currentIndex + 1) % tabs.length; // wraps to the first tab } else { nextIndex = (currentIndex - 1 + tabs.length) % tabs.length; // wraps to the last tab } selectTab(tabs[nextIndex]); }); WHY THIS WORKS -------------- - selectTab() is the single function responsible for BOTH halves of the roving-tabindex pattern at once: it sets aria-selected="false" and tabindex="-1" on every OTHER tab, while setting aria-selected= "true" and tabindex="0" on the newly active one — this exact combination is what this chapter describes: "only the active tab has tabindex='0'; every other tab has tabindex='-1'." - newTab.focus() actually MOVES real keyboard focus to the newly selected tab — this is what makes arrow-key navigation genuinely functional rather than just updating attributes with no visible effect; the user's next Tab/Shift+Tab or arrow press now operates relative to this newly focused tab. - The modulo arithmetic ((currentIndex + 1) % tabs.length and (currentIndex - 1 + tabs.length) % tabs.length) makes the arrow-key navigation WRAP AROUND at the ends of the tab list — pressing ArrowRight on the last tab moves to the first tab, and ArrowLeft on the first tab moves to the last one, a small but expected detail of the APG tabs pattern for a smooth, non-dead-ending keyboard experience. - The keydown listener is attached to the tablist CONTAINER (not each individual tab), checking document.activeElement to determine which tab currently has focus — this is more efficient than attaching a separate listener to every tab, and correctly reflects that only ONE tab can be focused (and therefore be document.activeElement) at any given moment, per the roving-tabindex model. - This directly addresses the exact gap this chapter's tip box warns about: role="tab" and aria-selected alone (Chapter 8's static markup example) do nothing to make arrow-key navigation actually work — this challenge is specifically the missing "other half" of behavior that markup alone can never provide.