Day 05: Tabs Component
Create a tab interface where clicking a tab shows its panel.
JavaScript focus
- syncing buttons to panels
- data-* attributes
- active states
- aria-selected
Nice extras
- arrow key navigation
- default active tab
- animated panel transition
MDN prep
Tabs
Overview content goes here.
Lorem ipsum dolor, sit amet consectetur adipisicing elit. Amet voluptate fugiat aut natus deleniti reiciendis, labore recusandae! Neque debitis est amet? Minima veniam deleniti amet minus repudiandae aliquid omnis delectus.
Notes content goes here.
Lorem ipsum dolor sit amet, Lorem ipsum dolor sit amet. consectetur adipisicing elit. Facilis, fugiat.
Resources content goes here.
Lorem ipsum dolor sit amet consectetur adipisicing elit. Eum, fugiat! Molestiae debitis quisquam nobis Lorem ipsum dolor sit amet. in repellendus iste deleniti officia cupiditate nam velit. Dignissimos corporis, unde rem odit tempore, ex modi officia dolore quia natus consequuntur.
The HTML
<section class="section tabs_component">
<h2 class="secondary_heading">Tabs</h2>
<div class="tabs">
<div class="tab_list" role="tablist" aria-label="Sample Tabs">
<button class="button" type="button" role="tab" aria-selected="false" aria-controls="tab-panel-1" id="tab-1" class="tab_button" data-tab-target="tab-panel-1" tabindex="-1">
Overview
</button>
<button class="button" type="button" role="tab" aria-selected="false" aria-controls="tab-panel-2" id="tab-2" class="tab_button" data-tab-target="tab-panel-2" tabindex="-1">
Notes
</button>
<button class="button" type="button" role="tab" aria-selected="false" aria-controls="tab-panel-3" id="tab-3" class="tab_button" data-tab-target="tab-panel-3" tabindex="-1">
Resources
</button>
</div>
<div class="tab_panels">
<div class="tab_item" id="tab-panel-1" role="tabpanel">
<p>Overview content goes here.</p>
<p><a href="#">Lorem ipsum dolor sit amet.</a></p>
<p>Lorem ipsum dolor, sit amet consectetur adipisicing elit. Amet voluptate fugiat aut natus deleniti reiciendis, labore recusandae! Neque debitis est amet? Minima veniam deleniti amet minus repudiandae aliquid omnis delectus.</p>
</div>
<div class="tab_item" id="tab-panel-2" role="tabpanel">
<p>Notes content goes here.</p>
<p>Lorem ipsum dolor sit amet, <a href="#">Lorem ipsum dolor sit amet.</a> consectetur adipisicing elit. Facilis, fugiat.</p>
</div>
<div class="tab_item" id="tab-panel-3" role="tabpanel">
<p>Resources content goes here.</p>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Eum, fugiat! Molestiae debitis quisquam nobis <a href="#">Lorem ipsum dolor sit amet.</a> in repellendus iste deleniti officia cupiditate nam velit. Dignissimos corporis, unde rem odit tempore, ex modi officia dolore quia natus consequuntur.</p>
</div>
</div>
</div>
</section>
The JavaScript
function initTabsComponent() {
const root = document.querySelector(".tabs_component");
if (!root) return;
const buttons = root.querySelectorAll(".button");
const panels = root.querySelectorAll(".tab_item");
// activate first panel
buttons[0].classList.add("active");
buttons[0].setAttribute("aria-selected", "true");
buttons[0].setAttribute("tabindex", "0");
panels[0].classList.add("active");
function activateTab(button) {
// deactivate all buttons
buttons.forEach((btn) => {
btn.classList.remove("active");
btn.setAttribute("aria-selected", "false");
btn.setAttribute("tabindex", "-1");
});
// activate button clicked
button.classList.add("active");
button.setAttribute("aria-selected", "true");
button.setAttribute("tabindex", "0");
// deactivate, then activate matching panel
panels.forEach((item) => {
item.classList.remove("active");
if (button.dataset.tabTarget === item.id) {
item.classList.add("active");
}
});
}
buttons.forEach((button, index) => {
["click", "keydown"].forEach((eventType) => {
button.addEventListener(eventType, (e) => {
// activate and focus via keyboard arrows
let newIndex = index;
if (e.key === "ArrowRight") {
newIndex = (index + 1) % buttons.length;
}
if (e.key === "ArrowLeft") {
newIndex = (index - 1 + buttons.length) % buttons.length;
}
if (newIndex !== index) {
e.preventDefault();
buttons[newIndex].focus();
activateTab(buttons[newIndex]);
return;
}
if (e.key === "Home") newIndex = 0;
if (e.key === "End") newIndex = buttons.length - 1;
// activate tabs on click
if (eventType === "click") {
activateTab(button);
}
});
});
});
}
initTabsComponent();