Pages must have a mechanism to bypass repeated blocks
The page lacks a way to bypass repetitive content (headers, navigation, sidebars). Users must navigate through the same content on every page to reach the main content.
Rule Description
This rule ensures pages have a mechanism to bypass blocks of content that are repeated on multiple pages (navigation, headers, sidebars).
What This Rule Checks
- Skip link exists and is functional
- Skip link is keyboard accessible
- Target element exists and receives focus
- OR proper landmark structure exists
Valid Bypass Mechanisms
- Skip link - Link to jump to main content
- ARIA landmarks -
<main>,<nav>,<aside>, etc. - Heading structure - Proper heading hierarchy
- Expandable sections - Collapsible repeated content
Why It Matters
Impact on Users
- Keyboard users must tab through dozens of links to reach content
- Screen reader users hear the same navigation on every page
- Switch device users experience extreme frustration and fatigue
- All users with motor disabilities waste time and energy
Real-World Scenario
A user navigating with a keyboard visits a news site with 50 navigation links in the header. On the first page, they tab through all 50 links to read an article. When they click to the next article, they must tab through the same 50 links again. After 10 articles, they've tabbed through 500 navigation links just to read content. They give up and leave the site.
How to Fix
Solution 1: Skip to Main Content Link
Add a skip link as the first focusable element.
Bad Example:
<!-- FAIL - No skip link --> <!DOCTYPE html> <html> <head> <title>Page</title> </head> <body> <header> <nav> <!-- 50 navigation links --> </nav> </header> <main> <h1>Article Title</h1> <!-- content --> </main> </body> </html>
Good Example:
<!-- PASS - Skip link as first element --> <!DOCTYPE html> <html> <head> <title>Page</title> <style> .skip-link { position: absolute; top: -40px; left: 0; background: #000; color: white; padding: 8px; text-decoration: none; z-index: 100; } .skip-link:focus { top: 0; } </style> </head> <body> <a href="#main-content" class="skip-link"> Skip to main content </a> <header> <nav> <!-- Navigation links --> </nav> </header> <main id="main-content" tabindex="-1"> <h1>Article Title</h1> <!-- content --> </main> </body> </html>
Solution 2: Multiple Skip Links
Provide options to skip to different sections.
Good Example:
<nav class="skip-links"> <a href="#main-content" class="skip-link">Skip to main content</a> <a href="#navigation" class="skip-link">Skip to navigation</a> <a href="#search" class="skip-link">Skip to search</a> <a href="#footer" class="skip-link">Skip to footer</a> </nav> <style> .skip-links { position: absolute; top: -100px; left: 0; z-index: 1000; } .skip-link { position: absolute; padding: 8px 12px; background: #000; color: #fff; text-decoration: none; border: 1px solid #fff; } .skip-link:focus { position: static; display: block; } </style> <header> <div id="search"> <input type="search" placeholder="Search"> </div> <nav id="navigation"> <!-- Navigation --> </nav> </header> <main id="main-content" tabindex="-1"> <h1>Content</h1> </main> <footer id="footer"> <!-- Footer --> </footer>
Solution 3: Proper Landmark Regions
Use HTML5 landmarks to enable AT navigation.
Bad Example:
<!-- FAIL - No semantic structure --> <div class="header"> <div class="nav">...</div> </div> <div class="content">...</div> <div class="sidebar">...</div> <div class="footer">...</div>
Good Example:
<!-- PASS - Semantic landmarks --> <a href="#main" class="skip-link">Skip to main content</a> <header> <nav aria-label="Main navigation"> <!-- Main nav --> </nav> </header> <main id="main" tabindex="-1"> <h1>Page Title</h1> <!-- Main content --> </main> <aside aria-label="Related articles"> <!-- Sidebar --> </aside> <footer> <!-- Footer --> </footer>
Solution 4: Framework Implementations
React:
// components/SkipLink.jsx export function SkipLink({ targetId = "main-content", children = "Skip to main content" }) { return ( <a href={`#${targetId}`} className="skip-link"> {children} </a> ); } // app/layout.jsx export default function Layout({ children }) { return ( <html lang="en"> <body> <SkipLink /> <Header /> <main id="main-content" tabIndex={-1}> {children} </main> <Footer /> </body> </html> ); } // styles/skip-link.css .skip-link { position: absolute; top: -40px; left: 0; background: #000; color: white; padding: 8px; text-decoration: none; z-index: 100; } .skip-link:focus { top: 0; }
Next.js:
// components/Layout.js import Link from 'next/link'; export default function Layout({ children }) { return ( <> <a href="#main" className="skip-link"> Skip to main content </a> <header> <nav> {/* Navigation */} </nav> </header> <main id="main" tabIndex={-1}> {children} </main> </> ); }
Vue:
<!-- components/SkipLink.vue --> <template> <a :href="`#${targetId}`" class="skip-link"> {{ text }} </a> </template> <script setup> defineProps({ targetId: { type: String, default: 'main-content' }, text: { type: String, default: 'Skip to main content' } }); </script> <!-- App.vue --> <template> <div id="app"> <SkipLink /> <AppHeader /> <main id="main-content" tabindex="-1"> <router-view /> </main> <AppFooter /> </div> </template>
Solution 5: Advanced Skip Link Patterns
Multiple regions with headings:
<nav class="skip-navigation" aria-label="Skip links"> <ul> <li><a href="#main">Skip to main content</a></li> <li><a href="#nav">Skip to navigation</a></li> <li><a href="#sidebar">Skip to sidebar</a></li> </ul> </nav> <style> .skip-navigation { position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden; } .skip-navigation:focus-within { position: static; width: auto; height: auto; background: #000; color: white; padding: 1rem; } .skip-navigation a:focus { outline: 2px solid white; outline-offset: 2px; } </style>
Always visible skip link:
<a href="#main" class="skip-link-visible"> Skip to content </a> <style> .skip-link-visible { display: inline-block; padding: 8px 12px; background: #0066cc; color: white; text-decoration: none; margin: 10px; } .skip-link-visible:focus { outline: 3px solid #ffbf47; outline-offset: 0; background: #003078; } </style>
Common Mistakes
1. Hidden Skip Link Not Focusable
<!-- FAIL - Skip link completely hidden --> <a href="#main" style="display: none;">Skip</a> <!-- PASS - Hidden but reveals on focus --> <a href="#main" class="skip-link">Skip to main</a> <style> .skip-link { position: absolute; top: -40px; } .skip-link:focus { top: 0; } </style>
2. Target Doesn't Exist
<!-- FAIL - href target doesn't exist --> <a href="#main-content">Skip to main</a> <div id="content"><!-- Wrong ID --></div> <!-- PASS - Matching IDs --> <a href="#main-content">Skip to main</a> <main id="main-content">Content</main>
3. Target Not Focusable
<!-- FAIL - Target can't receive focus --> <a href="#main">Skip</a> <main id="main">Content</main> <!-- PASS - Target can receive focus --> <a href="#main">Skip</a> <main id="main" tabindex="-1">Content</main>
4. Only Decorative Landmarks
<!-- FAIL - Landmarks present but no skip link --> <!-- (Some users may not know about landmark navigation) --> <header>...</header> <main>...</main> <!-- PASS - Both skip link AND landmarks --> <a href="#main" class="skip-link">Skip to main</a> <header>...</header> <main id="main" tabindex="-1">...</main>
Testing
Manual Testing
- Press Tab as first action on page
- Verify skip link becomes visible and focused
- Press Enter to activate skip link
- Verify focus moves to main content
- Continue tabbing - should be in main content area
Keyboard Testing
1. Load page
2. Press Tab → Skip link appears
3. Press Enter → Focus moves to main
4. Press Tab → Focus on first interactive element in main
Screen Reader Testing
NVDA/JAWS:
1. Load page
2. Press Tab → "Skip to main content, link"
3. Press Enter → Focus moves
4. Press H → First heading in main content
Expected: Quick access to main content
Automated Testing
// Check for skip link const skipLink = document.querySelector('a[href^="#"]'); if (skipLink) { const targetId = skipLink.getAttribute('href').slice(1); const target = document.getElementById(targetId); if (!target) { console.error('Skip link target does not exist:', targetId); } else if (!target.hasAttribute('tabindex')) { console.warn('Skip link target should have tabindex="-1":', target); } } else { // Check for landmarks as alternative const main = document.querySelector('main'); if (!main) { console.error('No skip link and no <main> landmark found'); } } // Using axe-core const results = await axe.run(); const violations = results.violations.filter( v => v.id === 'bypass' );
External Resources
Automate Your Accessibility Testing
Our tool automatically checks for this rule and hundreds of other accessibility issues.
Start Your Free Trial