Skip to content

feat: add headerSecondaryContent and hideHeaderText to ExpandableSection - #5088

Merged
gethinwebster merged 1 commit into
mainfrom
dev-v3-gethinw-expandable-section-header
Oct 30, 2026
Merged

gethinwebster merged 1 commit into
mainfrom
dev-v3-gethinw-expandable-section-header

Conversation

@gethinwebster

@gethinwebster gethinwebster commented Sep 30, 2026 •

Copy link
Copy Markdown
Member

Description

Adds two new props to ExpandableSection for the query-builder / observability header pattern, where the header row needs to carry an interactive control (or supporting text) beside the title:

  • headerSecondaryContent — content displayed next to the header text, such as an interactive control (a Select, a search Input) or description-style supporting text. Supported for the default and container variants. It renders outside the heading element, so interactive controls are not nested inside the heading (or inside the clickable expand button). When provided, it also becomes the trigger button's accessible description (aria-describedby), so a screen reader announces the header text as the name and the secondary content as the description — in both the visible and hidden header-text cases.
  • hideHeaderText — for headers whose only visible affordance is an interactive control. When set:
    • the header text is visually hidden but kept in the DOM for assistive technology;
    • no heading element is rendered, and the header text instead labels the expand/collapse trigger;
    • the whole section is exposed as a labelled landmark (role="region"), named by the header text (or by headerAriaLabel when provided), so the section remains navigable once its heading is gone.

To render the title without heading semantics, Header's headingTagOverride now also accepts div (and the previously-missing h6). This is the one public type change: HeaderProps.HeadingTag widens from h1–h5 to h1–h6 and div. It is additive — existing values are unchanged — but it is a change to the shared Header API, so the diff is a tightening only in the sense of accepting more, not fewer, values.

For the container variant, InternalHeader owns the layout of the secondary-content slot: it renders the content in the title row (outside the heading) and applies the packing styles only when the slot is used. The region role reaches the container root through a small internal __nativeAttributes passthrough on InternalContainer, because the container's base props only forward id/className/data-*.

How has this been tested?

  • New unit tests (ExpandableSection and Header), across the container, stacked, default and inline variants:
    • hideHeaderText renders no heading element and the trigger keeps the header text as its accessible name;
    • the section is exposed as role="region" labelled by the header text, with headerAriaLabel taking precedence; no region role when the text is visible;
    • headerSecondaryContent becomes the trigger's accessible description in both the visible and hidden cases, and no aria-describedby when it is absent;
    • an axe pass for a hidden-text container with a control (this fails if the region has no accessible name);
    • headingTagOverride accepts h6, and div renders the title with no heading semantics.
  • Full unit suite green, including the generated documenter / test-utils snapshot suites.
  • Manually verified rendering across the default and container variants at wide and narrow widths: the control sits beside single-line header text and aligns with the header actions; it wraps onto its own line under width pressure instead of being crushed; and with hideHeaderText the control sits where the title would be. A demo page covering all cases is included under pages/expandable-section/.
Review checklist

The following items are to be evaluated by the author(s) and the reviewer(s).

Correctness

  • Changes include appropriate documentation updates.
  • Changes are backward-compatible if not indicated, see CONTRIBUTING.md.
  • Changes do not include unsupported browser features, see CONTRIBUTING.md.
  • Changes were manually tested for accessibility, see accessibility guidelines.

Security

Testing

  • Changes are covered with new/existing unit tests?
  • Changes are covered with new/existing integration tests?

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@codecov

codecov Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.76%. Comparing base (64253e3) to head (adf7df8).
⚠️ Report is 11 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5088      +/-   ##
==========================================
+ Coverage   97.74%   97.76%   +0.02%     
==========================================
  Files        1000     1003       +3     
  Lines       32309    32572     +263     
  Branches    11948    12110     +162     
==========================================
+ Hits        31579    31843     +264     
+ Misses        724      684      -40     
- Partials        6       45      +39     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copilot AI left a comment

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.

🟡 Changes recommended

Secondary-content overflow and inconsistent navigation handling remain unresolved, and interaction-isolation coverage needs strengthening.

3 open issues
What changed in this PR

Adds secondary header content and visually hidden titles to ExpandableSection, supporting interactive query-builder headers while preserving accessible names and descriptions.

