31 — Accessibility — Semantics That Actually Reach Everyone
"A checklist you run at the end, before ship" was how I treated accessibility, which made it an afterthought instead of a property. The line that everything else hangs off: accessibility ("a11y") is just semantics done well, made universal. It's the same "describe what this content is" habit from the HTML notes — but now the question is whether that meaning actually reaches someone using a screen reader, a keyboard, or a low-vision setting, not just my own eyeballs [1].
The a11y and the PWA sibling in one frame: a11y is "reach everyone as a person" (meaning delivered through the right tags, contrast, keyboard support, and ARIA (Accessible Rich Internet Applications — attributes for when HTML alone can't express the meaning) where the semantics aren't enough); a Progressive Web App is "reach everyone on any device" (a web app that's installable and works offline, app-like, via a service worker and a manifest). Both are the same instinct — stop assuming one default user on one default machine.
Why a11y is "semantics done well"
A screen reader doesn't look at pixels. It reads the meaning the tags declare [1]. So if I describe the meaning correctly — one <h1> for the page's top-level heading, <h2>/<h3> for a logical outline, <main>/<nav>/<aside> as landmarks (named regions a screen reader can jump between) — a blind user navigates my page like a table of contents: jump to landmarks, skim the heading outline, skip the chrome they've heard a hundred times. That's not a separate accessibility layer. That's the HTML notes, applied seriously.
The failures I had to unlearn are all the same mistake: using meaning-free markup where meaning was needed.
- A <div onclick> instead of <button> — the <div> isn't focusable, isn't keyboard-operable, and announces nothing to assistive tech. A <button> is all three by default [2].
- Skipping heading levels for visual effect (<h1> straight to <h4>) — breaks the outline someone navigates by.
- An image with no alt — the screen reader either reads the filename or says nothing, so the meaning never arrives [3].
The fix is never "add an accessibility library." It's "use the right element." The right tag is the accessibility.
The four things semantics alone can't cover
Get the semantics right and a lot of a11y comes free. But meaning doesn't cover everything. Four things still need deliberate attention [1][3]:
- Alt text. Every content <img> needs alt describing its purpose — short for decoration, richer when the image carries information. Decorative images get alt="" (empty, not omitted) so they're explicitly skipped, not announced as a mystery.
- Keyboard navigation. Can I reach every interactive thing with Tab, and operate it with the expected keys (Enter/Space on buttons, arrows on tabs)? If a control is mouse-only, a keyboard user is locked out.
- Focus. The focus ring isn't ugly — it's the keyboard user's cursor. Hiding :focus globally (or worse, outline: none) without a replacement strands keyboard users with no idea where they are. :focus-visible lets me style the ring only for keyboard users, leaving mouse users clean.
- Color contrast. Text needs enough contrast against its background (WCAG AA — the Web Content Accessibility Guidelines, level AA — targets roughly 4.5:1 for body text). Low-contrast grey-on-white looks refined and reads as nothing for low-vision users [3].
These aren't a11y features — they're the last mile of "does the meaning actually reach the person."
ARIA: the gap-filler, not the default
There's a temptation, once you learn ARIA exists, to slap role="button" on every <div> and call it accessible. That's backwards. The first rule of ARIA is: don't use ARIA if a native element exists [4]. A real <button> gives you focus, keyboard handling, and the correct role for free. An ARIA-role="button" <div> gives you the label and leaves you to rebuild everything else by hand — and one missed key handler makes it worse than the plain <div>.
Where ARIA does earn its place is where HTML genuinely has no element for the thing: a custom disclosure widget, a live region that announces updates, or an expanded/collapsed state that needs to be told to assistive tech explicitly. aria-label, aria-expanded, aria-live, aria-describedby — these fill gaps the markup can't express on its own. The mental rule I keep: native first, ARIA for what native can't say.
Screen readers are the honest reader
The fastest way I found to stop guessing about a11y is to actually turn on VoiceOver (macOS) or NVDA (Windows) and try to use my own page without looking. Five minutes of that exposed more assumptions than any article: a form field with no <label> announced as "edit text, blank"; a nav built from <div>s read as an undifferentiated wall; a modal (popup dialog) that trapped the cursor because I never sent focus into it. Screen readers aren't a separate audience reading something different — they're reading the same document my markup describes, and they show me, plainly, where my description was wrong [5].
PWA — reach everyone on any device
If a11y is "reach every person," a Progressive Web App is "reach every device" — and it's the natural sibling, because both come from the same instinct: don't assume one default client [6]. A PWA is a regular web app (HTML, CSS, JavaScript) that, with two additions, behaves like a native app: installable to the home screen, and offline-capable.
The two additions [6][7]:
- Web App Manifest — a small JSON file (manifest.json) that tells the OS how to install the app: its name, icons, start URL, theme color, and display mode (standalone hides the browser chrome so it looks like a native app).
- Service Worker — a JavaScript file the browser runs separately from the page. It sits between the page and the network and can intercept requests, serving cached responses when offline. That's what makes "open the app with no signal" actually work — the service worker has the shell cached.
The payoff is the reach a native app can't match. One URL works on desktop, mobile, installable on iOS, installable on Android, indexed by search, linkable, and functional offline — no app store, no review queue, no binary download. The web's strengths (URLs, no install gate) plus the app's strengths (offline, home-screen presence) in one delivery.
How I use this
The habit these notes left me with is a single question added to my default reach: *when I write a tag, I ask not just what is this content? but does its meaning reach someone who isn't me?** That's alt text, a real <button>, a kept focus ring, a passing contrast check — almost all of it free if the semantics are right. ARIA only when native can't say it. And on the delivery side, when I want the app to arrive* on a device rather than just be visited in a tab, I reach for the manifest + service-worker pair instead of a native build.
That's the whole frame: describe meaning well, then make sure it reaches — as a person, and on a device.
References
[1] Google, "Accessibility for developers," web.dev, 2023. [Online]. Available: https://web.dev/accessibility
[2] Google, "Use semantic HTML," web.dev, 2023. [Online]. Available: https://web.dev/articles/semantic-html
[3] Mozilla, "What is accessibility?," MDN Web Docs, 2024. [Online]. Available: https://developer.mozilla.org/en-US/docs/Learn/Accessibility/What_is_accessibility
[4] W3C Web Accessibility Initiative, "ARIA Authoring Practices Guide — No. 1 rule," WAI-ARIA, 2024. [Online]. Available: https://www.w3.org/WAI/ARIA/apg/practices/read-me-first/
[5] Mozilla, "Screen readers," MDN Web Docs, 2024. [Online]. Available: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Screen_readers
[6] Google, "Learn PWA," web.dev, 2024. [Online]. Available: https://web.dev/learn/pwa/
[7] Mozilla, "Progressive Web Apps," MDN Web Docs, 2024. [Online]. Available: https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/
Knowledge check · Question 1 of 5
Which best describes what accessibility ("a11y") actually is?
Comments
Leave a Comment
You must be signed in to comment
0 Comments
No comments yet. Be the first to comment!