How to Build Toggle Tab Menus with CSS and JavaScript

How to Build Toggle Tab Menus with CSS and JavaScript

Overview

A toggle tab menu lets users switch between panels/content by clicking tabs. Implementing one requires accessible markup, CSS for layout/visibility, and JavaScript to toggle active states and manage focus/ARIA for assistive tech.

HTML structure

  • Use a tablist and buttons for tabs (keyboard focusable).
  • Associate each tab with a panel via aria-controls and matching id. Example pattern:

html

<div role=tablist aria-label=Sample Tabs> <button role=tab aria-selected=true aria-controls=panel-1 id=tab-1>Tab 1</button> <button role=tab aria-selected=false aria-controls=panel-2 id=tab-2>Tab 2</button> <button role=tab aria-selected=false aria-controls=panel-3 id=tab-3>Tab 3</button> </div> <div id=panel-1 role=tabpanel aria-labelledby=tab-1>Content 1</div> <div id=panel-2 role=tabpanel aria-labelledby=tab-2 hidden>Content 2</div> <div id=panel-3 role=tabpanel aria-labelledby=tab-3 hidden>Content 3</div>

CSS

  • Style active tab and hide inactive panels using [hidden] or a utility class.
  • Ensure keyboard focus styles and responsive layout. Example essentials:

css

[role=“tab”] { cursor: pointer; padding: 8px 12px; border: none; background: none; } [role=“tab”][aria-selected=“true”] { border-bottom: 2px solid #007acc; font-weight: 600; } [role=“tabpanel”] { padding: 16px; } [role=“tabpanel”][hidden] { display: none; }

JavaScript behavior

  • Attach click handlers to tabs to set aria-selected, update panels’ hidden state, and move focus if needed.
  • Support keyboard navigation: Left/Right (or Up/Down) to switch tabs, Home/End to jump to first/last. Example minimal script:

js

const tabs = Array.from(document.querySelectorAll(’[role=“tab”]’)); const panels = Array.from(document.querySelectorAll(’[role=“tabpanel”]’)); function activateTab(tab) { tabs.forEach(t => t.setAttribute(‘aria-selected’,‘false’)); panels.forEach(p => p.hidden = true); tab.setAttribute(‘aria-selected’,‘true’); document.getElementById(tab.getAttribute(‘aria-controls’)).hidden = false; tab.focus(); } tabs.forEach(tab => { tab.addEventListener(‘click’, () => activateTab(tab)); tab.addEventListener(‘keydown’, (e) => { let idx = tabs.indexOf(tab); if (e.key === ‘ArrowRight’) idx = (idx + 1) % tabs.length; else if (e.key === ‘ArrowLeft’) idx = (idx - 1 + tabs.length) % tabs.length; else if (e.key === ‘Home’) idx = 0; else if (e.key === ‘End’) idx = tabs.length - 1; else return; e.preventDefault(); activateTab(tabs[idx]); }); });

Accessibility notes

  • Use role=“tablist”, role=“tab”, role=“tabpanel”, aria-selected, aria-controls, aria-labelledby.
  • Keep tabs as buttons (not links) so they are operable by keyboard.
  • Manage focus and announce changes by ensuring panels are in DOM and shown/hidden with hidden, not removed.

Variations & enhancements

  • Animate panel transitions with CSS (height/fade).
  • Deep-linking: update URL hash on tab change and restore on load.
  • Lazy-load panel content when first activated for performance.
  • Make it responsive: convert to accordion pattern on small screens if needed.

Quick checklist

  • Semantic HTML roles and attributes ✔
  • Keyboard navigation implemented ✔
  • Visible focus states ✔
  • Inactive panels hidden (use hidden attribute) ✔
  • Test with screen readers ✔

If you want, I can generate a complete ready-to-copy example (HTML/CSS/JS) tailored for responsive behavior or with animations.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *