C.W.K.
Stream
Lesson 06 of 07 · published

Lighthouse + Accessibility Floor

~12 min · lighthouse, accessibility, a11y, performance

Level 0React Novice
0 XP0/54 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
Lighthouse measures four things: performance, accessibility, best practices, SEO. For a Vite SPA, accessibility is the one you have least excuse to neglect — it's mostly a discipline of using the right HTML.

Running Lighthouse

Open Chrome DevTools → Lighthouse tab → pick categories (performance, accessibility, best practices, SEO) → run on Production mode (start with npm run preview, not the dev server). Get a score per category and a list of issues with documentation links.

The performance score

Driven by metrics: LCP (largest contentful paint), TBT (total blocking time), CLS (cumulative layout shift). For SPAs, LCP and TBT usually dominate. Wins: code splitting (Track 8 lesson 2), lazy-load below-the-fold images, font-display: swap, eliminate render-blocking JS in <head>.

The accessibility floor (what every SPA needs)

  1. Semantic HTML<button> not <div onClick>, <nav> / <main> / <article> for structure, <label> for every input.
  2. Keyboard navigation — every interactive element reachable by Tab, with visible focus rings (don't outline: none without a replacement).
  3. Color contrast — text passes WCAG AA (4.5:1 for normal text, 3:1 for large). Lighthouse flags failures with specific elements.
  4. ARIA when HTML isn't enougharia-label for icon-only buttons, role="alert" for error toasts, aria-live for streamed content.
  5. Page titles & meta — every route should set document.title (or use a library like react-helmet-async).

The best-practices floor

HTTPS in production. No console errors. No deprecated APIs. Source maps available for error tracking. CSP headers (Content Security Policy) for production. Lighthouse flags the misses.

Accessibility is a default, not a feature. If you write semantic HTML and respect keyboard navigation from the start, you'll pass most of Lighthouse's a11y checks without trying. If you treat it as 'something to add later,' you'll be refactoring divs into buttons for weeks. Cheaper to do it right the first time.

Code

Common a11y fixes the Lighthouse audit flags·tsx
// ANTI — div as button (no keyboard, no role, no announcement)
<div onClick={save} className="p-2 bg-brand">Save</div>

// CORRECT — semantic button (keyboard + screen reader + focus ring)
<button
  onClick={save}
  className="p-2 bg-brand focus-visible:ring-2 focus-visible:ring-fg"
>
  Save
</button>

// ANTI — image icon with no description
<img src="/trash.svg" onClick={delete} />

// CORRECT — accessible icon button
<button onClick={delete} aria-label="Delete conversation">
  <img src="/trash.svg" alt="" />
  {/* Empty alt because the button label carries the meaning */}
</button>

// ANTI — form input with no label
<input name="email" />

// CORRECT — label associated with input
<label className="block">
  <span>Email</span>
  <input name="email" type="email" required />
</label>
Setting page titles per route·tsx
import { useEffect } from "react";

function useDocumentTitle(title: string) {
  useEffect(() => {
    const previous = document.title;
    document.title = title;
    return () => { document.title = previous; };
  }, [title]);
}

function SettingsPage() {
  useDocumentTitle("Settings — My App");
  return <h1>Settings</h1>;
}

External links

Exercise

Build your project (npm run build && npm run preview). Open Chrome DevTools → Lighthouse → run on the preview URL. Fix the top three accessibility issues. Re-run. Repeat until you hit 90+. Note: 100 isn't required; 90+ shows you've handled the basics.
Hint
Common first wins: add <label> to every input, replace clickable divs with buttons, add aria-label to icon-only buttons, ensure body text passes contrast (Tailwind's text-fg on bg-bg should be tuned to pass).

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.