Accessibility Audit With Claude: What I Found and Fixed

I got Claude Code to audit this site for accessibility. I pointed it at the project root and asked what was broken. It turns out I'd been missing some pretty fundamental stuff.

Accessibility compliance is standard practice in organisations. There's usually a QA process, automated checks in CI, and legal requirements driving it. On personal projects none of that exists. There's no legal obligation, no one flagging issues, and the site looks fine in your own browser. So it doesn't get done. The barrier isn't that people don't care, it's that the labour cost feels hard to justify when nobody's requiring it. Using an AI to do the audit and the bulk fixes made it practical in a way it wouldn't have been otherwise.

Which standards actually apply?

For a personal site, none are legally mandatory. But WCAG 2.1 AA is the benchmark everything references:

WCAG 2.1 AA is maintained by the W3C. Level A is the absolute minimum (screen readers can parse the page, nothing causes seizures). Level AA is what professional sites should meet. Level AAA is aspirational.

European Accessibility Act covers commercial products and services in the EU from June 2025. Personal blogs are exempt.

UK Equality Act 2010 covers "service providers." A blog probably doesn't count, but if you're offering tools or educational content to the public the line gets blurry.

US ADA / Section 508 applies to federal agencies and commercial sites. Personal blogs aren't targeted.

Even without legal pressure, WCAG 2.1 AA compliance means people with visual impairments, motor disabilities, cognitive differences, and vestibular disorders can actually use your site. That's a bigger audience than you'd think.

What it found

Missing lang attribute (WCAG 3.1.1, Level A)

Nearly every page was missing lang="en" on the <html> tag. This tells screen readers what language the content is in so they pick the right pronunciation. Without it, a screen reader might try to read English with French phonetics.

The fix is one attribute:

<html lang="en">

Claude also caught that my index page was missing the <!DOCTYPE html>, <html>, and <body> tags entirely. The page looked fine in a browser. I never would have noticed. Browsers are very forgiving. Assistive technology is not.

46 files updated. Two minutes.

Firefox devtools showing the lang=en attribute on the html element

No reduced motion support (WCAG 2.3.1, Level A)

The site has a CRT terminal aesthetic with a bunch of CSS animations. A scanning line sweeping down the page, glitch effects, blinking cursors, pulsing elements. I like how they look. But for people with vestibular disorders they can cause dizziness and nausea. For people with photosensitive epilepsy, rapid flashing can trigger seizures. For people with ADHD or migraines, constant motion is distracting or painful.

I didn't know prefers-reduced-motion existed before this. It's a CSS media query that checks a toggle in the user's OS. "Reduce motion" on macOS/iOS, "Animation effects" on Windows, equivalent toggles on Linux desktops. When it's on, your CSS rules inside the query kick in.

@media (prefers-reduced-motion: reduce) {
  body::before {
    animation: none;
  }

  .glitch,
  .glitch-text,
  .warning-text,
  .cursor-blink,
  .loading {
    animation: none;
  }

  .screen-break {
    animation: none;
  }

  .vortex-access-btn,
  .system-btn,
  .glitch-button {
    transition: none;
  }
}

One block of CSS in the shared stylesheet. All the animations stop, all the transitions go away. The design stays exactly the same. Same colours, same layout, same terminal look. It just doesn't move. Anyone without the OS setting enabled sees no difference.

The site index page with default animations active, showing the CRT scanline effect The same index page with prefers-reduced-motion enabled, identical design but no animations

Focus indicators (WCAG 2.4.7, Level AA)

Interactive elements on the site like nav buttons had :hover styles, but regular links in article content had no visual feedback at all on hover or focus. If you're navigating with a keyboard you're tabbing blind, and even with a mouse there was no indication you were hovering over a link beyond the cursor changing.

CSS has a pseudo-class called :focus-visible that only activates when the browser detects keyboard navigation. So clicking a link with a mouse won't trigger it, but tabbing to it will. This avoids the annoying situation where clicking a button leaves a focus ring stuck on it.

The fix was adding both :hover and :focus-visible outlines to links. Now you get a red outline on mouse hover as well as when tabbing with the keyboard. For nav buttons and the back link, it's the same background change they already had.

a:hover,
a:focus-visible {
  outline: 2px solid #ff0000;
  outline-offset: 2px;
}
A link on the site with a red outline focus indicator visible on hover

Skip navigation (WCAG 2.4.1, Level A)

Every page on the site has nav links. When a screen reader or keyboard user loads a page they have to Tab through all of those before reaching the actual content. On every single page load.

A skip link is a hidden link that becomes the first focusable element on the page. Press Tab and it appears. Press Enter and it jumps you straight past the navigation to the main content. Sighted mouse users never see it unless they Tab.

<a href="#main-content" class="skip-link">Skip to content</a>

The CSS hides it off-screen by default and slides it into view on focus:

.skip-link {
  position: absolute;
  top: -100%;
  left: 0;
}

.skip-link:focus {
  top: 10px;
  left: 10px;
}

Added to every page on the site. The target id="main-content" goes on the content area so focus jumps straight to where the actual text starts.

The skip to content link visible in the top left corner of the page after pressing Tab

Semantic HTML landmarks (WCAG 1.3.1, Level A)

Screen readers have two ways of navigating a page. Linear mode reads everything top to bottom, word by word. Landmark mode lets users jump between major sections using keyboard shortcuts. Without semantic HTML, a screen reader sees the page as one giant blob of content inside divs. With landmarks, users get something like a table of contents they can skip between.

The site was wrapping everything in <div class="terminal"> and <div class="output">. Visually fine. For assistive technology, meaningless.

The fix was adding semantic elements inside the existing structure. No CSS changes needed because these elements render identically to divs by default.

