Recently, I worked on fixing accessibility issues flagged by PageSpeed Insights across 13 pages of a production Next.js application called Accuguide — a platform that helps people discover accessible places and services.
In this article, I'll walk through the exact issues I found and how I fixed them. These are common mistakes that show up in many Next.js apps, so hopefully this saves you some debugging time.
The Problems PageSpeed Found
Running PageSpeed Insights on each page revealed 5 main categories of issues:
- Low contrast text
- Links relying on color alone to be distinguishable
- Invalid
<dl>element structure - Missing
<main>landmark - Heading elements skipping levels
Let's go through each one.
1. Low Contrast Text
PageSpeed flagged several elements with insufficient contrast between text and background colors.
The culprit: A global .secondary-text CSS class using text-slate-500 which doesn't meet WCAG AA contrast requirements on a light background.
/* ❌ Before */
.secondary-text {
@apply text-slate-500 dark:text-slate-400;
}
/* ✅ After */
.secondary-text {
@apply text-slate-700 dark:text-slate-300;
}
The same issue appeared in footer navigation links which were using text-slate-500:
/* ❌ Before */
className="py-2 font-semibold text-slate-500 text-sm"
/* ✅ After */
className="py-2 font-semibold text-slate-700 text-sm dark:text-slate-300"
Also fixed amber and green status messages in a location component:
/* ❌ Before */
Location access denied.
Showing results near you
/* ✅ After */
Location access denied.
Showing results near you
Rule of thumb: Always check contrast ratios using tools like WebAIM Contrast Checker. Aim for at least 4.5:1 for normal text.
2. Links Relying on Color Alone
PageSpeed flagged a "Netlify" link in the footer that was only distinguishable by its blue color — no underline, no other visual indicator.
/* ❌ Before */
Netlify
/* ✅ After */
Netlify
I also added a global underline to all anchor tags in the global CSS to prevent this across the entire app:
a {
@apply text-blue-600 hover:text-blue-500 dark:text-blue-400 dark:hover:text-blue-300 underline;
}
Why it matters: Users with color blindness or low vision cannot distinguish links from regular text if color is the only differentiator.
3. Invalid <dl> Element Structure
PageSpeed flagged this error:
<dl>'s do not contain only properly-ordered<dt>and<dd>groups
The app had a stats section using a <dl> element but the StatCard component inside it was using <div> and <p> tags instead of proper <dt> and <dd> tags.
/* ❌ Before */
export default function StatCard({ name, value, icon: Icon, color }) {
return (
{name}
)
}
/* ✅ After */
export default function StatCard({ name, value, icon: Icon, color }) {
return (
{name}
)
}
Two things changed:
-
<div>→<dd>for the value -
<p>→<dt>for the label - Added
aria-hidden="true"to the decorative icon
4. Missing <main> Landmark
PageSpeed flagged:
Document does not have a main landmark
Screen readers use landmark elements like <main>, <nav>, and <footer> to help users navigate pages efficiently. The app was missing a <main> wrapper around page content.
The fix was simple — add <main> in the root layout:
/* ❌ Before */
{children}
/* ✅ After */
{children}
One change, fixes every single page in the app at once.
5. Heading Elements Skipping Levels
PageSpeed flagged that footer section headings were <h3> elements appearing before any <h2> on several pages — skipping a heading level.
/* ❌ Before */
function FooterLinkList({ title, items }) {
return (
{title}
...
)
}
/* ✅ After */
function FooterLinkList({ title, items }) {
return (
{title}
...
)
}
Why it matters: Screen readers use heading levels to build a table of contents for the page. Skipping levels (h1 → h3) breaks the logical structure and confuses users.
Results
After these fixes, the accessibility score on PageSpeed Insights improved significantly across all 13 pages. The fixes were:
- Globally applied (footer, layout, global CSS) so they fixed every page at once
- Minimal code changes with maximum impact
- All following WCAG AA guidelines
Key Takeaways
- Run PageSpeed on every page — issues can vary page by page
- Fix global components first — footer, header, and layout fixes apply everywhere
-
text-slate-500is usually too light — bump totext-slate-700for body text -
Always add a
<main>landmark in your root layout -
Use
<dt>and<dd>correctly inside<dl>elements - Don't rely on color alone to distinguish links — always add underline
If you found this helpful, check out the Accuguide repo and my GitHub profile.
Have questions or spotted something I missed? Drop a comment below!
Top comments (0)