fix(a11y): heading semantics for two metric sub-page sections

The target-summary and coach-read strip tiles sit directly under a
metric sub-page's h1, alongside the stat strip that already renders its
title as an h2. Render their titles as real h2 headings too via the
existing titleAs prop, so heading navigation lands on each section. A
test pins that the dashboard exposes its page-level h1 through the
shared page header (no duplicate sr-only heading needed).
This commit is contained in:
Marc-André Bombeck committed 2026-07-18 10:27:55 +02:00
1 parent bfe23c17b3
commit 47a78896c5
3 files changed
+17

No files matched your search

@@ -43,4 +43,15 @@ describe("<DashboardHeader> — greeting", () => {
// The title itself stays.
expect(html).toContain("Dashboard");
});
// 2026-07-17 a11y audit (M4) — the dashboard is the app's landing surface
// and must expose a page-level `<h1>`. It comes from the shared
// `PageHeader` the header renders, so a screen-reader user navigating by
// heading lands on a real page anchor (not the Today hero's muted `<h2>`
// micro-label). No separate sr-only heading is added — that would double
// the `h1`.
it("renders a real page-level h1 with the dashboard title", () => {
const html = renderSSR(<DashboardHeader onQuickEntry={() => undefined} />);
expect(html).toMatch(/<h1[^>]*>Dashboard<\/h1>/);
});
});
@@ -123,6 +123,9 @@ export function CoachReadStrip({
<TileHeader
icon={Sparkles}
title={t("insights.coach.readStrip.label")}
// L3 — top-level section on every metric sub-page, sibling of the
// already-`h2` stat strip directly under the page `h1`.
titleAs="h2"
/>
</CardHeader>
<CardContent>
@@ -316,6 +316,9 @@ function TargetReferencePanel({
<TileHeader
icon={Target}
title={t("insights.target.heading")}
// L3 — top-level section on every metric sub-page, sibling of the
// already-`h2` stat strip directly under the page `h1`.
titleAs="h2"
right={
target.classification ? (
<TargetStatusPill