Accessibility Case Study
A structured accessibility audit of labunknown.ca — identifying WCAG 2.1 AA failures through automated and manual methods, applying code fixes, and measuring the impact through before/after Lighthouse testing.
This report documents a full accessibility audit cycle for Lab Unknown, a static portfolio site built with HTML/CSS/JS and deployed on Netlify. The audit followed a structured methodology combining automated Lighthouse testing with manual WCAG 2.1 code review, revealing a significant gap between what automated tools detect and what truly affects users.
The audit identified 16 distinct accessibility issues across 3 priority levels. Of these, only 1 category (color contrast) was detected by Lighthouse. The remaining 11 structural and semantic issues — including missing landmarks, keyboard focus styles, and dynamic ARIA state — were found exclusively through manual review.
The audit used a two-track approach: automated scanning via Google Lighthouse, and manual code inspection against WCAG 2.1 Level AA success criteria. Results from both tracks were then cross-referenced to produce a gap analysis.
Chrome DevTools → Lighthouse → Accessibility audit on the live site (labunknown.ca). Captured all failing audits, passing audits, and not-applicable checks. Baseline score: 87/100.
Full review of HTML source against WCAG 2.1 AA success criteria: semantic structure, keyboard navigation, ARIA patterns, color contrast ratios, motion, and dynamic state management.
Cross-referenced both result sets to identify what each method caught, missed, or overlapped — producing a prioritized fix list and insight into tool limitations.
Applied fixes to style.css
and index.html,
following the existing design system and code conventions.
Deployed updated files to Netlify (drag-and-drop). Re-ran Lighthouse on the live site. Final score: 100/100 Accessibility.
Lighthouse version 13 identified one failing audit category on the homepage, affecting 11 distinct elements. All other checks either passed or were not applicable.
| Audit | WCAG | Failing Elements | Status |
|---|---|---|---|
| Color Contrast Background and foreground colors do not have a sufficient contrast ratio |
1.4.3 AA |
.featured-label (×2) .featured-more (×2) .home-about-btn-solid .home-insights-more .footer-copy .footer-built (×3 links) |
Failed |
| Audit | WCAG |
|---|---|
| Document has a <title> element | 2.4.2 |
| <html> element has a valid [lang] attribute | 3.1.1 |
| Image elements have [alt] attributes | 1.1.1 |
| Links have a discernible name | 2.4.4 |
| [user-scalable="no"] not used in viewport meta | 1.4.4 |
| [aria-hidden="true"] not present on <body> | 4.1.2 |
Manual inspection of the HTML/CSS source code against WCAG 2.1 AA criteria identified 16 issues across 4 categories. These are organized by priority.
All gray-4 (#999999) text on white background fails the 4.5:1 minimum contrast ratio for normal text.
| Element | Before | Contrast Ratio | After | Contrast Ratio |
|---|---|---|---|---|
| .featured-label | #999 on white | 2.85:1 | #555 on white | 7.46:1 |
| .featured-more | #999 on white | 2.85:1 | #555 on white | 7.46:1 |
| .home-about-btn-solid | white on #FF5C00 | 3.09:1 | #0a0a0a on #FF5C00 | 6.40:1 |
| .home-insights-more | #999 on white | 2.85:1 | #555 on white | 7.46:1 |
| .footer-copy | #999 on white | 2.85:1 | #555 on white | 7.46:1 |
| .footer-built & links | #999 on white | 2.85:1 | #555 on white | 7.46:1 |
Note on the orange button:
The brand accent color #FF5C00
was preserved. Only the button text color was changed from white to near-black, maintaining full
brand identity while achieving a 6.40:1 contrast ratio — well above the 4.5:1 requirement.
| Issue | WCAG | Impact | Fix Applied |
|---|---|---|---|
| No skip navigation link | 2.4.1 A | Keyboard users must tab through all nav links to reach content on every page load | Added <a href="#main-content" class="skip-link"> |
| No <main> landmark | 1.3.1 A | Screen reader users cannot jump directly to main content via landmark navigation | Wrapped page content in <main id="main-content"> |
| <section> elements without accessible names | 1.3.6 AAA | Sections are announced as "region" without context in screen readers | Added aria-label to all 5 sections |
| Marquee not hidden from AT | 1.1.1 A | Screen readers announce repeated skill tags — adds noise, no informational value | Added aria-hidden="true" role="presentation" |
| Decorative canvas not hidden from AT | 1.1.1 A | Bubble animation canvas may be announced as an interactive element | Added aria-hidden="true" to canvas |
| Issue | WCAG | Impact | Fix Applied |
|---|---|---|---|
| Hamburger menu: aria-expanded not updated by JS | 4.1.2 A | Screen reader users cannot know if the mobile menu is open or closed | JS now toggles aria-expanded true/false on click |
| Hamburger menu: no aria-controls | 4.1.2 A | No programmatic link between button and the menu it controls | Added aria-controls="nav-menu", assigned matching id |
| Navigation green dot not hidden from AT | 1.1.1 A | Screen reader announces decorative status dot unnecessarily | Added aria-hidden="true" to dot span |
| Issue | WCAG | Impact | Fix Applied |
|---|---|---|---|
| No :focus-visible styles | 2.4.7 AA | Keyboard users cannot see which element is focused while navigating | Added 2px orange outline for all focusable elements via :focus-visible |
| No prefers-reduced-motion support | 2.3.3 AAA | Marquee, fade-ins, and bounce animations run regardless of system motion preference — may cause discomfort for users with vestibular disorders | Added @media (prefers-reduced-motion: reduce) — disables all animations |
/* No skip link styles */
/* No :focus-visible styles */
.skip-link {
position: absolute;
top: -100%;
left: 16px;
background: var(--accent);
color: var(--white);
padding: 10px 20px;
z-index: 99999;
}
.skip-link:focus { top: 0; }
:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 3px;
}
.footer-copy {
color: var(--gray-4);
}
.footer-built {
color: var(--gray-4);
}
.footer-built a {
color: var(--gray-4);
}
.footer-copy {
color: var(--gray-5);
}
.footer-built {
color: var(--gray-5);
}
.footer-built a {
color: var(--gray-5);
}
/* No motion preference support */
@media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; }
.marquee-track { animation: none; }
.fade-in {
opacity: 1;
transform: none;
transition: none;
}
* {
transition-duration: 0.01ms !important;
animation-duration: 0.01ms !important;
}
}
<body>
<nav class="nav">
...
</nav>
<div class="page">
<section class="hero">
...
<body>
<a href="#main-content"
class="skip-link">
Skip to main content
</a>
<nav class="nav">...</nav>
<main id="main-content">
<div class="page">
<section class="hero">
...
hamburger.addEventListener(
'click', () => {
nav.classList
.toggle('open');
// aria-expanded never updates
}
);
hamburger.setAttribute(
'aria-expanded', 'false');
hamburger.addEventListener(
'click', () => {
const isOpen =
nav.classList.toggle('open');
hamburger.setAttribute(
'aria-expanded',
isOpen ? 'true' : 'false');
}
);
This section documents what each method found, missed, and why — the core insight of this case study.
| Lighthouse Metric | Before | After | Change |
|---|---|---|---|
| Accessibility | 87 | 100 | +13 |
| Performance | 91 | 93 | +2 |
| Best Practices | 77 | 77 | — |
| SEO | 91 | 91 | — |
| Fix Category | Issues Fixed | Files | WCAG Criteria |
|---|---|---|---|
| Color Contrast | 6 element groups (11 elements) | style.css, index.html | 1.4.3 AA |
| Skip Link | 1 | style.css, index.html | 2.4.1 A |
| Main Landmark | 1 | index.html | 1.3.1 A |
| Section Labels | 5 sections | index.html | 1.3.6 AAA |
| Dynamic ARIA State | 3 (expanded, controls, dot) | index.html | 4.1.2 A |
| Decorative Elements | 2 (marquee, canvas) | index.html | 1.1.1 A |
| Focus Visibility | 1 | style.css | 2.4.7 AA |
| Reduced Motion | 1 | style.css | 2.3.3 AAA |
Lighthouse caught the only issue it's designed to catch well — color contrast — but missed 11 of the 16 issues found in this audit. A score of 87 felt "almost there," but the actual experience for keyboard and screen reader users had significant gaps.
Missing landmarks, improper ARIA state management, and lack of focus styles all affect real users daily but produce no Lighthouse penalty. Manual review is the only way to find them.
The root cause of all 6 contrast failures was a single CSS variable: --gray-4: #999999
being used for informational text. One token decision affected 11 elements across the page.
Accessibility-first design system documentation would have prevented this.
The orange accent #FF5C00
is a core brand element. Rather than changing the color, we changed the text on top of
it from white to near-black — achieving a 6.40:1 ratio while keeping the exact same brand feel.
For users with vestibular disorders, persistent marquee animations and scroll-triggered
fade effects can cause physical discomfort. The prefers-reduced-motion
media query is a simple, high-impact fix that takes minutes to implement.