How to Make a Website Accessible: A Practical Step-by-Step Guide
How to Make a Website Accessible
Over 1.3 billion people worldwide live with some form of disability. That's roughly 16% of the global population. When your website isn't accessible, you're locking out a significant portion of your potential users. And in many jurisdictions, you're also breaking the law.
But here's the thing: making a website accessible isn't as hard as most developers think. The basics cover the majority of issues, and you can implement them incrementally without rewriting your entire codebase.
This guide walks through the practical steps to make your website WCAG AA compliant, with real code examples and testing tools you can use today.
What Does "Accessible" Actually Mean?
An accessible website works for everyone, including people who:
- Can't see the screen and use screen readers (JAWS, NVDA, VoiceOver)
- Can't use a mouse and navigate entirely with a keyboard
- Have low vision and need high contrast or magnification
- Are color blind and can't distinguish certain color combinations
- Have motor disabilities and use switch devices or voice control
- Have cognitive disabilities and need clear, simple interfaces
The standard is WCAG 2.1 AA, which defines specific, testable criteria your website needs to meet. AA is the level most legal requirements reference. AAA is stricter and rarely required.
Step 1: Fix Your HTML Structure
This is the highest-impact change you can make. Proper semantic HTML gives screen readers the information they need to make your site navigable.
Use Semantic Elements
Stop using <div> for everything. Semantic elements tell assistive technology what each section of your page does:
<!-- Bad: screen readers see a wall of divs -->
<div class="header">
<div class="nav">
<div class="nav-item">Home</div>
</div>
</div>
<div class="content">
<div class="article">...</div>
</div>
<!-- Good: screen readers understand the structure -->
<header>
<nav aria-label="Main navigation">
<a href="/">Home</a>
</nav>
</header>
<main>
<article>...</article>
</main>
The key semantic elements to use:
<header>for page and section headers<nav>for navigation (addaria-labelwhen you have multiple navs)<main>for the primary content (one per page)<article>for self-contained content<aside>for sidebar content<footer>for page and section footers<section>for thematic groupings (with a heading)
Use Proper Heading Hierarchy
Headings (<h1> through <h6>) create a document outline that screen reader users navigate like a table of contents. Don't skip levels:
<!-- Bad: skips from h1 to h4 -->
<h1>Page Title</h1>
<h4>Some Section</h4>
<!-- Good: logical hierarchy -->
<h1>Page Title</h1>
<h2>Section</h2>
<h3>Subsection</h3>
Use one <h1> per page. Screen reader users jump between headings to find content, so meaningful heading text matters more than you think.
Step 2: Make Everything Keyboard Accessible
Not everyone uses a mouse. Some people navigate entirely with Tab, Enter, Space, and arrow keys. Your website needs to support this.
The Tab Order Test
Press Tab on your website and watch where the focus goes. It should move through interactive elements in a logical order, matching the visual layout. If the focus jumps around randomly or skips important elements, you have a problem.
Common fixes:
<!-- Make sure interactive elements are focusable -->
<button>Submit</button> <!-- Naturally focusable -->
<a href="/page">Link</a> <!-- Naturally focusable -->
<!-- Don't use divs as buttons -->
<div onclick="submit()">Submit</div> <!-- NOT focusable by keyboard -->
<!-- If you must use a div, add proper attributes -->
<div role="button" tabindex="0" onkeydown="handleKeyDown(e)">Submit</div>
<!-- But really, just use a <button> -->
Focus Visibility
When a user tabs to an element, they need to see where they are. Never remove focus outlines without providing an alternative:
/* Bad: removes focus visibility entirely */
*:focus {
outline: none;
}
/* Good: custom focus style that's clearly visible */
*:focus-visible {
outline: 2px solid #2563eb;
outline-offset: 2px;
}
The :focus-visible selector is better than :focus because it only shows the outline for keyboard navigation, not for mouse clicks.
Keyboard Shortcuts for Complex Components
Dropdowns, modals, tabs, and other complex UI patterns need specific keyboard interactions. Here's what users expect:
- Modals: Escape closes them. Focus is trapped inside (Tab doesn't escape to the background). Focus returns to the trigger when closed.
- Dropdowns: Arrow keys navigate options. Enter selects. Escape closes.
- Tabs: Arrow keys switch tabs. Tab moves focus out of the tab group.
- Accordions: Enter/Space toggles. Optionally, arrow keys move between headers.
For a detailed reference, see our keyboard navigation patterns guide.
Step 3: Add Alt Text to Images
Every <img> needs an alt attribute. But what goes in it depends on the image's purpose:
Informative images need descriptive alt text:
<img src="chart.png" alt="Bar chart showing 47% increase in signups after redesign">
Decorative images get empty alt text (so screen readers skip them):
<img src="decorative-line.svg" alt="">
Images with text need alt text that includes the text content:
<img src="sale-banner.jpg" alt="Summer sale: 30% off all plans through August">
Complex images (charts, diagrams) need extended descriptions:
<figure>
<img src="architecture.png" alt="System architecture diagram">
<figcaption>
The system uses three microservices: auth, payments, and notifications.
Auth handles login and token management. Payments processes Stripe webhooks.
Notifications sends emails via SendGrid.
</figcaption>
</figure>
Step 4: Get Color Contrast Right
WCAG AA requires a contrast ratio of at least 4.5:1 for normal text and 3:1 for large text (18px bold or 24px regular).
Common failures:
- Light gray text on white backgrounds (#999 on #fff = 2.85:1, fails)
- Placeholder text that's too faint
- Button text on colored backgrounds
- Links that aren't distinguishable from surrounding text
Tools to check contrast:
- Chrome DevTools: Inspect an element, and the color picker shows the contrast ratio
- WebAIM Contrast Checker: Paste two hex codes and get the ratio
- Stark (Figma plugin): Check contrast directly in your designs
If you're using a design token system, you can enforce contrast at the token level. Our guide on design tokens explains how to set this up so contrast compliance is built into your color palette from the start.
Step 5: Make Forms Accessible
Forms are where most accessibility failures happen on the web. Every input needs a visible label, clear error messages, and logical tab order.
Labels
Every form input needs a <label> connected by for/id:
<!-- Bad: placeholder as the only label -->
<input type="email" placeholder="Enter your email">
<!-- Good: visible label connected to input -->
<label for="email">Email address</label>
<input type="email" id="email" placeholder="you@example.com">
Placeholders are not labels. They disappear when you start typing, which makes it impossible to verify what the field was asking for.
Error Messages
When validation fails, tell the user exactly what's wrong and connect the error to the field:
<label for="password">Password</label>
<input
type="password"
id="password"
aria-describedby="password-error"
aria-invalid="true"
>
<p id="password-error" role="alert">
Password must be at least 8 characters
</p>
The aria-describedby attribute links the error message to the input so screen readers announce it. The role="alert" makes the message announced immediately when it appears.
For more patterns, see our guide on form validation patterns that don't frustrate users.
Required Fields
Mark required fields with both visual and programmatic indicators:
<label for="name">
Full name <span aria-hidden="true">*</span>
<span class="sr-only">(required)</span>
</label>
<input type="text" id="name" required aria-required="true">
Step 6: Handle Dynamic Content
Single-page apps and dynamic content updates need special attention. When content changes without a page reload, screen readers might not know about it.
Live Regions
Use ARIA live regions to announce dynamic changes:
<!-- Polite: waits for the screen reader to finish current content -->
<div aria-live="polite" aria-atomic="true">
3 items in your cart
</div>
<!-- Assertive: interrupts immediately (use sparingly) -->
<div aria-live="assertive">
Error: payment failed. Please try again.
</div>
Route Changes in SPAs
In React with Next.js, route changes don't trigger a page load, so screen readers don't announce the new page. Manage focus on navigation:
// Announce page changes to screen readers
useEffect(() => {
const heading = document.querySelector('h1');
if (heading) {
heading.focus();
}
}, [pathname]);
Step 7: Add ARIA Where Needed (But Don't Overdo It)
ARIA attributes fill gaps that HTML semantics can't cover. But the first rule of ARIA is: don't use ARIA if a native HTML element does the job.
<!-- Don't do this -->
<div role="button" tabindex="0" aria-label="Submit form">Submit</div>
<!-- Do this instead -->
<button>Submit form</button>
Where ARIA is genuinely useful:
<!-- Icon-only buttons need labels -->
<button aria-label="Close modal">
<svg>...</svg>
</button>
<!-- Custom components need roles -->
<div role="tablist">
<button role="tab" aria-selected="true" aria-controls="panel-1">Tab 1</button>
<button role="tab" aria-selected="false" aria-controls="panel-2">Tab 2</button>
</div>
<!-- State communication -->
<button aria-expanded="false" aria-controls="dropdown-menu">
Options
</button>
For a complete reference of ARIA attributes in React, check our ARIA attributes cheat sheet.
Step 8: Test Your Work
You can't know if your website is accessible without testing it. Here's a practical testing workflow:
Automated Testing (Catches ~30% of issues)
Run these tools regularly:
- axe DevTools (Chrome extension): Scans the current page for WCAG violations
- Lighthouse Accessibility audit: Built into Chrome DevTools
- eslint-plugin-jsx-a11y: Catches React accessibility issues during development
Manual Testing (Catches ~70% of issues)
- Keyboard-only navigation: Unplug your mouse and navigate your entire site with just the keyboard
- Screen reader testing: Use VoiceOver (Mac) or NVDA (Windows) to navigate your site
- Zoom to 200%: Your layout should still work at 200% browser zoom
- Color contrast check: Run every text/background combination through a contrast checker
The Quick Test Checklist
Run through this on every page:
- Can you reach every interactive element with Tab?
- Is the focus indicator visible on every focused element?
- Do all images have appropriate alt text?
- Does every form input have a visible label?
- Are error messages announced by screen readers?
- Does the heading hierarchy make sense?
- Is the color contrast at least 4.5:1 for text?
- Can you use the page at 200% zoom?
- Do modals trap focus and close with Escape?
Start With What Matters Most
You don't have to fix everything at once. Start with the highest-impact issues:
- Semantic HTML and heading structure (affects all screen reader users)
- Keyboard navigation (affects anyone who can't use a mouse)
- Color contrast (affects low-vision users and everyone in bright sunlight)
- Form labels and errors (affects everyone, but especially assistive tech users)
- Alt text (affects screen reader users)
If you're building a new project, consider starting with a foundation that handles accessibility from the start. The thefrontkit SaaS Starter Kit ships with WCAG AA compliance built into every component, so you don't have to retrofit it later. The E-commerce Starter Kit does the same for online stores, covering the complex accessibility patterns in product grids, checkout flows, and account management.
Building accessibility in from the start is always cheaper than fixing it after launch. Our guide on why accessibility should be your first priority makes the business case in detail.
FAQ
How much does it cost to make a website accessible? For a new project, the cost is close to zero if you build with accessible components from the start. Using semantic HTML, proper labels, and good contrast doesn't take more time than doing it wrong. Retrofitting an existing site costs more because you're touching code that's already built and tested. Starting with accessible UI kits like thefrontkit eliminates this cost entirely.
Is WCAG compliance legally required? In many places, yes. The ADA (US), EAA (EU), and similar laws in Canada, UK, and Australia require websites to be accessible. The specific standard varies, but WCAG 2.1 AA is the most commonly referenced benchmark. E-commerce sites, government sites, and any site receiving federal funding face stricter enforcement. Read more about WCAG 2.1 AA requirements.
What's the difference between WCAG A, AA, and AAA? A is the minimum (covers the most critical barriers). AA adds requirements for contrast, resize, and navigation (this is what most laws reference). AAA is the gold standard and includes requirements like sign language for video and 7:1 contrast ratios. Most organizations target AA. Our WCAG AA checklist breaks down every requirement.
Can automated tools catch all accessibility issues? No. Automated tools catch roughly 30% of WCAG issues, mainly things like missing alt text, contrast failures, and missing labels. The remaining 70% require manual testing: keyboard navigation flow, screen reader comprehension, focus management in dynamic content, and meaningful alt text (not just "image.jpg"). Automated tools are a good first pass, but manual testing is essential.
How do I make my React app accessible? Use semantic HTML elements instead of styled divs. Add proper labels to form inputs. Manage focus on route changes. Use aria attributes only when native HTML isn't sufficient. Install eslint-plugin-jsx-a11y to catch issues during development. Test with a screen reader. Libraries like Radix UI and React Aria provide accessible primitives you can build on. For a quick reference, see our ARIA attributes cheat sheet for React.
Where do I start if my existing website isn't accessible? Start with an audit using axe DevTools or Lighthouse to identify the biggest issues. Fix them in priority order: heading structure, keyboard navigation, color contrast, form labels, then alt text. Don't try to fix everything at once. Ship improvements incrementally. Even partial improvement helps real users.