Responsive Site Header
Create accessible responsive headers that adapt from desktop navigation to mobile menu while maintaining keyboard and screen reader accessibility.
The Bad (Inaccessible)
<div class="header">
<div class="logo" onclick="goHome()">Brand</div>
<nav class="desktop-nav">
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
<div class="mobile-toggle" onclick="toggleMobile()">☰</div>
</div>
Accessibility-Ready Code
<!-- Gold Standard: Semantic header with skip links -->
<header class="site-header">
<a href="#main-content" class="skip-link">Skip to content</a>
<div class="header-container">
<!-- Logo -->
<a href="/" class="logo" aria-label="Company Home">
<img src="/logo.svg" alt="Company Logo" width="120" height="40">
</a>
<!-- Desktop Navigation -->
<nav class="desktop-nav" aria-label="Main navigation">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/services">Services</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
<!-- Mobile Toggle -->
<button
type="button"
class="mobile-toggle"
aria-expanded="false"
aria-controls="mobile-nav"
aria-label="Open menu"
>
<span class="hamburger" aria-hidden="true">
<span></span>
<span></span>
<span></span>
</span>
</button>
</div>
<!-- Mobile Navigation -->
<div id="mobile-nav" class="mobile-nav-panel" hidden>
<nav aria-label="Mobile navigation">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/services">Services</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
</div>
</header>
<main id="main-content">
<h1>Welcome</h1>
</main>
<style>
.skip-link {
position: absolute;
top: -40px;
left: 0;
background: #000;
color: #fff;
padding: 8px;
}
.skip-link:focus {
top: 0;
}
@media (max-width: 768px) {
.desktop-nav {
display: none;
}
.mobile-toggle {
display: flex;
min-width: 44px;
min-height: 44px;
}
}
</style>
// Gold Standard: Semantic header with skip links
header.site-header
a.skip-link(href="#main-content") Skip to content
.header-container
// Logo
a.logo(href="/" aria-label="Company Home")
img(src="/logo.svg" alt="Company Logo" width="120" height="40")
// Desktop Navigation
nav.desktop-nav(aria-label="Main navigation")
ul
li
a(href="/") Home
li
a(href="/about") About
li
a(href="/services") Services
li
a(href="/contact") Contact
// Mobile Toggle
button(
type="button"
class="mobile-toggle"
aria-expanded="false"
aria-controls="mobile-nav"
aria-label="Open menu"
)
span.hamburger(aria-hidden="true")
span
span
span
// Mobile Navigation
div#mobile-nav.mobile-nav-panel(hidden)
nav(aria-label="Mobile navigation")
ul
li
a(href="/") Home
li
a(href="/about") About
li
a(href="/services") Services
li
a(href="/contact") Contact
main#main-content
h1 Welcome
const Header = () => {
const [isMobile, setIsMobile] = useState(false);
const [isMenuOpen, setIsMenuOpen] = useState(false);
return (
<header className="site-header">
<a href="#main-content" className="skip-link">Skip to content</a>
<div className="header-container">
<a href="/" className="logo" aria-label="Company Home">
<img src="/logo.svg" alt="Company Logo" width="120" height="40" />
</a>
<nav className="desktop-nav" aria-label="Main navigation">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/services">Services</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
{isMobile && (
<button
type="button"
className="mobile-toggle"
aria-expanded={isMenuOpen}
aria-controls="mobile-nav"
aria-label={isMenuOpen ? 'Close menu' : 'Open menu'}
onClick={() => setIsMenuOpen(!isMenuOpen)}
>
<span className="hamburger" aria-hidden="true">
<span></span>
<span></span>
<span></span>
</span>
</button>
)}
</div>
{isMobile && (
<div id="mobile-nav" className="mobile-nav-panel" hidden={!isMenuOpen}>
<nav aria-label="Mobile navigation">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/services">Services</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
</div>
)}
</header>
);
};
<template>
<header class="site-header">
<a href="#main-content" class="skip-link">Skip to content</a>
<div class="header-container">
<a href="/" class="logo" aria-label="Company Home">
<img src="/logo.svg" alt="Company Logo" width="120" height="40" />
</a>
<nav class="desktop-nav" aria-label="Main navigation">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/services">Services</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
<button
v-if="isMobile"
type="button"
class="mobile-toggle"
:aria-expanded="isMenuOpen"
aria-controls="mobile-nav"
:aria-label="isMenuOpen ? 'Close menu' : 'Open menu'"
@click="isMenuOpen = !isMenuOpen"
>
<span class="hamburger" aria-hidden="true">
<span></span>
<span></span>
<span></span>
</span>
</button>
</div>
<div v-if="isMobile" id="mobile-nav" class="mobile-nav-panel" v-show="isMenuOpen">
<nav aria-label="Mobile navigation">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/services">Services</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
</div>
</header>
</template>
<header className="sticky top-0 bg-white border-b">
<a href="#main-content" className="absolute -top-10 left-0 bg-black text-white px-4 py-2 focus:top-0">
Skip to content
</a>
<div className="flex items-center justify-between px-8 py-4 max-w-7xl mx-auto">
<a href="/" className="logo" aria-label="Company Home">
<img src="/logo.svg" alt="Company Logo" width="120" height="40" />
</a>
<nav className="hidden md:block" aria-label="Main navigation">
<ul className="flex space-x-8">
<li><a href="/" className="hover:text-blue-600">Home</a></li>
<li><a href="/about" className="hover:text-blue-600">About</a></li>
<li><a href="/services" className="hover:text-blue-600">Services</a></li>
<li><a href="/contact" className="hover:text-blue-600">Contact</a></li>
</ul>
</nav>
<button
type="button"
className="md:hidden min-w-[44px] min-h-[44px]"
:aria-expanded="isMenuOpen"
aria-controls="mobile-nav"
:aria-label="isMenuOpen ? 'Close menu' : 'Open menu'"
@click="isMenuOpen = !isMenuOpen"
>
<span aria-hidden="true" className="block space-y-1">
<span className="block w-6 h-0.5 bg-current"></span>
<span className="block w-6 h-0.5 bg-current"></span>
<span className="block w-6 h-0.5 bg-current"></span>
</span>
</button>
</div>
<div v-show="isMenuOpen" className="md:hidden bg-white border-t">
<nav aria-label="Mobile navigation">
<ul className="p-4 space-y-3">
<li><a href="/" className="block py-2">Home</a></li>
<li><a href="/about" className="block py-2">About</a></li>
<li><a href="/services" className="block py-2">Services</a></li>
<li><a href="/contact" className="block py-2">Contact</a></li>
</ul>
</nav>
</div>
</header>
The Standard
Responsive headers must maintain accessibility across all viewport sizes. Use semantic HTML, skip links, and proper ARIA attributes for both desktop and mobile views.
WCAG Criteria
- 1.3.1 Info and Relationships: Semantic HTML structure.
- 2.4.1 Bypass Blocks: Skip links to main content.
- 3.3.2 Labels or Instructions: Clear labels for controls.
❌ The Bad (Inaccessible)
What’s Wrong?
- Using
divfor header: No landmark for screen readers. onclickon logo: Not keyboard accessible.- No skip links: Keyboard users must tab through navigation every time.
- Missing navigation labels: Screen readers can’t identify purpose.
✅ The Good (Accessibility-Ready Code)
Why This Works
- Semantic
<header>: Creates a banner landmark for screen readers. - Skip Link: Lets keyboard users bypass navigation.
- Proper Logo Link: Uses
<a>instead ofonclick. - Labeled Navigation:
aria-labelidentifies each nav’s purpose.
Accessibility Checklist
- Use
<header role="banner">for the site header. - Include skip link as first focusable element.
- Use
<a>for logo, notonclick. - Add
aria-label="Main navigation"to desktop nav. - Add
aria-label="Mobile navigation"to mobile nav. - Update mobile toggle
aria-expandeddynamically. - Ensure mobile toggle is 44×44px minimum.
<header class="site-header">
<a href="#main" class="skip-link">Skip to content</a>
<a href="/" aria-label="Company Home">
<img src="/logo.svg" alt="Company Logo">
</a>
<nav aria-label="Main navigation">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
<button
aria-expanded="false"
aria-controls="mobile-menu"
aria-label="Open menu"
>
☰
</button>
<div id="mobile-menu" hidden>
<nav aria-label="Mobile navigation">
<!-- mobile links -->
</nav>
</div>
</header>
<main id="main">
<h1>Welcome</h1>
</main>
<style>
.skip-link {
position: absolute;
top: -40px;
}
.skip-link:focus {
top: 0;
}
</style>
Technical Deep Dive
Screen Reader Announcements
- NVDA: “Banner landmark, navigation landmark, Main navigation”
- VoiceOver: “Navigation, Main navigation”
- JAWS: “Banner region, Main navigation, navigation”
Best Practice: Semantic Landmarks
Use native HTML5 elements (header, nav, main) instead of divs with ARIA roles. The semantic elements provide landmark regions automatically, helping screen reader users navigate quickly. Only use role="banner" and role="navigation" for backwards compatibility or when working with non-semantic markup.
Interactive Behavioral Lab
Interactive Sandbox
🔬 Technical Internals
Understand how this component is processed by the browser and Assistive Technology (AT). This section bridges the gap between visual code and the hidden logic that powers accessibility.
🌲 Accessibility Tree
The data structure used by screen readers to "see" your page. It translates HTML roles and attributes into standardized objects.
⚙️ Event Logic
Expected behavioral standards for keyboard navigation and state transitions. Crucial for users who don't use a mouse.
- Focus: Highlights via
:focus-visible - Activation: Responds to
EnterandSpace - Role: Identified as
Landmark