<main> tells the screen reader "this is the primary content." Most screen readers have a single keypress to jump straight to it. Added to every page.

<header> marks the introductory section, in this case the page title. Users navigating by landmarks can distinguish "this is the page header" from "this is the actual content." If they're partway through a long article they can jump back to check what page they're on.

<article> marks a self-contained piece of content. Tells the screen reader "everything inside here is one coherent piece" rather than random content in a div. Added to all blog posts and lab writeups.

For article pages the structure went from:

<div class="terminal">
  <nav>...</nav>
  <h1>...</h1>
  <div class="output">...</div>
</div>

To:

<div class="terminal">
  <nav>...</nav>
  <header>
    <h1>...</h1>
  </header>
  <main>
    <article>
      <div class="output">...</div>
    </article>
  </main>
</div>

Visually identical. For sighted users nothing changes. The entire difference is for people using assistive technology.

Browser devtools showing the semantic HTML structure with main, header, and article elements wrapping the page content

Colour-only information (WCAG 1.4.1, Level A)

The privacy audit page calculates a privacy score and colours it red, orange, or green depending on how good it is. That was the only thing communicating the score level. About 8% of men have some form of colour vision deficiency. A screen reader user gets the number but no context for whether it's good or bad.

The fix was adding a text label alongside the colour. Instead of just "73" in orange, it now shows "73/100 (Moderate)". The colour stays as visual reinforcement but it's no longer the only way the information is communicated.

if (privacyScore < 50) {
    scoreElement.textContent = `${privacyScore}/100 (Poor)`;
} else if (privacyScore < 80) {
    scoreElement.textContent = `${privacyScore}/100 (Moderate)`;
} else {
    scoreElement.textContent = `${privacyScore}/100 (Good)`;
}
Privacy audit page showing the privacy score with a text label indicating the score level alongside the colour

Low-contrast text (WCAG 1.4.3, Level AA)

The initial audit flagged #ff6666 (audit labels on the privacy page) as low contrast against black. After actually calculating the ratio it comes out at 7.34:1, which comfortably passes the 4.5:1 AA minimum. The lowest contrast on the site is the red links at 5.25:1, which also passes. No changes needed here.

Audit checklist

IssueWCAGStatus
Add lang="en" to all pages3.1.1 (A)✔
Add prefers-reduced-motion support2.3.1 (A)✔
Add focus indicators for keyboard navigation2.4.7 (AA)✔
Add skip navigation link2.4.1 (A)✔
Use semantic HTML landmarks (<main>, <article>, etc.)1.3.1 (A)✔
Fix colour-only score indicators on privacy audit page1.4.1 (A)✔
Low-contrast text on secondary elements1.4.3 (AA)✔ (already passing)
Keyboard-scrollable overflowing code blocks (tabindex="0")2.1.1 (A)✔ (axe-core pass)
All content within landmarks on error, fingerprint, and test pages1.3.1 (A)✔ (axe-core pass)

How the Claude workflow went

I asked Claude Code to audit the site and it read every HTML file, the stylesheet, all the JavaScript, and the site config. It came back with a categorised report mapped to specific WCAG criteria. Then we worked through fixes one at a time. I'd test each change in the browser, then push the update live and verify before moving on.

The useful part wasn't just the fixes. It was learning things I didn't know. prefers-reduced-motion, the fact that my index page had no document structure at all, which WCAG levels actually matter for a personal site. The kind of stuff you don't notice when you're the one writing the code and testing it in your own browser.

Postscript: a second pass with axe-core

After publishing this, someone on LinkedIn asked whether I'd used axe-core. I hadn't. axe-core is the industry-standard automated accessibility engine, it powers Lighthouse's a11y audit and most CI accessibility tests. The audit above was Claude working from memory of the WCAG spec rather than running a scanner.

So we ran it. @axe-core/cli against ten representative pages, headless Chromium, fifteen seconds of runtime. It caught three categories of issue the from-memory audit missed:

Scrollable <pre> blocks need tabindex="0". When a code block overflows horizontally, keyboard users literally cannot scroll it, because scroll requires focus and <pre> isn't focusable by default. One attribute fixes it. 185 instances across 31 files.

<h1> outside landmarks. On the error pages (401, 403, 404, 500, 502, 503), fingerprint.html, and test.html I'd added <main> somewhere but left the <h1> outside it. Screen-reader users navigating by landmarks would skip past the heading. Moved the heading inside the main element.

Perceptual contrast on the privacy audit panels. axe did not flag the #ff0000 section titles on fingerprint.html because they pass the WCAG contrast ratio mathematically against the computed background. But pure red on a red-tinted panel reads poorly regardless of ratio. Bumped the titles to #ff6666 to match the labels and made the panel background opaque enough to block the scanning body gradient from bleeding through.

The takeaway is that the two approaches catch different things. A human audit from memory gets the big-picture WCAG criteria but misses the mechanical consistency issues a scanner catches deterministically. A scanner catches those but doesn't flag perceptual contrast problems that pass the ratio math. Running both produced a cleaner result than either on its own. I should have run axe-core the first time, and I will from now on.

The short version

Nine issues audited, nine resolved across two passes. The first pass, from a human-and-Claude WCAG walkthrough, covered lang attributes, reduced motion support, focus indicators, skip navigation, semantic HTML landmarks, and colour-only information. The second pass, with axe-core, covered keyboard-scrollable code blocks, headings inside landmarks, and perceptual panel contrast. Each fix addressed a real accessibility failure without any visual redesign.

If you run a static site, point an AI at it and ask about WCAG compliance, then run axe-core. Each catches things the other misses.

> End of output.

‎