@samitouri / QOS-React-1 / commits / 278d318de1

[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();