Skip to content

[Bug][Drawer] Blocked aria-hidden on an element because its descendant retained focus. #320

@Jesse205

Description

@Jesse205

Description

When using the temporary variant drawer and opening it, the browser warns:

Blocked aria-hidden on an element because its descendant retained focus. The focus must not be hidden from assistive technology users. Avoid using aria-hidden on a focused element or its ancestor. Consider using the inert attribute instead, which will also prevent focus. For more details, see the aria-hidden section of the WAI-ARIA specification at https://w3c.github.io/aria/#aria-hidden.
Element with focus: <button.MuiButtonBase-root MuiIconButton-root MuiIconButton-edgeEnd MuiIconButton-sizeMedium MuiIconButton-root MuiButtonBase-root css-5ki7wftn>
Ancestor with aria-hidden: <div#root> <div id=​"root" aria-hidden=​"true">​…​</div>​

Reason

Unexpected aria-hidden

Based on my diagnosis, SUID incorrectly adds aria-hidden to the div used for mounting MuiModal.

 <body style="overflow: hidden">
   <div id="root" aria-hidden="true">...</div>
+  <div aria-hidden="true">
     <div
       class="MuiModal-root MuiDrawer-root MuiDrawer-modal MuiDrawer-root MuiModal-root css-o6o9lwg9"
       role="presentation"
     >
       <div
         aria-hidden="true"
         class="MuiBackdrop-root MuiModal-backdrop MuiBackdrop-root css-bh4oe82h"
         style="opacity: 1; transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1)"
       ></div>
       <div
         class="MuiPaper-root MuiPaper-elevation MuiPaper-elevation16 MuiDrawer-paper MuiDrawer-paperAnchorRight MuiDrawer-paper MuiPaper-root css-cslrtd3y_1"
         style="transform: none; transition: transform 225ms cubic-bezier(0, 0, 0.2, 1)"
       >
         ...
       </div>
     </div>
   </div>
 </body>

After reviewing the code, I found the issue originates here:

ariaHidden(modal.ref, false);
const hiddenSiblings = getHiddenSiblings(container);
ariaHiddenSiblings(container, modal.ref, hiddenSiblings, true);

SolidJS's Portal nests a div when passing modal.ref to the body instead of direct attachment. This causes the ariaHiddenSiblings function to consistently fail to exclude the current drawer element.

A similar issue exists here:

ariaHidden(modal.ref, true);
ariaHiddenSiblings(
containerInfo.container,
modal.ref,
containerInfo.hiddenSiblings,
false
);
this.containers.splice(containerIndex, 1);
} else {
// Otherwise make sure the next top modal is visible to a screen reader.
const nextTop = containerInfo.modals[containerInfo.modals.length - 1];
// as soon as a modal is adding its modalRef is undefined. it can't set
// aria-hidden because the dom element doesn't exist either
// when modal was unmounted before modalRef gets null
ariaHidden(nextTop.ref, false);
}

However, replacing these model.ref with modal.ref.parentElement! still fails to resolve the error.

Unmanaged Focus

Further diagnosis reveals that SUID fails to handle focus management:

Mui Site:

> document.activeElement
<body dir="ltr" class="mode-light"></body>
> document.querySelector('header .MuiIconButton-root').click()
undefined
> document.activeElement
<div class="MuiPaper-root MuiPaper-elevation MuiPaper-elevation16 MuiDrawer-paper MuiDrawer-paperAnchorLeft css-iqol3p" tabindex="-1" style="--Paper-shadow:​ var(--muidocs-shadows-16)​;​ --Paper-overlay:​ var(--muidocs-overlays-16)​;​ transform:​ none;​ transition:​ transform 225ms cubic-bezier(0, 0, 0.2, 1)​;​"></div>flex

Suid Site:

> document.activeElement
<body></body>
> document.querySelector('.MuiToolbar-root .MuiIconButton-root').click()
undefined
> document.activeElement
<body style="overflow:​ hidden;​"></body>

The problem occurs here:

return (
<TransitionContext.Provider
value={{
get in() {
return !!props.transition && props.open;
},
onEnter: () => {
props.transition && setExited(false);
},
onExited: () => {
if (props.transition) {
setExited(true);
if (props.closeAfterTransition) handleClose();
}
},
}}
>
<Show when={!noMount()}>
<Portal container={props.container} disablePortal={props.disablePortal}>
{/*
* Marking an element with the role presentation indicates to assistive technology
* that this element should be ignored; it exists to support the web application and
* is not meant for humans to interact with directly.
* https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-static-element-interactions.md
*/}
<Dynamic
{...otherProps}
component={Root()}
role="presentation"
{...rootProps()}
{...(!isHostComponent(Root()) && {
//component: baseProps.component,
ownerState: allProps,
})}
onKeyDown={handleKeyDown}
class={clsx(classes.root, rootProps().class, otherProps.class)}
ref={element}
>
<Show when={!props.hideBackdrop && !!props.BackdropComponent}>
<Dynamic
component={props.BackdropComponent}
open={props.open}
onClick={handleBackdropClick}
{...(props.BackdropProps ?? {})}
/>
</Show>
{props.children}
</Dynamic>
</Portal>
</Show>
</TransitionContext.Provider>
);
});

In Mui, a TrapFocus component handles focus management:

https://github.com/mui/material-ui/blob/89617ef683d2bcd2c870e9c1bd58ad4d55247fd5/packages/mui-base/src/ModalUnstyled/ModalUnstyled.js#L268-L290

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions