Skip to content

ariaHideOutside: inconsistent attribute check between initial scan and MutationObserver #9762

@lixiaoyan

Description

@lixiaoyan

Provide a general summary of the issue here

There is an inconsistency in ariaHideOutside.ts between how the data-react-aria-top-layer attribute is checked during the initial DOM scan vs. when handling dynamically inserted nodes via MutationObserver.

Initial scan (line 87) — matches attribute presence (any value):

root.querySelectorAll('[data-live-announcer], [data-react-aria-top-layer]')

This CSS attribute selector matches data-react-aria-top-layer regardless of its value ("", "true", anything).

MutationObserver (lines 178, 221) — requires exact value "true":

node.dataset.reactAriaTopLayer === 'true'

When the attribute is set as an empty string (e.g. data-react-aria-top-layer=""), dataset.reactAriaTopLayer is "", which does not equal 'true'.

Note: data-live-announcer is always set to "true" in LiveAnnouncer.tsx, so the === 'true' check is correct for it. The inconsistency only affects data-react-aria-top-layer.

🤔 Expected Behavior?

Both the initial scan and MutationObserver should use the same logic to detect the data-react-aria-top-layer attribute. Elements with this attribute (regardless of value) should be kept visible and not hidden with aria-hidden="true".

😯 Current Behavior

When a top-layer element (e.g. toast) is dynamically inserted into the DOM while ariaHideOutside is active, and its data-react-aria-top-layer attribute value is not exactly the string "true", the MutationObserver fails to recognize it as a visible node. The element gets incorrectly hidden with aria-hidden="true".

Current internal usage in useToastRegion.ts passes true (boolean) in JSX props ('data-react-aria-top-layer': true), which React renders as the string "true", so it works by coincidence. But the inconsistency is a latent bug for any consumer that sets the attribute differently (e.g. via setAttribute('data-react-aria-top-layer', '')).

💁 Possible Solution

Change the MutationObserver checks for data-react-aria-top-layer to use !== undefined instead of === 'true' to match the CSS attribute presence selector behavior:

node.dataset.reactAriaTopLayer !== undefined

This applies to both MutationObserver callbacks (lines 178 and 221).

Alternatively, the initial scan at line 87 could be changed to [data-react-aria-top-layer="true"] to match the MutationObserver — but using attribute presence (!== undefined) is more robust and forgiving.

🔦 Context

No response

🖥️ Steps to Reproduce

  1. Call ariaHideOutside on some target elements
  2. Dynamically insert a new element with data-react-aria-top-layer="" (empty string) into the DOM
  3. Observe that the MutationObserver does NOT add it to visibleNodes because node.dataset.reactAriaTopLayer === 'true' evaluates to false
  4. The element gets incorrectly hidden with aria-hidden="true"

Compare with: if the element existed before ariaHideOutside was called, the initial scan's querySelectorAll('[data-react-aria-top-layer]') would correctly match it regardless of attribute value.

Version

react-aria-components@1.16.0

What browsers are you seeing the problem on?

Chrome

If other, please specify.

No response

What operating system are you using?

macOS

🧢 Your Company/Team

No response

🕷 Tracking Issue

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions