Skip to main content
Ramon Carrillo

All posts

10 min read

Checklist

The WCAG 2.1 AA checklist I run on every project

A practical, ordered checklist for shipping accessible Next.js + Tailwind projects — landmarks, skip links, keyboard support, contrast, reflow, and forms — distilled from auditing four live sites in one morning.

Why I have a checklist

Every project in my portfolio goes through the same accessibility pass before I call it done. Not because a client asked for it — because I've watched too many "polished" portfolios fall apart the moment you tab through them with a keyboard, or open them on a phone at 200% zoom. A developer portfolio that can't clear WCAG 2.1 AA is a tell: either the author doesn't know the rules, or they know and skipped them.

This post is the actual checklist I ran against four live projects in one sitting — the Google Maps RAG Assistant, this portfolio, a chimney-and-roofing marketing site, and an e-commerce demo. Everything here is something I've shipped a PR for. No theory, no "ideal world" bullets that I don't personally enforce.

WCAG is a big standard, so I'm not going to pretend this covers everything. The goal is AA conformance on a typical Next.js + Tailwind + shadcn/ui project. If you ship government work or need Section 508, go deeper. For a portfolio, this checklist is what separates "accessible enough that a screen-reader user can actually use it" from "I added an alt tag once."

Structure: six blocks, roughly in order

I work the list top to bottom because earlier fixes often prevent later ones. If your HTML landmarks are wrong, the skip link you're about to add will jump to the wrong place.

1. Landmarks and document structure

The screen-reader user's first action is usually "jump to main content" or "list the landmarks." If your page has five <div>s in a trench coat, they're stuck.

2. Skip link

One of the highest-value fixes per line of code. A skip link lets keyboard users bypass your nav on every page — without it they tab through the same eight menu items on every route.

// app/layout.tsx
<body>
  <a
    href="#main"
    className="sr-only focus:not-sr-only focus:fixed focus:top-2 focus:left-2 focus:z-50 focus:rounded-md focus:bg-primary focus:px-4 focus:py-2 focus:text-sm focus:font-medium focus:text-primary-foreground"
  >
    Skip to main content
  </a>
  {children}
</body>

The sr-onlyfocus:not-sr-only pattern keeps it hidden until the first Tab press, then it pops into view. Covers WCAG 2.4.1 Bypass Blocks.

3. Keyboard support

The rule I keep in my head: if I can do it with the mouse, I must be able to do it with only the keyboard, and I must be able to see where I am at all times.

4. Color and contrast

This is where I see the most silent failures. A design looks fine on the designer's calibrated monitor and is unreadable on a cheap laptop screen in a coffee shop.

I run real tooling here — @axe-core/cli, Chrome Lighthouse, or the excellent Accessible Colors site — not eyeball checks.

5. Touch targets and reflow

Mobile accessibility isn't a separate category. It's WCAG.

6. Forms and live content

Forms are where screen-reader UX either shines or collapses.

The things that aren't on the list (and why)

I don't check every WCAG AA criterion on every pass. Some I let the framework handle, some I rely on tooling to catch.

The automation layer

A checklist is only useful if it gets run. I pair manual review with three tools:

  1. @axe-core/cli in CI for the obvious failures (missing alt, missing labels, duplicate IDs, bad contrast on static elements).
  2. Lighthouse on the deployed preview URL for a rough overall score — the number isn't the goal, but a drop from 100 to 92 is a signal to look.
  3. Manual keyboard pass, every time, on every route. Tab through the whole page. Trigger every button and link. Open every modal. Close every modal. This catches everything automated tools can't — focus order bugs, missing focus rings, unexpected focus jumps.

I don't believe in accessibility tooling alone. Axe catches maybe 40% of real issues; the other 60% need a human who understands what the page is trying to do. But the 40% automated check is free once it's set up, and it catches regressions I'd otherwise ship.

How I apply this to an existing project

When I audit a site that's already live (the common case — Chimneys Plus, EcoShop, Lumina in my portfolio), I do it in a specific order:

  1. Read the DOM first. Open the deployed page, right-click → Inspect, and just read the HTML structure. Before running any tool, I want to see: is there a <main>? One <h1>? Meaningful landmarks? If the bones are wrong, fixing contrast is polishing a corpse.
  2. Tab through the page. No tools, no screen reader, just Tab. I'm looking for: does the focus ring appear? Does it stay visible? Does tab order match visual order? Where does focus go when I open a modal — and where does it go back to when I close it?
  3. Run axe for the static failures — missing alts, missing labels, duplicate IDs.
  4. Run Lighthouse for the score and the contrast audit.
  5. Zoom to 200% and narrow to 320px. Any horizontal scrollbar? Any clipped text?
  6. Fix in the same order as this checklist. Landmarks → skip link → keyboard → contrast → touch → forms.

A minimal layout.tsx that covers most of the bases

If I were starting a Next.js project from scratch today, the root layout would ship with most of this baked in:

// app/layout.tsx
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body className="min-h-screen antialiased">
        <a
          href="#main"
          className="sr-only focus:not-sr-only focus:fixed focus:top-2 focus:left-2 focus:z-50 focus:rounded-md focus:bg-primary focus:px-4 focus:py-2 focus:text-sm focus:font-medium focus:text-primary-foreground"
        >
          Skip to main content
        </a>
        <Nav />
        <main id="main">{children}</main>
        <Footer />
      </body>
    </html>
  );
}

Plus this in globals.css:

@layer base {
  *:focus-visible {
    outline: 2px solid var(--ring);
    outline-offset: 2px;
    border-radius: 4px;
  }
}

That one file and one CSS block gets you past the three most common WCAG failures in shipped Next.js apps: missing lang, missing skip link, and invisible focus rings. Start every project this way and you've done more for accessibility than most portfolios on the internet.

The real takeaway

Accessibility is not a separate pass. It's part of building the thing. The reason I can run this checklist across four projects in a morning is that most of the boxes were already ticked while I was writing the code the first time — I just double-check them before calling anything done.

If you've never done this pass on one of your own projects: start with the three cheapest, highest-value fixes, in this order.

  1. Add lang="en" to <html> if it isn't there.
  2. Add a skip link.
  3. Replace any faint focus ring with a solid 2px one.

Those three changes take ten minutes and move you from "fails basic checks" to "passes the smoke test." Everything else on this list builds from there.