Building Accessible Websites: WCAG 2.1 Compliance Without the Headaches
Here's a truth that took our industry too long to accept: accessibility isn't a feature. It's a fundamental aspect of quality web development. Yet many teams treat it as an afterthought, a compliance checkbox to tick before launch, or worse, a "nice to have" that never makes the roadmap.
The reality? Building accessible websites from the start is easier, cheaper, and more effective than retrofitting later. And the benefits extend far beyond users with disabilities. Accessible design improves the experience for everyone.
Let's cut through the jargon and show you how to build WCAG 2.1 compliant websites without the usual headaches.
What WCAG 2.1 Actually Requires
The Web Content Accessibility Guidelines (WCAG) 2.1 organizes requirements into four principles, often called POUR:
- Perceivable: Information must be presentable in ways users can perceive
- Operable: Interface components must be operable by all users
- Understandable: Information and UI operation must be understandable
- Robust: Content must be robust enough for diverse user agents
Within these principles, there are three conformance levels:
- Level A: Minimum accessibility (basic requirements)
- Level AA: Standard accessibility (most common legal requirement)
- Level AAA: Enhanced accessibility (highest standard)
For most websites and applications, Level AA is the target. It's what ADA lawsuits reference, what government contracts require, and what provides meaningful access to users with disabilities.
The High-Impact Fundamentals
Rather than listing all 50+ WCAG criteria, let's focus on the changes that provide the biggest impact with the least effort.
Semantic HTML: Your First Line of Defense
Before reaching for ARIA attributes, master semantic HTML. Screen readers and assistive technologies understand native HTML elements. Use them correctly, and you're already halfway to compliance.
<!-- Bad: Divs for everything -->
<div class="header">
<div class="nav">
<div class="nav-item" onclick="navigate('/')">Home</div>
</div>
</div>
<!-- Good: Semantic elements -->
<header>
<nav aria-label="Main navigation">
<a href="/">Home</a>
</nav>
</header>
Key semantic elements to use:
| Element | Purpose |
|---------|---------|
| <header> | Introductory content, navigation |
| <nav> | Navigation sections |
| <main> | Primary content of the page |
| <article> | Self-contained compositions |
| <section> | Thematic groupings |
| <aside> | Tangentially related content |
| <footer> | Footer information |
| <button> | Clickable actions |
| <a> | Navigation links |
Heading Hierarchy: Structure Matters
Screen reader users often navigate by headings. A logical heading structure acts as a table of contents for your page.
<!-- Bad: Skipping levels, using headings for styling -->
<h1>Welcome</h1>
<h4>Our Services</h4> <!-- Skipped h2, h3 -->
<h2>Contact Us</h2> <!-- Inconsistent -->
<!-- Good: Logical hierarchy -->
<h1>Welcome to Our Platform</h1>
<h2>Our Services</h2>
<h3>Web Development</h3>
<h3>Design Services</h3>
<h2>Contact Us</h2>
Rules for headings:
- One
<h1>per page (your main topic) - Don't skip levels (h1 → h2 → h3, not h1 → h3)
- Use headings for structure, not styling
- Keep headings descriptive and concise
Color Contrast: The Numbers Game
WCAG 2.1 requires minimum contrast ratios between text and backgrounds:
- Normal text: 4.5:1 ratio (Level AA)
- Large text (18pt+ or 14pt+ bold): 3:1 ratio
- UI components and graphics: 3:1 ratio
Testing tools make this easy:
/* Fails contrast (2.5:1) */
.low-contrast {
color: #999999;
background: #ffffff;
}
/* Passes contrast (7:1) */
.high-contrast {
color: #595959;
background: #ffffff;
}
Recommended tools:
- WebAIM Contrast Checker
- Chrome DevTools (Accessibility panel)
- Figma plugins (Stark, Contrast)
Keyboard Navigation: No Mouse Required
Every interactive element must be accessible via keyboard. This is non-negotiable for users who can't use a mouse.
The basics:
Tabmoves forward through focusable elementsShift + Tabmoves backwardEnteractivates buttons and linksSpaceactivates buttons and checkboxes- Arrow keys navigate within components (menus, tabs, etc.)
// Bad: Click-only interaction
<div className="card" onClick={handleSelect}>
Select this option
</div>
// Good: Keyboard accessible
<button
className="card"
onClick={handleSelect}
onKeyDown={(e) => e.key === 'Enter' && handleSelect()}
>
Select this option
</button>
// Best: Native button handles keyboard automatically
<button className="card" onClick={handleSelect}>
Select this option
</button>
Focus indicators are mandatory. Never do this:
/* Accessibility violation */
*:focus {
outline: none;
}
Instead, create visible, attractive focus states:
/* Custom focus indicator */
button:focus-visible {
outline: 2px solid var(--brand-cyan);
outline-offset: 2px;
}
Images and Alt Text: Context is Everything
Every image needs alternative text, but what you write matters:
<!-- Bad: Missing or useless -->
<img src="chart.png">
<img src="chart.png" alt="chart">
<img src="chart.png" alt="image">
<!-- Good: Descriptive -->
<img src="chart.png" alt="Bar chart showing 40% increase in Q4 sales">
<!-- Decorative images: empty alt -->
<img src="decorative-swoosh.svg" alt="">
Guidelines for alt text:
- Informative images: Describe the content and function
- Functional images: Describe the action (e.g., "Search" for a search icon button)
- Decorative images: Use empty alt (
alt="") - Complex images: Provide detailed descriptions nearby or via
aria-describedby
Forms: Labels, Errors, and Instructions
Forms are accessibility minefields. Here's how to navigate them:
// Bad: No label association
<input type="email" placeholder="Email">
// Good: Explicit label association
<label htmlFor="email">Email address</label>
<input
type="email"
id="email"
name="email"
aria-describedby="email-hint"
aria-invalid={hasError}
aria-errormessage={hasError ? "email-error" : undefined}
/>
<span id="email-hint">We'll never share your email</span>
{hasError && (
<span id="email-error" role="alert">
Please enter a valid email address
</span>
)}
Form accessibility checklist:
- ✅ Every input has an associated label
- ✅ Required fields are indicated (not just by color)
- ✅ Error messages are announced to screen readers
- ✅ Error messages explain how to fix the problem
- ✅ Form can be completed with keyboard alone
ARIA: When HTML Isn't Enough
ARIA (Accessible Rich Internet Applications) fills gaps where native HTML falls short. But here's the golden rule: No ARIA is better than bad ARIA.
When to Use ARIA
Use ARIA when you're building custom components that don't exist in native HTML:
// Custom dropdown menu
<button
aria-haspopup="true"
aria-expanded={isOpen}
aria-controls="dropdown-menu"
>
Options
</button>
<ul
id="dropdown-menu"
role="menu"
aria-hidden={!isOpen}
>
<li role="menuitem">Edit</li>
<li role="menuitem">Delete</li>
</ul>
Common ARIA Patterns
Live regions for dynamic content:
// Announce status updates to screen readers
<div aria-live="polite" aria-atomic="true">
{statusMessage}
</div>
Modal dialogs:
<div
role="dialog"
aria-modal="true"
aria-labelledby="dialog-title"
aria-describedby="dialog-description"
>
<h2 id="dialog-title">Confirm Deletion</h2>
<p id="dialog-description">
Are you sure you want to delete this item?
</p>
</div>
Tab interfaces:
<div role="tablist" aria-label="Account settings">
<button role="tab" aria-selected="true" aria-controls="panel-1">
Profile
</button>
<button role="tab" aria-selected="false" aria-controls="panel-2">
Security
</button>
</div>
<div role="tabpanel" id="panel-1" aria-labelledby="tab-1">
Profile content...
</div>
Testing Your Accessibility
Automated testing catches roughly 30% of accessibility issues. The rest require manual testing.
Automated Tools
Run these on every build:
- axe-core: Industry standard engine (integrates with Jest, Playwright)
- Lighthouse: Built into Chrome DevTools
- eslint-plugin-jsx-a11y: Catch issues in your React code
// Example: axe-core with Playwright
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
test('homepage should be accessible', async ({ page }) => {
await page.goto('/');
const results = await new AxeBuilder({ page }).analyze();
expect(results.violations).toEqual([]);
});
Manual Testing
Keyboard testing (5 minutes):
- Unplug your mouse
- Tab through the entire page
- Can you reach everything? Can you see where you are?
- Can you operate all controls?
- Can you escape from modals and menus?
Screen reader testing (15 minutes):
- macOS: VoiceOver (built-in, Cmd + F5)
- Windows: NVDA (free) or JAWS
- Browser: ChromeVox extension
Quick checks:
- [ ] Zoom to 200%. Does the layout break?
- [ ] Turn off CSS. Is the content still logical?
- [ ] Check with grayscale filter. Is meaning conveyed beyond color?
The Business Case for Accessibility
Beyond legal compliance, accessibility makes business sense:
Expanded Market
- 15% of the world's population has some form of disability
- 100% of users benefit from accessible design (temporary injuries, situational limitations, aging)
Legal Protection
Accessibility lawsuits have increased 300% since 2018. Proactive compliance is far cheaper than reactive legal defense.
SEO Benefits
Accessible websites tend to have:
- Better heading structure
- More descriptive content
- Proper image alt text
- Cleaner HTML
All factors that search engines reward.
Better Code Quality
Accessibility requirements push you toward:
- Semantic HTML (more maintainable)
- Consistent UI patterns (better UX)
- Proper state management (fewer bugs)
Quick Wins to Implement Today
- Run Lighthouse on your homepage and fix critical issues
- Add skip links for keyboard users to bypass navigation
- Check your color contrast with WebAIM's tool
- Test with keyboard only for 10 minutes
- Audit your forms for proper labels and error handling
Building Accessibility Into Your Process
At High Mountain Studio, accessibility isn't a phase. It's embedded in how we work. From design systems with built-in contrast ratios to component libraries with proper ARIA attributes, we build accessible foundations that make compliance natural, not painful.
If you're struggling with accessibility compliance or want to build your next project right from the start, let's talk. We'll help you create a website that works for everyone.


