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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions