Skip to content

fix(a11y): WCAG 2.4.6 — add descriptive headings and section labels#39241

Open
Aitema-gmbh wants to merge 3 commits intoapache:masterfrom
Aitema-gmbh:fix/wcag-2.4.6-headings-labels
Open

fix(a11y): WCAG 2.4.6 — add descriptive headings and section labels#39241
Aitema-gmbh wants to merge 3 commits intoapache:masterfrom
Aitema-gmbh:fix/wcag-2.4.6-headings-labels

Conversation

@Aitema-gmbh
Copy link
Copy Markdown
Contributor

SUMMARY

Implements WCAG 2.1 criterion 2.4.6 (Headings and Labels, Level AA).

  • Add screen-reader-only <h1> headings to all main pages (Home, Dashboards, Charts, SQL Lab, Datasets, Chart Creation)
  • Add <h2> section headings for Dashboard content areas and filter panels
  • Render chart titles as semantic <h2> elements
  • Use sr-only CSS pattern to maintain visual design

TESTING INSTRUCTIONS

  1. Install HeadingsMap browser extension
  2. Navigate through main pages → each should have an h1
  3. Open a Dashboard → chart sections should have h2 headings
  4. Screen reader should announce page structure correctly

ADDITIONAL INFORMATION

- Add visually-hidden h1 with dashboard name to Dashboard Header
- Add visually-hidden h1 with chart name to Explore/Chart Header
- Add visually-hidden h1 to Home page and ChartCreation page
- Fix ChartCreation heading hierarchy (h3 → h2)
- Add sr-only h2 section headings for Filters and Dashboard Content areas
- All headings use clip/sr-only pattern for screen readers only

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@bito-code-review
Copy link
Copy Markdown
Contributor

bito-code-review Bot commented Apr 9, 2026

Code Review Agent Run #effbd8

Actionable Suggestions - 0
Review Details
  • Files reviewed - 5 · Commit Range: 14d7b43..14d7b43
    • superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx
    • superset-frontend/src/dashboard/components/Header/index.tsx
    • superset-frontend/src/explore/components/ExploreChartHeader/index.tsx
    • superset-frontend/src/pages/ChartCreation/index.tsx
    • superset-frontend/src/pages/Home/index.tsx
  • Files skipped - 0
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers a full AI review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

@dosubot dosubot Bot added change:frontend Requires changing the frontend design:accessibility Related to accessibility standards labels Apr 9, 2026
{renderDraggableContent}
</Droppable>
</StyledHeader>
<SrOnlyH2>{t('Dashboard content')}</SrOnlyH2>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The new filters heading is only rendered in the vertical-filter branch, so dashboards using the horizontal filter bar still expose an unlabeled filter section. Add a screen-reader heading for the horizontal orientation as well so both filter-bar modes have equivalent section labeling. [logic error]

Severity Level: Major ⚠️
- ⚠️ Horizontal filter bar lacks accessible section heading.
- ⚠️ WCAG 2.4.6 compliance inconsistent across filter orientations.
- ⚠️ Screen reader users lose clear filters region label.
Suggested change
<SrOnlyH2>{t('Dashboard content')}</SrOnlyH2>
{showFilterBar &&
filterBarOrientation === FilterBarOrientation.Horizontal && (
<SrOnlyH2>{t('Filters')}</SrOnlyH2>
)}
Steps of Reproduction ✅
1. Open any dashboard route that renders `DashboardPage` at
`superset-frontend/src/dashboard/containers/DashboardPage.tsx:30-55`, which lazily loads
and renders `<DashboardBuilder />` inside `<DashboardContainer>`.

2. Configure or select a dashboard whose metadata has `filter_bar_orientation` set to
horizontal so that `hydrate.ts` maps it to `FilterBarOrientation.Horizontal` (see
`superset-frontend/src/dashboard/actions/hydrate.ts:22-24`, where `filterBarOrientation`
is populated from `metadata.filter_bar_orientation`).

3. Ensure native filters are enabled and the dashboard is in view mode (not edit mode), so
in `DashboardBuilder` the `showFilterBar` flag at
`superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx:159-171`
is `true` and `filterBarOrientation === FilterBarOrientation.Horizontal`, causing the
horizontal `<FilterBar>` to render in `headerContent` at lines 219-229.

