fix: adaptive-header a11y — single h1, breadcrumb nav, heading order (#1994)#2199
fix: adaptive-header a11y — single h1, breadcrumb nav, heading order (#1994)#2199larryro wants to merge 2 commits into
Conversation
…1994) Several adaptive-header accessibility fixes from a keyboard/AT sweep: - Duplicate page-title h1: the title is rendered twice (desktop root + mobile slot mirror). Mark the breakpoint-inactive copy aria-hidden so exactly one h1 stays in the accessibility tree even where the responsive display:none isn't applied. - Heading order: the chat message-history label was an sr-only h2 that preceded the page h1 — make it a plain sr-only span still referenced via aria-labelledby, so it leaves the heading outline. EmptyState now renders its title as an h2 by default (was h3), so a list empty state under a page h1 no longer skips h1->h3; level stays overridable via headingLevel. - Breadcrumb: the agent detail trail was a single h1 of links with the parent crumb auto-tagged aria-current=page by TanStack's Link. Render it as nav[aria-label] > ol; the parent uses activeOptions={{ exact: true }} to drop the stale aria-current, and only the leaf (the page's single h1) carries aria-current=page. Closes #1994
The previous pass flipped EmptyState's default heading level to h2 to stop the
documents empty state skipping h1->h3. But that regressed every table that
lives under a settings section h2 (e.g. Teams): its 'No <x> yet' title became a
second h2, so the settings-depth e2e (getByRole('heading', { name: 'Teams',
level: 2 })) hit a strict-mode violation matching both the section heading and
the empty-state title.
Restore EmptyState's default to h3 (correct for the common in-section / in-dialog
case) and thread an explicit headingLevel through DataTable -> DataTableEmptyState.
Only the documents table — which sits directly under the page h1 'Knowledge'
with no intervening section heading — opts into headingLevel=2, fixing the
h1->h3 skip without making any in-section empty state a duplicate h2.
Desk review — #1994 adaptive-header a11yVerdict: NOT READY — CHANGES REQUIRED. The PR correctly fixes the duplicate- Evidence
What's correct (credit)
Blocking findings1. (BLOCKING) The reported chat 2. (BLOCKING) The
Non-blocking (should address)
NOT READY — CHANGES REQUIRED:
|
Summary
Fixes the adaptive-header accessibility issues reported in #1994 (keyboard/AT sweep):
h1— the page title is rendered twice (desktopAdaptiveHeaderRoot+ the mobileAdaptiveHeaderSlotmirror). The breakpoint-inactive copy is nowaria-hidden(driven byuseIsMobile), so exactly oneh1stays in the accessibility tree even where the responsivedisplay:noneisn't applied.role="log"message-history label was ansr-onlyh2that preceded the pageh1(e.g. the welcome heading). It's now a plainsr-only<span>still referenced viaaria-labelledby, so it leaves the heading outline.EmptyStatenow renders its title as anh2by default (wash3), so a list empty state directly under a pageh1no longer skipsh1→h3. The level is overridable via a newheadingLevelprop.h1of links, with the parent "Agents" crumb auto-taggedaria-current="page"by TanStack's<Link>. It's now a semanticnav[aria-label] > ol; the parent link usesactiveOptions={{ exact: true }}to drop the stalearia-current, and only the leaf (the page's singleh1) carriesaria-current="page".Tests
packages/uiempty-state: defaulth2+headingLeveloverride.platformadaptive-header: only oneh1exposed when the title is mirrored into the slot.tsc --noEmitgreen for both@tale/platformand@tale/ui; oxlint clean.Closes #1994