Accessibility Improvements: Fixing Dual h1 Headings and ARIA Tab Patterns
Accessibility Improvements: Fixing Dual h1 Headings and ARIA Tab Patterns
Release: v1.0.437 · Control: A11Y-07 · Category: Screen Reader
This release resolves two structural accessibility issues identified under control A11Y-07 that affected screen reader users on the dashboard and settings pages.
What Was Wrong
Two <h1> Elements on the Dashboard
Every web page should have exactly one <h1> — the top-level heading that defines the document's main topic. The dashboard was rendering two:
- A
<h1>insideDashboardHeaderdisplaying the page title sourced from thePAGE_TITLESlookup. - A second
<h1>insidedashboard/page.tsxfor the personalised welcome message.
When a screen reader user navigates by headings (a common technique to quickly scan a page), two competing <h1> elements produce a confusing and ambiguous document outline. Automated accessibility auditors also flag this as a failure.
Settings Tabs Announced as Plain Buttons
The SettingsTabs component rendered its navigation tabs as <button> elements with no ARIA semantics. Without the ARIA tab pattern in place:
- Screen readers announced each tab as a generic button, giving no indication it was part of a tab interface.
- There was no programmatic signal for which tab was currently active (
aria-selected). - Assistive technologies could not expose the relationship between a tab control and the panel it reveals.
What Changed
Single <h1> — dashboard-header.tsx
The page title rendered by DashboardHeader has been demoted from <h1> to a <p> or <span> element. Its visual appearance (size, weight, colour) is unchanged — only the semantic element has been corrected. The <h1> on each dashboard page now belongs solely to the main content area, giving every page a clear, unambiguous top-level heading.
// Before
<h1 className="text-lg font-semibold">{title}</h1>
// After
<p className="text-lg font-semibold">{title}</p>
Full ARIA Tab Pattern — SettingsTabs
The settings tab strip now implements the complete ARIA Authoring Practices tab pattern:
// Tab container
<div role="tablist">
// Each tab button
<button
role="tab"
aria-selected={active === tab.id}
id={`tab-${tab.id}`}
>
{tab.label}
</button>
</div>
// Each panel
<div
role="tabpanel"
aria-labelledby={`tab-${tab.id}`}
>
{/* panel content */}
</div>
With these changes:
- Screen readers announce the widget as a tab list and identify each item as a tab.
aria-selectedistrueon the active tab andfalseon all others, so the current state is always communicated.aria-labelledbyon each panel links it back to its tab, so navigating into a panel reveals which tab controls it.
Who Is Affected
These fixes benefit:
- Users who navigate with a screen reader (NVDA, JAWS, VoiceOver, TalkBack).
- Users who navigate by keyboard only.
- Automated accessibility audit tools (axe, Lighthouse, Wave) — both issues were reportable failures.
No visual changes have been made. The dashboard and settings pages look identical to previous versions.
Affected Files
| File | Change |
|---|---|
src/app/dashboard/dashboard-header.tsx | <h1> → <p> for page title in DashboardHeader; role="tablist", role="tab", aria-selected, role="tabpanel", aria-labelledby added to SettingsTabs |