Skip to Content
HTML and CSS Advanced: Styles That Hold Up in Production
Advanced
0 likes
Courses
HTML and CSS Advanced: Styles That Hold Up in Production

Audit Accessibility and Visual Regressions Before Launch

Run axe, walk the keyboard path, test reflow and reduced motion, and compare screenshots before and after a change. After this, you ship with a checklist and not a hope.

Course content

Sign in to save your progress.

Course Home

Skip ahead: If you already run axe, tab through the page, and keep screenshot baselines that fail a build, skip to "Visual regressions", then continue.

Most accessibility and layout bugs ship because nobody looked at the finished page in the way a user does. Looking takes an hour once and ten minutes after that. Skipping it costs support tickets and fixes that touch every page. Do three passes: automated checks for what a tool can see, manual checks for what it cannot, and screenshots to catch what you changed by accident.

Pass 1: automated, and what it misses

I ran axe-core 4.14.0 in Chrome 154 against a small contact page with a light grey intro paragraph, a button and a link. This is the part of the report that matters:

color-contrast   serious   .muted
  Element has insufficient color contrast of 2.64 (foreground color: #9aa0a6,
  background color: #ffffff, font size: 12.0pt (16px), font weight: normal).
  Expected contrast ratio of 4.5:1
landmark-one-main   moderate   html   Document does not have a main landmark
region              moderate   4 nodes   Some page content is not contained by landmarks

Three findings, and each one is real. The grey text fails the 4.5:1 ratio for normal text. The page has no <main>, so keyboard and screen reader users cannot jump to the content. #626976 on white computes to 5.52:1, which passes, and that was a one-line fix.

The same page had a:focus, .btn:focus { outline: none; } in its CSS. axe reported nothing about that. After two Tab presses the focused element was the button, and its outline-style was none. A tool cannot tell that a focus indicator is missing, because that depends on what you can see. That is the pass you do by hand.

Pass 2: manual

  1. Tab through the whole page. Can you see where you are at every step? Does the order match the visual order? Can you reach and use every control without a mouse?
  2. Zoom to 200%, then narrow the window to 320 CSS pixels. WCAG 1.4.10 asks for content to reflow at that width without two-direction scrolling.
  3. Emulate reduced motion. Nothing should slide or loop, and nothing important should vanish.
  4. Check contrast for every state, not just the default: hover, focus, disabled, error, and each theme.

Non-text parts need 3:1 as well, such as input borders and focus rings (WCAG 1.4.11). In my demo the input border #c9ced6 on white is 1.58:1, so a field drawn only by that border is hard to see.

⚠ Warning: A green axe report does not mean the page is accessible. Automated tools catch only part of the problems. Treat a clean report as the start of the audit and not the end.

Visual regressions

Screenshots catch the change you did not mean to make. Take a baseline of the page at two widths, make a change, take it again, and compare. This is the script I used, with Playwright 1.63.0 and Pillow 12.3.0.

from playwright.sync_api import sync_playwright

url, out = "http://127.0.0.1:8765/page.html", "current"
with sync_playwright() as p:
    browser = p.chromium.launch()
    for width in (1280, 390):
        page = browser.new_page(viewport={"width": width, "height": 800})
        page.goto(url)
        page.screenshot(path=f"{out}-{width}.png", full_page=True)
    browser.close()
from PIL import Image, ImageChops

for width in (1280, 390):
    a = Image.open(f"baseline-{width}.png").convert("RGB")
    b = Image.open(f"current-{width}.png").convert("RGB")
    box = ImageChops.difference(a, b).getbbox()
    print(f"{width}px:", "identical" if box is None else f"pixels differ inside {box}")

Two runs with no change printed identical at both widths. After I changed the .btn padding from 8px 16px to 8px 20px on a page with three buttons, both widths printed pixels differ inside (25, 8, 72, 101). The box tells you where to look. Pixel diffs are strict: fonts, anti-aliasing and animations create noise, so pin the browser version, disable animations, and wait for fonts before capture.

Common mistakes

  • Running the audit on the staging page with sample content, and then launching with real content that is longer.
  • Fixing contrast only in light mode.
  • Treating every pixel difference as a failure. Decide what is worth a human look.
  • Auditing once, at launch, and never again.

💡 Tip: Put the axe run and the screenshot compare in the build, so a regression fails before a reviewer sees it. Keep the manual pass as a short checklist in the pull request template.

What happens if you skip this

The first person to find the missing focus ring is a user who cannot use the form. The first person to find the broken layout is a customer on a narrow phone. Both reports arrive after launch.

Try it

Run axe on your most important page, then Tab through it with your mouse unplugged. Write down every place where you could not see the focus.

How many of the problems you found would a tool have reported?

About

Run axe, walk the keyboard path, test reflow and reduced motion, and compare screenshots before and after a change. After this, you ship with a checklist and not a hope.

Duration
5 min
Last update
08/07/2026
Was this helpful?
​ 0 ​ 0

Comments (0)

Sign in to comment

No comments yet.