You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat: Add ref forwarding via nativeAttributes - #5049
Components accepting nativeAttributes now accept an optional ref alongside the attributes, merged with the component's own forwarded ref, so consumers can reach the underlying native element.
nativeAttributes lets you set attributes on the native element but gives no way to obtain the element itself. The only access today is whatever a component vends explicitly — typically focus() plus zero to two component-specific methods — which leaves text selection (selectionStart, setSelectionRange), imperative scrolling, measurement and observation (getBoundingClientRect, ResizeObserver), containment checks for focus and click-outside handling, and any third-party library that takes an element ref out of reach. The workarounds are a data-* attribute plus a DOM query, or an extra wrapper element — both brittle and coupled to internal structure.
The merge happens in the shared with-native-attributes wrapper, so every component already accepting nativeAttributes gains the ref at once. The component's own exposed ref is unaffected and continues to represent imperative actions on the composite component.
The ref is typed React.Ref<ET>, so object refs, callback refs and null all work.
Related links, issue #, if available: tracked internally (AWSUI ticket and approved contribution kick-off doc); no public issue.
How has this been tested?
Three new unit tests in src/internal/utils/__tests__/with-native-attributes.test.tsx cover an object ref, a callback ref, and merging a consumer ref with the component's internal ref; that suite passes 13/13.
✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.83%. Comparing base (fb5485b) to head (a9f0d02). ⚠️ Report is 35 commits behind head on main.
Components accepting nativeAttributes now accept an optional ref
alongside the attributes, merged with the component's own forwarded ref,
so consumers can reach the underlying native element for text selection,
imperative scrolling, measurement and observation, containment checks,
and integration with libraries that take an element ref.
The merge happens in the shared nativeAttributes wrapper, so every
component already accepting nativeAttributes gains the ref at once. The
component's own exposed ref is unaffected and continues to represent
imperative actions on the composite component.
The ref is typed React.Ref, so object refs, callback refs and null all
work.
BREAKING CHANGE: NativeAttributes takes the element as a required first
type parameter, e.g. NativeAttributes<HTMLButtonElement,
React.ButtonHTMLAttributes<HTMLButtonElement>>. It affects only
consumers who name the type explicitly; object literals passed to
component props are unchanged, and no compiled behaviour changes.
Every prop accepting nativeAttributes now states that a ref is accepted
and what it is for. Components that expose imperative ref methods also
carry a note that operating on the native element directly can bypass
the component's own behavior; display and layout components have no such
methods, so they carry only the first sentence.
The page now exercises a single Textarea carrying both the component ref
and a native ref: focus through each, an API reached only through the
native element, and observers attached to that element. The chained
native onChange reports inputType, data and selectionStart, none of
which the component's change detail carries.
Section headings are h2 to follow the page title's h1. Skipping to h3
fails the axe heading-order rule.
These props carry computed attributes injected by the existing Table's
td-element and th-element. They are now typed as the element's own
props rather than through the NativeAttributes generic, keeping the
previous shape of omitted children plus data-* attributes.
The reason will be displayed to describe this comment to others. Learn more.
Consider never as the fallback instead of HTMLElement. There's no mechanical advantage to lying about a type even if its case should never appear in practice — I swapped both fallback branches to never locally and tsc is clean across the repo, so nothing currently relies on it.
Separately, is there a reason NativeElement is exported? Nothing re-exports or consumes it, so it's only reachable by deep import.
The reason will be displayed to describe this comment to others. Learn more.
These cells never go through the native attributes util, so none of this type's behavior applies to them — nothing honors the ref it declares, and the merge/chain semantics aren't implemented here either. They're raw element buckets, so typing them as such would leave this type for the actual util. Same in the header cell.
This is where I started. I abandoned it and posted the breaking change instead, since that version was more complete and a better basis for discussing the tradeoffs. Fine by me as the direction.
The tradeoff is that you're locked in if the element and the attributes type ever need to diverge, though that shouldn't come up with standard element types, and a cast or declaration merge covers it in a pinch.
This is where I started. I abandoned it and posted the breaking change instead, since that version was more complete and a better basis for discussing the tradeoffs. Fine by me as the direction.
The tradeoff is that you're locked in if the element and the attributes type ever need to diverge, though that shouldn't come up with standard element types, and a cast or declaration merge covers it in a pinch.
If we'd need to change types in an existing component - that is going to be a breaking change anyways. For new implementations, if the need emerges - we should be always able to introduce an alternative NativeAttributes version with two generic args.
Non-blocking: Add screenshotArea={{}}, as required by .github/instructions/dev-pages.instructions.md:17–19. Without it, SimplePage renders the content without a ScreenshotArea (pages/app/templates.tsx:39–45). The prop provides an isolated screenshot target while keeping the page heading outside it.
Correct textarea ref guidance for text selection
src/textarea/interfaces.ts:64
Non-blocking: TextareaProps.Ref exposes only focus() (lines 89–94), so this guidance directs consumers to a selection method that does not exist. Recommend the component ref for focus and the native ref for text selection, then regenerate the corresponding documentation snapshot.
🧠 Review effort: Balanced
Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.
The reason will be displayed to describe this comment to others. Learn more.
Left comments on this before. It's not using any of the native attributes ref features or semantics. This is the true type for this, not just a convenient usage of some other type. If the native attributes type changes because the utils for native attributes changed, this should not.
The reason will be displayed to describe this comment to others. Learn more.
Can we filter out ref in processAttributes so it doesn't get duplicated with mergedRef? Right now the ref from nativeAttributes falls into the else branch and gets copied into the returned object, so it ends up spread onto here. It works because the explicit ref={mergedRef} comes last, but that leaves correctness depending on the attribute order. Stripping ref out of the processedAttributes would help us avoid the dependency on order.
The reason will be displayed to describe this comment to others. Learn more.
The attribute order is intentional, not random and it should be tested behavior. It's a feature of the language. You can pull it out but its order dependency is the same as any objects keys.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Components accepting
nativeAttributesnow accept an optionalrefalongside the attributes, merged with the component's own forwarded ref, so consumers can reach the underlying native element.nativeAttributeslets you set attributes on the native element but gives no way to obtain the element itself. The only access today is whatever a component vends explicitly — typicallyfocus()plus zero to two component-specific methods — which leaves text selection (selectionStart,setSelectionRange), imperative scrolling, measurement and observation (getBoundingClientRect,ResizeObserver), containment checks for focus and click-outside handling, and any third-party library that takes an element ref out of reach. The workarounds are adata-*attribute plus a DOM query, or an extra wrapper element — both brittle and coupled to internal structure.The merge happens in the shared
with-native-attributeswrapper, so every component already acceptingnativeAttributesgains the ref at once. The component's own exposed ref is unaffected and continues to represent imperative actions on the composite component.The ref is typed
React.Ref<ET>, so object refs, callback refs andnullall work.Related links, issue #, if available: tracked internally (AWSUI ticket and approved contribution kick-off doc); no public issue.
How has this been tested?
Three new unit tests in
src/internal/utils/__tests__/with-native-attributes.test.tsxcover an object ref, a callback ref, and merging a consumer ref with the component's internal ref; that suite passes 13/13.