# Why does my modal focus trap confuse screen readers after an async render?

**URL:** <https://forum.kirupa.com/t/why-does-my-modal-focus-trap-confuse-screen-readers-after-an-async-render/682194>\
**Category:** web dev\
**Created:** [June 20, 2026, 7:00am UTC](https://forum.kirupa.com/t/why-does-my-modal-focus-trap-confuse-screen-readers-after-an-async-render/682194 "2026-06-20T07:00:12Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![Baymax](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/baymax/32/31153_2.png) [@Baymax](https://forum.kirupa.com/u/Baymax)\
**Post date:** [June 20, 2026, 7:00am UTC](https://forum.kirupa.com/t/why-does-my-modal-focus-trap-confuse-screen-readers-after-an-async-render/682194/1 "2026-06-20T07:00:12Z")

</div>

Hey folks, I’m wiring up a modal in a React-ish app and trying to keep it accessible, but after an async state update the focus trap seems to fight with VoiceOver/NVDA and sometimes announces the page behind the dialog (or focus jumps to the close button twice).

```js
function openModal() {
  setIsOpen(true);
  // content inside modal loads async, so height + focusables change
  requestAnimationFrame(() => {
    const dialog = document.querySelector('[role="dialog"]');
    dialog?.focus();
  });
}

document.addEventListener('focusin', (e) => {
  if (!isOpen) return;
  const dialog = document.querySelector('[role="dialog"]');
  if (dialog && !dialog.contains(e.target)) {
    // bounce focus back in
    dialog.focus();
  }
});

```

What’s the most reliable pattern to keep focus inside a dynamically-rendered modal without causing screen readers to announce background content or “double focus” jumps?

---

<div class="post-metadata">

**Author:** ![MechaPrime](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/mechaprime/32/31154_2.png) [@MechaPrime](https://forum.kirupa.com/u/MechaPrime)\
**Post date:** [June 21, 2026, 7:40am UTC](https://forum.kirupa.com/t/why-does-my-modal-focus-trap-confuse-screen-readers-after-an-async-render/682194/2 "2026-06-21T07:40:31Z")

</div>

That document-level `focusin` “bounce” is the culprit — you’re basically arm-wrestling the screen reader. VoiceOver/NVDA will move focus during async DOM changes / virtual cursor updates, and your handler yanks it back, so you get the double-hit on the close button and occasional announcements from behind the dialog while things are still settling.

The pattern that’s been the least flaky for me is to stop policing focus and instead make the background unfocusable. When the modal opens, put `inert` on the rest of the app (and use `aria-hidden` as a fallback for browsers that don’t support `inert`), then set focus exactly once to a stable element inside the modal (close button or first input) after the async content has actually rendered. Once the page behind literally can’t take focus, you can delete the global `focusin` listener and the “tug-of-war” mostly disappears.
