I've been putting off a proper accessibility audit since we shipped the new homepage. Not because I don't care—I do—but because I knew once I started looking, I'd find things that needed fixing. And I was right.
The Audit
I ran the homepage through axe-core 4.9.1, Lighthouse, and Playwright automated tests, then did manual source review and keyboard navigation testing. The goal was WCAG 2.1 AA compliance—the standard most organizations target. We got a 96/100 Lighthouse accessibility score, which sounds great until you read the details.
The good news: our heading hierarchy is correct, all landmarks are present, the live activity indicator has proper ARIA labels, and our reduced-motion CSS works. The bad news: we had six violations that ranged from critical (color contrast failures) to important (missing ARIA attributes).
What We Fixed
Footer text contrast: Our muted text color (#55556a) only had 2.71:1 contrast against the dark background. WCAG requires 4.5:1 for normal text. We lightened it to #7a7a92 to hit the minimum. The footer disclaimer had an even worse 1.68:1 ratio because of a /60 opacity modifier—removed.
Mobile nav toggle: The hamburger menu button was missing aria-expanded. Screen reader users couldn't tell if the menu was open or closed. One-line fix: aria-expanded={mobileOpen}.
Focus indicators: We had no custom focus styles, so keyboard users got the browser default—a sub-pixel ring that was effectively invisible on our dark background. Added a global :focus-visible rule with a 2px aurora-green outline and 3px offset. Now you can actually see where you are when you tab through the page.
Nav landmark label: Added aria-label="Main navigation" to the nav element. Not strictly required when you only have one nav, but it makes screen reader announcements clearer.
Reduced motion: The live activity indicator pulse animation used repeat: Infinity in Framer Motion. When users enable prefers-reduced-motion, Framer sets duration to 0 but the infinite loop still ticks. We now use useReducedMotion() to conditionally set the animation variant to 'initial' when motion is reduced.
The Test Fixes
Writing tests for these fixes surfaced two anti-patterns. The reduced-motion test was checking data-animate synchronously before the fetch resolved, so it would pass even if the useReducedMotion() guard were removed. Added await screen.findByText() to wait for render. Also replaced module-scope global.fetch mutation with vi.stubGlobal in beforeEach—same pattern we flagged in the PR #42 review.
Then we isolated the framer-motion mock into a dedicated test file. Vitest hoists vi.mock calls to module scope, so the mock was applying to every describe block in the file. Future additions of framer-motion hooks to Navbar or Footer would silently receive mocked return values. Bad test hygiene.
What's Next
The critical issues are fixed, but the audit surfaced medium-priority items we're tracking: touch target sizes on nav links (36px instead of the 44px recommended), inline text link distinguishability (color-only differentiation without underlines), and validating that focus order is logical when you tab through the entire page.
We also need to run VoiceOver and NVDA smoke tests—automated tools can't verify that live region announcements are actually intelligible or that landmark navigation makes sense to a real screen reader user.
The homepage is now WCAG AA compliant for the issues we can programmatically test. Next up: auditing the product pages, team page, and blog. Then we'll expand testing to include real assistive technology users.