headlessui: [Bug]: Uncaught RangeError: Maximum call stack size exceeded. focusElement of Modal

What package within Headless UI are you using?

@headlessui/react

What version of that package are you using?

1.4.1

What browser are you using?

chrome

Reproduction repository

Not now.

Describe your issue

while we create Modal multiple (2+) it will throw me

focus-management.esm.js?d1e3:99 Uncaught RangeError: Maximum call stack size exceeded.
    at focusElement (focus-management.esm.js?d1e3:99)
    at eval (use-focus-trap.esm.js?25af:110)
    at handler (use-window-event.esm.js?b5e3:8)
    at focusElement (focus-management.esm.js?d1e3:99)
    at eval (use-focus-trap.esm.js?25af:110)
    at handler (use-window-event.esm.js?b5e3:8)
    at focusElement (focus-management.esm.js?d1e3:99)
    at eval (use-focus-trap.esm.js?25af:110)
    at handler (use-window-event.esm.js?b5e3:8)
    at focusElement (focus-management.esm.js?d1e3:99)
    at eval (use-focus-trap.esm.js?25af:110)
    at handler (use-window-event.esm.js?b5e3:8)
    at focusElement (focus-management.esm.js?d1e3:99)
    at eval (use-focus-trap.esm.js?25af:110)
    at handler (use-window-event.esm.js?b5e3:8)
    at focusElement (focus-management.esm.js?d1e3:99)
    at eval (use-focus-trap.esm.js?25af:110)
    at handler (use-window-event.esm.js?b5e3:8)
    at focusElement (focus-management.esm.js?d1e3:99)
    at eval (use-focus-trap.esm.js?25af:110)

Screenshot here

BTW, can Modal need to provider flag props to disabled focusElement feature, in most case we don’t need this, i think.

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 16
  • Comments: 35

Most upvoted comments

Hey! Thank you for your bug report! Much appreciated! 🙏

For everyone finding this issue.

The problem: You want 2 Dialogs, but it won’t work Solution: Dialogs must be nested if you want multiple Dialogs. Dialogs can’t be siblings because if you happen to open them at the same time each Dialog will try to steal focus again because a Dialog should be considered a separate page where you can’t interact with the content behind it.

We’re having this issue too, it’s a pretty common use case to have sibling modals, other libs like Baseweb handle this fine or at least provide a way to turn off autofocus

Upon opening, focus will be transferred to the first interactive element (unless autofocus is set to false) https://baseweb.design/components/modal/#accessibility

is there a way to do that with HeadlessUI, and if not would you be open to a PR allowing for autofocus to be turned off ?

Hey! Thank you for your bug report! Much appreciated! 🙏

This is definitely an error that should be fixed (by improving the error message), however the root cause is that you are rendering 2 dialogs as siblings, if you want nested dialogs, then you have to nest them inside one another, and not render them as a sibling.

The reason that this error is happening is because the dialog is implemented in a way so that if an element is being focused outside of a dialog, that the focus is restored into the dialog itself. However, with 2 sibling dialogs this happens:

Dialog A has focus
Dialog B gets rendered, steals focus
Dialog A loses focus, thus tries to restore focus into A
Dialog B loses focus, thus tries to restore focus into B
Dialog A loses focus, thus tries to restore focus into A
Dialog B loses focus, thus tries to restore focus into B
Dialog A loses focus, thus tries to restore focus into A
Dialog B loses focus, thus tries to restore focus into B
...

Add an initialFocus prop to the dialog and set it to document.activeElement. For folks using vue:

<template>
    <Dialog as="div" :initialFocus="getActiveElement()">
   ...
    </Dialog>
</template>
<script>
...
   methods: {
       getActiveElement() {
           return document.activeElement;
       }
   }
...
</script>

This shouldn’t be closed. There should be a proper solution for Dialogs that get opened one after another or at least proper reporting of how to resolve the issue.

@RobinMalfait Guys, this is a real issue. Having modals as siblings is not a whim it is a real requirement. Please provide an option to disable this behaviour…

now i resolved this issue, close previous dialog before new dialog open.

what’s progress of this issue

@Mhmdhammoud if you don’t use transition component it should work but if you use transition component make sure on TransitionRoot component apply props :show and also on Dialog component apply props :open with same value.

Same problem here. To be honest, I would rather abandon focus trap to prevent this error

@RobinMalfait Guys, this is a real issue. Having modals as siblings is not a whim it is a real requirement. Please provide an option to disable this behaviour…

While I (and the community) agrees with this being a real requirement. I think we need to remember it’s an open source library maintained for free, and we have no right to demand anything.

However, it is strange how the Dialog (Modal) docs on headlessui.com do not ever mention this limitation. Despite the large impact on how large scale web applications are architected. If there are no plans to add support for multiple modal support as siblings, we should at the bare minimum communicate this publicly instead of burying this valuable piece of information in a Closed issue.

I went ahead and created https://github.com/tailwindlabs/headlessui/issues/2689 in hopes of giving this issue some closure.

Feb 6 2024 Update: no mention of multiple modal usage in the docs yet 😞 feel free to leave a comment/reaction on the above thread if you’d also like this mentioned early in the docs. As it seems early communicating of this restriction could have avoided a lot of frustration for developers who’ve already committed to headless UI for their projects unaware of it.

👍 Building a payments form inside an accessible dialog - using Stripe Elements (which renders its own inputs inside iframes) results in some serious issues related to focus management. Is there anything that can be done?

I had the same issue with Stripe element, but it only happens on Firefox Try Google Chrome it’s working just fine there…

👍 Building a payments form inside an accessible dialog - using Stripe Elements (which renders its own inputs inside iframes) results in some serious issues related to focus management. Is there anything that can be done?

Workaround for this.

Set open prop on Modal to false and it will work without throwing that error. If anyone knows why it behaves this way, comment please.

I’m also experiencing issues with focus trap and Stripe elements in a dialog. When a Stripe element input component is currently focused and you then attempt to focus on another element outside of the Stripe iframe, the Stripe element will demand back focus making it so you can’t move on to other components in a form.

As @kayvaninvemo mentioned, I don’t think FocusTrap plays well with iframes.

This happens sporadically on Chrome and consistently on Firefox.

Is there any workaround for this? I just need to be able to move my cursor through form elements without FocusTrap getting in the way.

@solomonhawk try installing the insiders build

I also continue to run into this, each dialog is always sibling in a portal outside of root, as they can be opened by global methods and are not redefined in other components.

My use case is a base modal that has a form the user can fill in, and on error, I show another modal with the error message, thus creating a second sibling, triggering the focus loop when the error modal closes.

Setting initialFocus={document.activeElement} does not seem to work. (React)

Can’t we just disable the Dialog component from trying to get focus in the first place? Like mentioned by @mockingjet and @tianyingchun, the FocusTrap behaviour could be opted out of.

Oh extract new Component named FocusStrap for Modal, if we need, we can place FocusStrap into Modal children props by ourself.

In our design we have stripe elements in a dialog, and stripe elements are rendered in iframe, the focus trap does not work well with iframe, i guess. That would be nice to have a “opt out of focus trap” option in dialogs.

what’s progress of this issue

@RobinMalfait replied the solution. Basically what you need to do is move one modal inside other.

<ModalOne> <ModalTwo /> </ModalOne>