こんなエラーが出た。
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
Ancestor with aria-hidden: <div class="modal fade" tabindex="-1" style="display: block;" aria-hidden="true" >…</div>
Modalが消える要素なのに、フォーカスが当たっていたらダメですよと。
手っ取り早くcloseする時にフォーカスをモーダルの親コンポーネントのボタンとかにRefを当ててそこにしたら消えた。
import React, { useRef, useState } from 'react'; const MyModal = () => { const [isOpen, setIsOpen] = useState(false); const triggerRef = useRef<HTMLButtonElement>(null); const openModal = () => setIsOpen(true); const closeModal = () => { setIsOpen(false); triggerRef.current?.focus(); // フォーカスを元のトリガーボタンに戻す }; return ( <div> <button ref={triggerRef} onClick={openModal}> Open Modal </button> {isOpen && ( <div role="dialog" aria-modal="true" style={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, background: 'rgba(0, 0, 0, 0.5)' }} > <div style={{ background: 'white', padding: '20px' }}> <p>Modal Content</p> <button onClick={closeModal}>Close</button> </div> </div> )} </div> ); }; export default MyModal;
ただ、データ一覧があって各データにボタンがあったりそのModalをトリガーしたボタンが消えてしまっていては意味がない。
そこで、こんな感じにする。ポイントは、
- DIV要素にtabIndexを付けてフォーカス可能にする。
- focus({ preventScroll: true })でフォーカス時にスクロールしないようにする。
import React, { useRef, useState } from 'react'; const MyModal = () => { const [isOpen, setIsOpen] = useState(false); const triggerRef = useRef<HTMLDivElement>(null); // ボタン以外の要素 const openModal = () => setIsOpen(true); const closeModal = () => { setIsOpen(false); triggerRef.current?.focus({ preventScroll: true }); // tabindexが設定されていれば問題なく動作 }; return ( <div> <div ref={triggerRef} tabIndex={0} // フォーカス可能にする onClick={openModal} style={{ display: 'inline-block', padding: '10px', background: 'lightblue', cursor: 'pointer', }} > Open Modal (Non-button Element) </div> {isOpen && ( <div role="dialog" aria-modal="true" style={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, background: 'rgba(0, 0, 0, 0.5)', display: 'flex', justifyContent: 'center', alignItems: 'center', }} > <div style={{ background: 'white', padding: '20px', borderRadius: '8px' }}> <p>Modal Content</p> <button onClick={closeModal}>Close</button> </div> </div> )} </div> ); }; export default MyModal;
というのをChatGPTに教えてもらった。