Changes:

  • Adds headerSecondaryContent and hideHeaderText, with labelled regions for hidden titles.
  • Extends shared header rendering and container attribute forwarding.
  • Adds accessibility tests, documentation snapshots, and a demo page.
File Description
src/​header/​styles.scss Styles secondary content and title alignment.
src/​header/​internal.tsx Renders secondary content outside the heading.
src/​header/​interfaces.ts Allows h6 and div title tags.
src/​header/​__tests__/​header.test.tsx Tests the new title-tag options.
src/​expandable-section/​styles.scss Adds secondary-content layout and hidden-title styles.
src/​expandable-section/​internal.tsx Connects new props and region labelling.
src/​expandable-section/​interfaces.ts Documents the new public props.
src/​expandable-section/​expandable-section-header.tsx Renders secondary content and updates trigger semantics.
src/​expandable-section/​expandable-section-container.tsx Forwards region attributes to section roots.
src/​expandable-section/​__tests__/​expandable-section.test.tsx Tests slots, hidden titles, and accessible descriptions.
src/​container/​internal.tsx Adds internal native-attribute forwarding.
src/​__tests__/​snapshot-tests/​__snapshots__/​documenter.test.ts.snap Updates generated API documentation snapshots.
pages/​expandable-section/​header-secondary-content.page.tsx Demonstrates interactive and supporting-text headers.

Review effort: Balanced

Comment thread src/header/styles.scss Resolved
@gethinwebster
gethinwebster force-pushed the dev-v3-gethinw-expandable-section-header branch 2 times, most recently from 1f34b14 to 6b4c743 Compare October 26, 2026 11:30
@gethinwebster

Copy link
Copy Markdown
Member Author

Rebased onto origin/main (now at b3106e4). The only conflict was in src/expandable-section/expandable-section-header.tsx, where mainline added the headerEndInfo feature in the same header regions this PR touches. Resolved by keeping both: the new headerSecondaryContent/hideHeaderText/secondaryContentId surface and mainline's headerEndInfo, including folding its headerEndInfo term into restrictClickableArea and rendering endInfo as a sibling of the header-main wrapper. Scoped suites + documenter/test-utils snapshots green (403 tests, 107 snapshots), tsc/eslint/prettier clean. CI re-running on 6b4c743.

Copilot AI left a comment

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.

Copilot AI left a comment

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.

🟡 Changes recommended

Truthiness checks mishandle valid numeric zero secondary content, causing missing content or an unwrapped zero without an accessible description; consistent presence checks and a zero-count regression test are needed.

1 open issue
1 resolved since last review

Review effort: Balanced

@gethinwebster
gethinwebster marked this pull request as ready for review October 29, 2026 13:26
@gethinwebster
gethinwebster requested a review from a team as a code owner October 29, 2026 13:26
@gethinwebster
gethinwebster requested review from a team, amanabiy and georgylobko and removed request for a team and georgylobko October 29, 2026 13:26
const [value, setValue] = useState('');
return (
<ExpandableSection
variant="container"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we add one more example with variant equals to container, and it has headerCounter, headerInfo and headerDescription? It would be easy to test and see how they work together.

Comment thread src/header/interfaces.ts Resolved
warnOnce(componentName, `The \`headerActions\` prop is only supported for the "container" and "default" variants.`);
}

if (headerSecondaryContent && !variantSupportsActions(variant) && isDevelopment) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we write a new function in utils, variantSupportsHeaderSecondaryContent, and use it here? This will make the code easier to understand. It will also tighten support to only the two variants, instead of ['container', 'compact', 'default', 'inline'], since headerSecondaryContent is supposed to be used only in these two variants. Something like this in utils

export function variantSupportsHeaderSecondaryContent(variant: InternalVariant) {
  return variantIsOneOf(variant, ['container', 'default']);
}

@gethinwebster
gethinwebster added this pull request to the merge queue Oct 30, 2026
Merged via the queue into main with commit f1e9235 Oct 30, 2026
68 of 69 checks passed
@gethinwebster
gethinwebster deleted the dev-v3-gethinw-expandable-section-header branch October 30, 2026 15:47

This branch was successfully deployed

3 active deployments
dev-pages-react18 — adf7df84 Deployed Oct 30, 2026 by gethinwebster via deploy (React 18) / deploy #3072
dev-pages-react16 — adf7df84 Deployed Oct 30, 2026 by gethinwebster via deploy (React 16) / deploy #3072
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants