Skip to content

fix(core): forward pass-through attributes on native-root components - #3871

Merged
cixzhang merged 1 commit into
mainfrom
fix/native-rest-forwarding
Jul 12, 2026
Merged

cixzhang merged 1 commit into
mainfrom
fix/native-rest-forwarding

Conversation

@cixzhang

Copy link
Copy Markdown
Contributor

What

Several native-root components extend BaseProps (or a Pick/Omit of it) but never captured ...rest, so any pass-through attribute they didn't explicitly consume — data-testid, aria-*, id, tabIndex, neutral event handlers — was silently dropped. This fixes the same "rest-forwarding / prop-collision" bug class addressed in #3738 / #3852 / #3869 for a cluster of non-Chat native-root components.

Each fixed component now captures ...rest and spreads it onto its primary rendered element, placed after mergeProps(...) (so className/style/xstyle still combine) and before the component-owned contract props (role, owned aria-*, computed id, data-testid) so the component keeps precedence over its own contract.

Components fixed (with the previously-dropped attributes)

Component Primary element Was dropping
Switch root <div> (switch-field) data-testid, aria-*, id, all neutral pass-throughs
Pagination root <nav> everything except the manually-forwarded data-testid (e.g. id, aria-describedby)
RadioListItem root <div> everything except the manually-forwarded data-testid; also xstyle/className/style were declared on the interface but never applied — now merged
SideNavSection root <div> everything except data-testid/className/style (e.g. id, aria-describedby)
TableHeader / TableBody / TableFooter <thead> / <tbody> / <tfoot> className, style, data-testid, id, aria-* — only xstyle was applied
TopNavMegaMenuFeaturedCard root <div> xstyle, className, style, data-testid, id, aria-* (none were consumed or forwarded)

Each fix ships with a colocated test asserting a data-testid / id / aria-* now reaches the DOM.

Precedence details

  • className / style / xstyle combine via mergeProps.
  • Component-owned contract props (role, owned aria-labelledby, data-testid) are set after {...rest} so the component wins.
  • No component in this cluster implements its own handler on the primary element that collides with a consumer handler, so no composeEventHandlers composition was needed here.

Needs Review (intentionally NOT changed)

  • MetadataListItem — renders two different shapes depending on layout: a single wrapper <div> in stacked mode, but a bare <dt> + <dd> Fragment in inline mode (where data-testid is deliberately split into -label/-value suffixes). There is no single primary element in inline mode, so forwarding arbitrary ...rest would behave inconsistently between the two layouts. Left as-is pending a decision on where inline-mode pass-throughs should land.
  • TabMenu — its props interface is intentionally narrow: Pick<BaseProps<HTMLButtonElement>, 'xstyle' | 'className' | 'style'> only. It does not expose data-testid or aria-* in its public contract, so there is no pass-through surface to forward and adding ...rest would widen the API beyond what the type declares. Left as-is.

Testing

  • pnpm -F @astryxdesign/core typecheck — clean
  • vitest run across all affected dirs (Switch, Pagination, RadioList, SideNav, Table, TopNav) — 634 passed
  • pnpm -F @astryxdesign/core build — succeeds
  • eslint on all changed files — clean

Credit: @cixzhang

…omponents

Switch, Pagination, RadioListItem, SideNavSection, TableHeader/Body/Footer,
and TopNavMegaMenuFeaturedCard captured no ...rest, so pass-through attributes
(data-testid, aria-*, id, etc.) not explicitly consumed were silently dropped.

Capture ...rest and spread it onto the primary rendered element after
mergeProps() and before component-owned contract props (role, owned aria,
data-testid) so className/style/xstyle still combine and the component keeps
precedence over its contract props. RadioListItem's manual data-testid-only
forward is replaced with a full ...rest spread and now also merges
xstyle/className/style.
@cixzhang
cixzhang requested a review from imdreamrunner as a code owner July 12, 2026 07:54
@vercel

vercel Bot commented Jul 12, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
astryx Ready Ready Preview, Comment Jul 12, 2026 7:56am

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Jul 12, 2026
github-actions Bot added a commit that referenced this pull request Jul 12, 2026
@github-actions

Copy link
Copy Markdown
Contributor

PR Analysis Report

📚 Storybook Preview

View Storybook for this PR
GitHub Pages may take up to a minute to hydrate after deploy.

🧪 Sandbox Preview

View Sandbox for this PR
GitHub Pages may take up to a minute to hydrate after deploy.

No new or modified components detected.

Bundle Size Summary

Package Size (ESM) Size (CJS) Gzipped
@astryxdesign/core N/A 4.6KB 0B

Accessibility Audit

Status: No accessibility violations detected.


Generated by PR Enrichment workflow | Storybook | Sandbox | View full report

@thedjpetersen thedjpetersen 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.

Approved.

@cixzhang
cixzhang merged commit 76a53e4 into main Jul 12, 2026
22 checks passed
@github-actions
github-actions Bot deleted the fix/native-rest-forwarding branch July 13, 2026 07:13

This branch was successfully deployed

1 active deployment
Preview — 20f0a731 Deployed Jul 12, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:core CLA Signed This label is managed by the Meta Open Source bot. type:fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants