What a 100 Accessibility Score Didn't Check

●2 ●8 ●20
calendar_today • schedule4 min read
— Originally published at tarek-elzoghby.hashnode.dev

Originally published on Hashnode: https://tarek-elzoghby.hashnode.dev/what-a-100-accessibility-score-didn-t-check

Lighthouse gave my practice site a 100 for accessibility. Then I went through it by hand, checking what Lighthouse doesn't, and found a markup mistake that neither Lighthouse nor a screen reader had flagged. This post covers what I checked, what I found, and what I changed.

I built a one-page site for a made-up potter as a practice brief: hero, about, gallery, contact. The client and the name are fictional. I worked with an AI as a design partner throughout, mostly to review my HTML and talk through decisions before I made them. The code is on GitHub. This post is only about the last stretch, after everything was built, when I went back through it for accessibility.

The baseline

Lighthouse gave me 99 Performance, 95 Accessibility, 100 Best Practices, and 91 SEO. Accessibility had exactly one complaint: the three contact links had low color contrast.

The links were set in terracotta (#B5603F) on a cream background, which comes to about 3.9:1. Normal-size text needs 4.5:1 to pass WCAG AA (the conformance level most sites aim for). Terracotta was the single accent color in the whole design, reserved for real calls to action, so I didn't want to drop it. The palette already had --color-primary-dark, a darker shade I'd originally made for hover states. I pointed the links at that instead. Accessibility went to 100 from that one change.

The link color had looked fine to me by eye, and it failed anyway. That's why I ran the audit at all.

What the 100 covers

A Lighthouse score covers the checks Lighthouse runs. It says nothing about the ones it doesn't. So I went through the site by hand, looking for gaps.

Decorative icons

The contact section has three inline SVG icons (email, phone, Instagram), each next to a text label that says the same thing. Screen readers handle inline SVGs inconsistently. Some announce "graphic," some read out raw markup, some skip them. Nothing breaks, but the experience is unpredictable, and the icons carry no information the label doesn't already carry.

<svg class="contact-icon" aria-hidden="true" focusable="false" ...>

aria-hidden="true" removes the icon from the accessibility tree (what assistive technology reads instead of the visual page). focusable="false" is a safety net for older IE and Edge, where SVGs could be focusable by default and pick up a stray tab stop.

Landmarks

Lighthouse checks that basic page structure is present. It doesn't check whether landmarks are useful. <main> gave me one landmark for free. The four blocks inside it (hero, about, gallery, contact) weren't landmarks at all. A plain <section> only becomes a region landmark once it has an accessible name, and <address> has no landmark role by default.

So the question I worked out before touching any markup was which sections are places someone would want to jump to. Having a heading wasn't the test.

The hero is where a screen reader user already lands, so making it a landmark would only add a "you are here" entry to the jump list. About, Gallery, and Contact are content someone might skip straight to. Those three got landmark status.

For About and Gallery, I gave the <h2> inside each an id and pointed the parent section at it:

<section aria-labelledby="about-heading">
  <h2 id="about-heading">About me</h2>
  ...
</section>

Contact was different. Its wrapper was an <address>, and aria-labelledby on an element with no landmark role names something that was never a landmark. I added role="region" alongside it:

<address role="region" aria-labelledby="contact-heading">
  <h2 id="contact-heading">Contact</h2>
  ...
</address>

Contact is as much a jump-to destination as the other two, and its tag choice shouldn't make it the odd one out. That got it into the landmark list, and it was still wrong. <address> is for contact details, and the HTML spec doesn't allow heading elements inside it, so the <h2> there was invalid markup. I found this later, when the AI reviewed the repo again before I wrote this post. Nothing I had run flagged it, and NVDA (a free screen reader for Windows) listed the region as if nothing were off.

The fix was smaller than I expected. I changed the wrapper from <address> to <section> and removed role="region". Everything else stayed the same:

<section class="contact" aria-labelledby="contact-heading">
  <div class="address-content">
    <h2 id="contact-heading">Contact</h2>
    ...
  </div>
</section>

A <section> with an accessible name is already a region, so once the tag was right the role had nothing left to do.

Checking it

Reading markup only tells me what I intended. I opened NVDA's Elements List (NVDA+F7) and filtered to Landmarks. It showed the implicit main plus three named regions: About me, Recent Work, and Contact. The hero was correctly absent and nothing was duplicated.

That check passed on the version with the invalid <address>. It tells me what a screen reader is given, not whether the markup is valid. Validity is a separate check, and the W3C validator is the one that covers it.

What's still open

Accessibility is at 100. I left SEO at 91 on purpose. What remains there depends mostly on the meta description and real content, which is better written once real copy and photos exist than guessed at now.


What does your accessibility pass cover that Lighthouse doesn't? I'm still building my own list and would like to see yours.

Part 3 of 7 in Build log

2 Comments

1 vote
1
🔥 Join developers growing publicly
Share your knowledge, build in public, and grow your developer presence with a global community.

More Posts

How I Built a React Portfolio in 7 Days That Landed ₹1.2L in Freelance Work

Dharanidharan - Feb 9

I’m a Senior Dev and I’ve Forgotten How to Think Without a Prompt

Karol Modelski - Mar 19

Attention-Free Score: How Domain Reports Show Which Pages Need Work

ApogeeWatcherverified - Sep 1

TypeScript Complexity Has Finally Reached the Point of Total Absurdity

Karol Modelski - Apr 23

Sovereign Intelligence: The Complete 25,000 Word Blueprint (Download)

Pocket Portfolio - Apr 1
chevron_left
7Posts
8Comments
5Connections
Frontend developer, still learning. I don't stop at the interface — I build the UI and the automatio... Show more

Related Jobs

Commenters (This Week)

8 comments
3 comments
1 comment

Contribute meaningful comments to climb the leaderboard and earn badges!