Web Accessibility (WCAG) for Business Websites: A Practical Developer's Checklist
Here's an uncomfortable truth: roughly 15% of the world's population lives with some form of disability. If your business website isn't accessible, you're not just locking out potential customers — you're also exposing your organisation to legal risk, tanking your SEO, and shipping code that's objectively harder to maintain. Yet most development teams treat accessibility as a post-launch checkbox rather than a first-class engineering concern.
This article cuts through the abstract WCAG guidelines and gives you concrete, actionable patterns you can apply today, whether you're building a Laravel-powered monolith, a Livewire SPA, or a headless frontend.
What WCAG Actually Means (Without the Jargon)
The Web Content Accessibility Guidelines (WCAG), published by the W3C, are organised around four core principles — often abbreviated as POUR:
- Perceivable — information must be presentable in ways users can perceive
- Operable — UI components must be operable by all users
- Understandable — content and operation must be understandable
- Robust — content must be interpreted reliably by assistive technologies
WCAG has three conformance levels: A (minimum), AA (the legal and industry standard most businesses target), and AAA (enhanced). If a client asks for "WCAG compliance," they almost always mean WCAG 2.1 AA.
The Developer's Quick-Win Checklist
1. Semantic HTML First
The single highest-leverage accessibility improvement you can make costs nothing: stop using <div> for everything. Screen readers rely on semantic HTML to understand page structure.
<!-- ❌ Bad -->
<div class="btn" onclick="submitForm()">Submit</div>
<!-- ✅ Good -->
<button type="submit">Submit</button>
Use <nav>, <main>, <article>, <aside>, <header>, and <footer> as landmark regions. They're free, they cost zero bytes of JavaScript, and they immediately improve screen reader navigation.
2. ARIA — Use It Sparingly
ARIA (Accessible Rich Internet Applications) attributes exist to fill gaps where semantic HTML isn't enough — not to replace it. The first rule of ARIA is: don't use ARIA if a native HTML element can do the job.
That said, dynamic UI components like modals, tabs, and dropdowns genuinely need ARIA:
<!-- Modal dialog example -->
<div
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
aria-describedby="modal-description"
>
<h2 id="modal-title">Confirm Deletion</h2>
<p id="modal-description">This action cannot be undone.</p>
<button autofocus>Cancel</button>
<button>Delete</button>
</div>
When role="dialog" is active, autofocus should move keyboard focus inside the modal. When it closes, focus must return to the trigger element. Forgetting this is one of the most common keyboard-trap bugs in SPAs.
3. Colour Contrast — Automate the Check
WCAG 2.1 AA requires:
- 4.5:1 contrast ratio for normal text
- 3:1 for large text (18px+ regular or 14px+ bold)
- 3:1 for UI components and graphical objects
Don't eyeball this. Add axe-core to your test suite:
npm install --save-dev @axe-core/playwright
// playwright.spec.js
import { checkA11y, injectAxe } from 'axe-playwright';
test('homepage has no critical a11y violations', async ({ page }) => {
await page.goto('http://localhost:8000');
await injectAxe(page);
await checkA11y(page, null, {
includedImpacts: ['critical', 'serious'],
});
});
This catches contrast failures, missing alt text, unlabelled form fields, and dozens of other issues automatically on every CI run.
4. Keyboard Navigation and Focus Management
Every interactive element must be reachable and operable via keyboard alone. Test this yourself: unplug your mouse and try navigating your site with Tab, Shift+Tab, Enter, and arrow keys.
Never do this:
/* ❌ Destroys keyboard navigation for everyone */
*:focus {
outline: none;
}
Instead, style focus indicators to match your design system:
/* ✅ Visible, on-brand focus ring */
*:focus-visible {
outline: 2px solid #2563EB;
outline-offset: 3px;
border-radius: 4px;
}
The :focus-visible pseudo-class is key here — it shows the outline only for keyboard navigation, not mouse clicks, so you get accessibility without visual clutter.
5. Forms — The Most Common Failure Point
Forms are where accessibility falls apart most often. Every input needs a programmatically associated label — placeholder is not a substitute.
<!-- ❌ Placeholder-only, inaccessible -->
<input type="email" placeholder="Email address">
<!-- ✅ Properly labelled -->
<div>
<label for="email">Email address</label>
<input
type="email"
id="email"
name="email"
autocomplete="email"
aria-required="true"
aria-describedby="email-hint"
>
<span id="email-hint" class="text-sm text-gray-500">
We'll never share your email.
</span>
</div>
For inline validation errors, associate the error message with the field using aria-describedby and set aria-invalid="true" on the input when validation fails.
6. Images and Alt Text — Context Matters
The rule is simple but often misapplied:
- Informative images → descriptive alt text
-
Decorative images →
alt=""(empty string, not omitted) -
Functional images (e.g., logo linking to homepage) → describe the function:
alt="HanzWeb homepage"
<!-- Decorative divider image -->
<img src="/divider.svg" alt="">
<!-- Chart conveying data -->
<img
src="/q3-revenue-chart.png"
alt="Bar chart showing Q3 revenue up 23% year-over-year"
>
Testing Beyond Automated Tools
Automated scanners like axe-core catch roughly 30–40% of accessibility issues. The rest require manual testing:
- Screen reader testing — Use NVDA (Windows, free) or VoiceOver (macOS/iOS, built-in) to navigate your critical user journeys
- Zoom testing — Set browser zoom to 200% and verify content doesn't break or overlap (WCAG 1.4.4)
-
Reduced motion — Respect
prefers-reduced-motionfor users with vestibular disorders
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
Accessibility in the Laravel / TALL Stack Context
If you're building with Livewire, pay extra attention to dynamic content updates. When Livewire re-renders a component, focus can silently reset to the document body, which is disorienting for keyboard and screen reader users.
Use wire:ignore on elements that shouldn't be re-rendered, and dispatch focus-management events where needed. The team at https://hanzweb.ae integrates accessibility audits directly into the Livewire component development workflow, which surfaces these focus-trap issues during build rather than after launch.
For Alpine.js components, the Alpine Accessibility plugin provides trap and focus utilities that handle modal and dropdown patterns correctly out of the box.
Conclusion
Accessibility isn't a niche concern or a legal box to tick — it's a measure of how well you actually built something. The patterns above — semantic HTML, proper ARIA usage, colour contrast checks in CI, keyboard-navigable focus management, and correctly labelled forms — represent the 20% of effort that solves 80% of real-world WCAG failures.
Start by adding axe-core to your test pipeline this week. Run it against your three most-visited pages. Fix what it surfaces. Then do a keyboard-only walkthrough of your primary conversion flow. You'll be surprised how much you find — and how straightforward most of it is to fix.
Top comments (0)