4. Inspect the accessible heading structure with a tool like HeadingsMap or a screen
reader: for horizontal orientation there is no `<SrOnlyH2>Filters</SrOnlyH2>` heading near
the horizontal `<FilterBar>` (the only sr-only heading in this area is `Dashboard content`
at line 667), while the vertical filter bar path at lines 64-85 renders
`<SrOnlyH2>{t('Filters')}</SrOnlyH2>` before the vertical `ResizableSidebar`, resulting in
the filters section being unlabeled only in the horizontal-mode dashboards.
Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx
**Line:** 667:667
**Comment:**
	*Logic Error: The new filters heading is only rendered in the vertical-filter branch, so dashboards using the horizontal filter bar still expose an unlabeled filter section. Add a screen-reader heading for the horizontal orientation as well so both filter-bar modes have equivalent section labeling.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
👍 | 👎

Comment on lines 630 to 633
{isVerticalFilterBarVisible && (
<>
<SrOnlyH2>{t('Filters')}</SrOnlyH2>
<ResizableSidebar
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Architect Review — HIGH

The new sr-only "Filters" section heading is only rendered when the filter bar orientation is vertical; when dashboards use the horizontal filter bar, the filter area has no equivalent heading, so the WCAG 2.4.6 heading/label improvement is applied inconsistently.

Suggestion: Add an equivalent semantic heading for the horizontal filter bar path (e.g., render an sr-only

before the when FilterBarOrientation.Horizontal is active) so both orientations expose a labeled filters section to assistive technologies.

@bito-code-review
Copy link
Copy Markdown
Contributor

bito-code-review Bot commented Apr 9, 2026

Code Review Agent Run #df80d9

Actionable Suggestions - 0
Review Details
  • Files reviewed - 1 · Commit Range: 14d7b43..179d7b0
    • superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx
  • Files skipped - 0
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers a full AI review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements WCAG 2.4.6 (Headings and Labels) improvements in Superset’s main UI surfaces by introducing semantic (mostly screen-reader-only) page/section headings to improve navigability for assistive technologies.

Changes:

  • Add screen-reader-only <h1> headings to key pages (Home, Explore, Dashboard, Chart Creation).
  • Add screen-reader-only <h2> section headings for Dashboard areas (Filters, Dashboard content).
  • Adjust Chart Creation heading markup to support the new heading structure.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
superset-frontend/src/pages/Home/index.tsx Adds SR-only <h1> “Home” heading.
superset-frontend/src/pages/ChartCreation/index.tsx Adds SR-only <h1> and introduces a visible heading element for the page title.
superset-frontend/src/explore/components/ExploreChartHeader/index.tsx Adds SR-only <h1> derived from slice name / fallback label.
superset-frontend/src/dashboard/components/Header/index.tsx Adds SR-only <h1> for the dashboard title.
superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx Adds SR-only <h2> headings for Filters and Dashboard content areas.

Comment on lines +87 to +92
const SrOnlyH1 = styled.h1`
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SrOnlyH1 visually-hidden CSS block is duplicated across several pages/components in this PR. Consider extracting a shared VisuallyHidden/SrOnly component or CSS helper to keep the hiding behavior consistent everywhere.

Copilot uses AI. Check for mistakes.
Comment on lines +180 to +184
const SrOnlyH1 = styled.h1`
position: absolute;
width: 1px;
height: 1px;
padding: 0;
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SrOnlyH1 visually-hidden CSS block is duplicated across several pages/components in this PR. Consider extracting a shared VisuallyHidden/SrOnly component or CSS helper so this pattern is implemented consistently and maintained in one place.

Copilot uses AI. Check for mistakes.
hidden={isReport}
/>
<>
<SrOnlyH2>{t('Filters')}</SrOnlyH2>
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SrOnlyH2 is rendered even when the horizontal FilterBar is hidden (hidden={isReport}). This leaves a “Filters” heading in the accessibility tree for report views where the filters UI is not shown. Consider only rendering the heading when the filter bar is actually visible (e.g., gate it on !isReport or the same hidden condition).

Suggested change
<SrOnlyH2>{t('Filters')}</SrOnlyH2>
{!isReport && <SrOnlyH2>{t('Filters')}</SrOnlyH2>}

Copilot uses AI. Check for mistakes.
Comment on lines 634 to 637
{isVerticalFilterBarVisible && (
<>
<SrOnlyH2>{t('Filters')}</SrOnlyH2>
<ResizableSidebar
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In vertical filter-bar mode this adds an extra “Filters” heading outside the sidebar, while renderChild already renders its own SrOnlyH2. This creates duplicate “Filters” headings, and when isReport hides the actual filter panel the outer heading still remains, which is misleading for screen readers. Suggest removing the outer heading (or gating it with the same hidden condition) and keeping a single heading tied to the visible panel.

Copilot uses AI. Check for mistakes.
Comment on lines +354 to +359
const SrOnlyH2 = styled.h2`
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SrOnlyH2 visually-hidden CSS block is duplicated across several pages/components in this PR. To avoid future drift (and make future a11y fixes easier), consider extracting a shared VisuallyHidden/SrOnly component or CSS helper and reusing it here instead of redefining the same styles inline.

Copilot uses AI. Check for mistakes.
Comment on lines +79 to +83
const SrOnlyH1 = styled.h1`
position: absolute;
width: 1px;
height: 1px;
padding: 0;
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SrOnlyH1 visually-hidden CSS block is duplicated across several pages/components in this PR. Consider extracting a shared VisuallyHidden/SrOnly component or CSS helper to prevent divergence and reduce repetitive CSS definitions.

Copilot uses AI. Check for mistakes.
Comment on lines +161 to +165
const SrOnlyH1 = styled.h1`
position: absolute;
width: 1px;
height: 1px;
padding: 0;
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SrOnlyH1 visually-hidden CSS block is duplicated across several pages/components in this PR. Consider extracting a shared VisuallyHidden/SrOnly component or CSS helper instead of redefining the same CSS here.

Copilot uses AI. Check for mistakes.
Comment on lines +344 to +345
<SrOnlyH1>{t('Create a new chart')}</SrOnlyH1>
<h2 aria-hidden="true">{t('Create a new chart')}</h2>
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This renders the same title twice (a screen-reader-only h1 plus a visible h2 that is aria-hidden). If the intent is a single semantic page heading without altering visuals, consider rendering just one heading element (e.g., make the visible title the h1 and style it to match) to avoid duplicated DOM content and reliance on aria-hidden for visible text.

Suggested change
<SrOnlyH1>{t('Create a new chart')}</SrOnlyH1>
<h2 aria-hidden="true">{t('Create a new chart')}</h2>
<h1>{t('Create a new chart')}</h1>

Copilot uses AI. Check for mistakes.
@rusackas
Copy link
Copy Markdown
Member

rusackas commented Apr 20, 2026

Bugs/concerns:

  1. Duplicate "Filters" h2 in the vertical filter bar path. Look at the vertical case: the PR adds Filters outside ResizableSidebar, and also inside renderChild (line 583 before the ) — which itself is rendered inside that same ResizableSidebar. Result: screen readers will announce "Filters, heading level 2" twice in a row when the vertical filter bar is visible. One or the other should be removed.
  2. DRY / code smell. The identical SrOnlyH1 / SrOnlyH2 styled block is copy-pasted into 5 files. PR 39240 already creates src/components/Accessibility/ — a shared VisuallyHidden or SrOnly component with a configurable as prop belongs there.
  3. ChartCreation double-renders the title unnecessarily:
    - <SrOnlyH1>{t('Create a new chart')}</SrOnlyH1>
    - <h2 aria-hidden="true">{t('Create a new chart')}</h2>
  4. Two copies of the same text. Cleaner: make the visible heading <h1> and style it to look like the old h3/h2. Also the StyledContainer CSS was updated from h3 → h2, which changes the visual level name — worth verifying no other CSS in the app targets ChartCreation h3.
  5. Indentation. The new fragment wrapper around ResizableSidebar leaves the inner at the pre-wrap indent level. Prettier will likely reformat, but it signals the change wasn't fully linted locally.
  6. No tests added - these would be helpful here.

Aitema-gmbh pushed a commit to Aitema-gmbh/superset that referenced this pull request May 7, 2026
…and SkipLink tests

Addresses three review polish points on PR apache#39240:

- Add `<main>` landmark via role="main" on Layout.Content so the skip target
  has semantic meaning beyond just an id anchor.
- Scope `outline: none` to `[tabindex='-1']:focus` on the same element so the
  programmatic-focus moment doesn't render a stray browser outline. The rule
  is intentionally tight: only fires when SkipLink has dynamically attached
  tabindex='-1', does not affect any other element.
- Add `VisuallyHidden` reusable component (with `as` prop for h1, h2, etc.)
  in the Accessibility folder. This is the shared sr-only primitive that
  WCAG 2.4.6 (PR apache#39241) needs to deduplicate across pages.

Tests added for both SkipLink and VisuallyHidden covering default behavior,
custom props, focus management, and tabindex cleanup on blur.
…isuallyHidden

Addresses six review concerns on PR apache#39241:

- Bug: vertical filter bar rendered "Filters" h2 twice (once outside the
  ResizableSidebar wrapper, once inside renderChild before StickyPanel).
  Screen readers announced the heading twice. Removed the outer one and
  kept the inner one — that's the semantically correct location since it
  scopes to the FiltersPanel container.
- DRY: replaced the SrOnlyH1 / SrOnlyH2 styled blocks copy-pasted into
  five files with the shared VisuallyHidden component (with `as` prop).
  Component lives in src/components/Accessibility/ alongside SkipLink.
- ChartCreation no longer renders the title twice (sr-only h1 plus visible
  aria-hidden h2). Now uses a single visible h1 styled to match the previous
  h2 size, with the StyledContainer CSS rule updated from h2 to h1.
- StyledContainer CSS audit: no other selectors target ChartCreation h3
  anywhere in src — the previous h3 → h2 → h1 chain is contained.
- Indentation in DashboardWrapper return cleaned up (no fragment wrapper
  needed after removing the duplicate h2).
- Tests added for VisuallyHidden covering default rendering, custom `as`,
  prop forwarding, and the sr-only style invariant.

This PR depends on apache#39240 introducing the same Accessibility folder; once
that merges first, the duplicate component definition will rebase away.
@pull-request-size pull-request-size Bot added size/L and removed size/M labels May 7, 2026
@netlify
Copy link
Copy Markdown

netlify Bot commented May 7, 2026

Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit 2679d1a
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/69fcb96bf28fbb0008e2cc7a
😎 Deploy Preview https://deploy-preview-39241--superset-docs-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@bito-code-review
Copy link
Copy Markdown
Contributor

bito-code-review Bot commented May 8, 2026

Code Review Agent Run #ae7f96

Actionable Suggestions - 0
Additional Suggestions - 1
  • superset-frontend/src/components/Accessibility/VisuallyHidden.test.tsx - 1
    • Incomplete Test Assertion · Line 50-54
      The test 'applies clip-rect style for screen-reader-only behavior' only checks position: 'absolute', but the name implies verification of the clip-rect technique for visual hiding. Per BITO rule [6262], tests should assert the actual behavior claimed. Add a check for the clip property.
      Code suggestion
       @@ -53,1 +53,2 @@
      -  expect(el).toHaveStyle({ position: 'absolute' });
      +  expect(el).toHaveStyle({ position: 'absolute' });
      +  expect(el).toHaveStyle({ clip: 'rect(0, 0, 0, 0)' });
Review Details
  • Files reviewed - 8 · Commit Range: 179d7b0..2679d1a
    • superset-frontend/src/components/Accessibility/VisuallyHidden.test.tsx
    • superset-frontend/src/components/Accessibility/VisuallyHidden.tsx
    • superset-frontend/src/components/Accessibility/index.tsx
    • superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx
    • superset-frontend/src/dashboard/components/Header/index.tsx
    • superset-frontend/src/explore/components/ExploreChartHeader/index.tsx
    • superset-frontend/src/pages/ChartCreation/index.tsx
    • superset-frontend/src/pages/Home/index.tsx
  • Files skipped - 0
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers a full AI review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

change:frontend Requires changing the frontend design:accessibility Related to accessibility standards size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants