誤って公開してしまいました!詳細については、後でもう一度戻ってください!
アクセシビリティ対応の Web アプリケーションを作成することは、単なる良い習慣ではなく、今や必須となっています。最近、a11y に焦点を当てたナビゲーション メニューバーを構築する機会がありました。調べているうちに、世の中のほとんどのメニューバーが ARIA パターンに準拠していないことに気づきました。たとえば、メニュー項目をタブで移動する代わりに、メニューバーを矢印キーで移動し、独自のフォーカスを管理する必要があることをご存知ですか?
いくつかのチュートリアルを見つけましたが、結局完全に従うことはできませんでした。私がこれを書いているのは、私が最終的に構築したものは、小さなコンポーネントやカスタム フックにも興味があるのであれば、共有する価値があると思うからです。
このブログはいくつかの開発ステップで構成しますが、私の目標はステップバイステップのガイドを書くことではありません。あなたは React の基本とカスタム フックがどのように機能するかを理解していると思います。
現在は主要な実装の詳細のみを共有していますが、将来的には時間があるときにコード サンドボックスの例を含めてこの記事を更新する予定です。
このブログでは、多くの Web アプリケーションの上部または横にあるようなナビゲーション メニュー バーを目指して構築しています。このメニュー バーでは、一部のメニュー項目にはサブ メニューがあり、マウスの入力/終了で開閉します。
何よりもまず、セマンティック HTML と適切なロールと ARIA 属性がアクセシビリティに不可欠です。メニューバーのパターンについては、ここの公式ドキュメントから詳しく読むことができます。
適切な HTML マークアップの例を次に示します:
<nav aria-label="Accessible Menubar"> <menu role="menubar"> <li role="none"> <a role="menuitem" href="/">Home</a> </li> <li role="none"> <a role="menuitem" href="/about">About</a> </li> <li role="none"> <button role="menuitem" aria-haspopup="true" aria-expanded="false" > Expand Me! </button> <menu role="menu"> <li role="none"> <a role="menuitem" href="/sub-item-1">Sub Menu Item 1</a> </li> <li role="none"> <a role="menuitem" href="/sub-item-2">Sub Menu Item 2</a> </li> </menu> </li> </menu> </nav>
セマンティック HTML に button タグを使用していることに注意してください。ボタンには、スクリーン リーダーに警告する aria-haspopup も必要です。最後に、メニューの状態に応じて、適切な aria-expanded 属性を割り当てる必要があります。
必要なコンポーネントについて見ていきましょう。明らかに、メニュー項目コンポーネントだけでなく、メニュー全体のコンポーネントも必要です。
一部のメニュー項目にはサブメニューがありますが、サブメニューがないものもあります。サブメニューのあるメニュー項目は、ホバーイベントやキーボードイベントでのサブメニューの開閉の状態を管理する必要があります。したがって、独自のコンポーネントである必要があります。
サブメニューも独自のコンポーネントである必要があります。サブメニューもメニュー項目の単なるコンテナですが、サブメニューの状態を管理したり、キーボード イベントを処理したりすることはありません。これにより、トップレベルのナビゲーション メニューと区別されます。
私は最終的に次のコンポーネントを作成しました:
非常に簡単に言うと、「フォーカス管理」とは、コンポーネントがどの子にフォーカスを持っているかを知る必要があることを意味します。したがって、ユーザーのフォーカスが離れて戻ってくると、以前にフォーカスされていた子が再度フォーカスされます。
フォーカス管理の一般的な手法は「ロービング タブ インデックス」です。グループ内のフォーカスされた要素のタブ インデックスは 0 で、他の要素のタブ インデックスは -1 になります。こうすることで、ユーザーがフォーカス グループに戻ると、タブ インデックス 0 の要素が自動的にフォーカスを持つようになります。
NavMenu の最初の実装は次のようになります。
export function NavMenu ({ menuItems }) { // state for the currently focused index const [focusedIndex, setFocusedIndex] = useState(0); // functions to update focused index const goToStart = () => setCurrentIndex(0); const goToEnd = () => setCurrentIndex(menuItems.length - 1); const goToPrev = () => { const index = currentIndex === 0 ? menuItems.length - 1 : currentIndex - 1; setCurrentIndex(index); }; const goToNext = () => { const index = currentIndex === menuItems.length - 1 ? 0 : currentIndex + 1; setCurrentIndex(index); }; // key down handler according to aria specification const handleKeyDown = (e) => { e.stopPropagation(); switch (e.code) { case "ArrowLeft": case "ArrowUp": e.preventDefault(); goToPrev(); break; case "ArrowRight": case "ArrowDown": e.preventDefault(); goToNext(); break; case "End": e.preventDefault(); goToEnd(); break; case "Home": e.preventDefault(); goToStart(); break; default: break; } } return ( <nav> <menu role="menubar" onKeyDown={handleKeyDown}> {menuItems.map((item, index) => <MenuItem key={item.label} item={item} index={index} focusedIndex={focusedIndex} setFocusedIndex={setFocusedIndex} /> )} </menu> </nav> ); }
e.preventDefault() は、ArrowDown によるページのスクロールなどを防ぐためにあります。
これが MenuItem コンポーネントです。サブメニューのある項目は少し無視してみましょう。 useEffect、usePrevious、element.focus() を使用して、focusedIndex が変更されるたびに要素にフォーカスします。
export function MenuItem ({ item, index, focusedIndex, setFocusedIndex }) { const linkRef = useRef(null); const prevFocusedIndex = usePrevious(focusedIndex); const isFocused = index === focusedIndex; useEffect(() => { if (linkRef.current && prevFocusedIndex !== currentIndex && isFocused) { linkRef.current.focus() } }, [isFocused, prevFocusedIndex, focusedIndex]); const handleFocus = () => { if (focusedIndex !== index) { setFocusedIndex(index); } }; return ( <li role="none"> <a ref={linkRef} role="menuitem" tabIndex={isFocused ? 0 : -1} onFocus={handleFocus} > {item.label} </a> </li> ); }
これは ref (サブメニューのあるメニュー項目のボタン) を持つ必要があるタグであることに注意してください。そのため、これらのタグにフォーカスすると、Enter キーでのナビゲーションなど、デフォルトのキーボード動作が期待どおりに開始されます。さらに、タブインデックスはフォーカスされている要素に応じて適切に割り当てられています。
フォーカス イベントがキー/マウス イベントによるものではない場合に備えて、フォーカス イベントのイベント ハンドラーを追加しています。以下はウェブドキュメントからの引用です:
すべてのフォーカスの変更がキーとマウスのイベントによって行われるとは考えないでください。スクリーン リーダーなどの支援テクノロジは、フォーカス可能な要素にフォーカスを設定できます。
上記の useEffect に従うと、ユーザーがキーボードを使用して移動しなくても、最初の要素にフォーカスがあることがわかります。これを修正するには、アクティブな要素を確認し、ユーザーがキーボード イベントを開始したときにのみ focus() を呼び出します。これにより、フォーカスが本体から移動します。
useEffect(() => { if (linkRef.current && document.activeElement !== document.body // only call focus when user uses keyboard navigation && prevFocusedIndex !== focusedIndex && isCurrent) { linkRef.current.focus(); } }, [isCurrent, focusedIndex, prevFocusedIndex]);
So far, we have functional NavMenu and MenuItemLink components. Let's move on to menu item with sub menus.
As I was quickly building it out, I realized that this menu item will share the majority of the logic
以上がReact フックを使用してアクセシブルなナビゲーション メニューバーを構築するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。