[DOM] Blur portaled Fragment focus targets (#37161)
focus() passes through portals as it attempts focus down the fiber tree. blur() exited early based on a containment check, causing it to stop at portals. The result could be a focused element that cannot be blurred. Follow up to https://github.com/react/react/pull/37125, which made blur() apply recursively to be consistent with focus().
Jack Pope committed
Aug 11, 2026 at 18:05 UTC
278d318de1b40d6c96ff4e39ca0a2702ab133521
2 files changed
+36
-6
packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js
+1
-6
@@ -3224,7 +3224,6 @@ function collectChildren(child: Fiber, collection: Array<Fiber>): boolean {
3224
}
3225
// $FlowFixMe[prop-missing]
3226
FragmentInstance.prototype.blur = function (this: FragmentInstanceType): void {
3227
- // Early exit if activeElement is not within the fragment's parent
3227
const parentHostFiber = getFragmentParentInstanceOrContainerFiber(
3228
this._fragmentFiber,
3229
);
@@ -3239,13 +3238,9 @@ FragmentInstance.prototype.blur = function (this: FragmentInstanceType): void {
3238
parentInstanceOrContainer,
3239
);
3240
const activeElement = ownerDocument.activeElement;
3242
- if (
3243
- activeElement === null ||
3244
- !parentInstanceOrContainer.contains(activeElement)
3245
- ) {
3241
+ if (activeElement === null) {
3242
return;
3243
}
3248
-
3244
traverseFragmentInstancesAndTextInstances(
3245
this._fragmentFiber,
3246
blurActiveElementWithinFragment,
packages/react-dom/src/__tests__/ReactDOMFragmentRefs-test.js
+35
@@ -484,6 +484,41 @@ describe('FragmentRefs', () => {
484
expect(document.activeElement).toEqual(document.body);
485
});
486
487
+ // @gate enableFragmentRefs
488
+ it('removes focus from a portaled element inside of the Fragment', async () => {
489
+ const fragmentRef = React.createRef();
490
+ const root = ReactDOMClient.createRoot(container);
491
+
492
+ function Test() {
493
+ return (
494
+ <div>
495
+ <Fragment ref={fragmentRef}>
496
+ {createPortal(
497
+ <div>
498
+ <input id="portaled-input" />
499
+ </div>,
500
+ document.body,
501
+ )}
502
+ </Fragment>
503
+ </div>
504
+ );
505
+ }
506
+
507
+ await act(() => {
508
+ root.render(<Test />);
509
+ });
510
+
511
+ await act(() => {
512
+ fragmentRef.current.focus();
513
+ });
514
+ expect(document.activeElement.id).toEqual('portaled-input');
515
+
516
+ await act(() => {
517
+ fragmentRef.current.blur();
518
+ });
519
+ expect(document.activeElement).toEqual(document.body);
520
+ });
521
+
522
// @gate enableFragmentRefs
523
it('does not remove focus from elements outside of the Fragment', async () => {
524
const fragmentRefA = React.createRef();