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 AA. 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. 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 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.
Top comments (0)