Micromodal.jsを活用し、アクセシビリティに配慮したハンバーガーメニューの作り方を解説します。
意外とアクセシビリティ対応が難しいのがモーダルウィンドウの処理。ここで言うモーダルとは、「画像ギャラリー」などに限らず、いわゆる「ハンバーガーメニュー」も含まれます。
そんなモーダルですが、下手に自作するよりも、信頼できるライブラリを活用したほうがずっと安全です。
そこで今回は、アクセシビリティ対応で評価の高い「Micromodal.js」を使って、ウェブサイトに適したハンバーガーメニューを作ってみました。
アクセシビリティに配慮したウェブサイトの制作
近年、アクセシビリティに配慮したウェブサイト制作の重要性はますます高まっています。
2024年4月に改正された「障害者差別解消法」の施行以降、特に官公庁系の案件ではアクセシビリティに関するチェックがより厳格になった印象です。
現時点では、法的な義務は課されていないものの、社会的責任の観点から、官民問わず今後はアクセシビリティ対応が「標準」になっていくと思われます。まあ、考えてみれば、ごく自然な流れです。
そういった背景から、Micromodal.jsのようにアクセシビリティを意識したライブラリの活用機会は、今後さらに増えていくと考えられ、今のうちに仕組みを理解し、知見を深めておくことが大切だと実感しています。
Micromodal.jsとは
Micromodal.jsは、軽量かつシンプルなモーダル用JavaScriptライブラリです。最大の特徴は、アクセシビリティに配慮されたモーダルダイアログを簡単に実装できる点にあります。
ARIA属性の自動付与、キーボード操作対応、フォーカストラップ、ESCキーによる閉じる処理、背景スクロールの無効化などの機能が標準搭載されており、アクセシビリティに配慮したモーダルを構築するうえで非常に有効な選択肢です。
Micromodal.js – Tiny javascript library for creating accessible modal dialogs
https://micromodal.vercel.app/
Micromodal.jsが出来ること
- モーダルの表示・非表示制御
- モーダル表示中に背景のスクロールを固定
- 閉じるボタンのほか、背景クリックでもモーダルを閉じられる
TabキーやESCキーによるキーボード操作に対応- モーダル内でのみフォーカスが移動するフォーカストラップ機能
- 最初にフォーカス可能な要素へ自動でフォーカスを設定
- 状態に応じたARIA属性の自動付与
- そのほか、アクセシビリティ関連の基本機能が一通り揃っている
ARIA属性とは
ARIA(Accessible Rich Internet Applications)属性とは、機能の役割や状態の情報を、スクリーンリーダーなどの支援技術に伝えるために、HTMLを補完するものです。
たとえば、モーダルウィンドウを表示した際に「この要素はダイアログです」と伝えたり、「このボタンはモーダルを閉じる役割です」といった情報を明示的に提供できます。
フォーカストラップとは
フォーカストラップとは、モーダルが開いている間、Tabキーなどのキーボード操作で、フォーカスがモーダルの外へ出ていかないように制御する仕組みです。
これにより、ユーザーは操作中、意図せずメニュー外の背景コンテンツにフォーカスが移動してしまう事態を防ぎます。
かつての自分も含めて、自戒を込めて言いますが、このフォーカストラップをまったく意識していないハンバーガーメニューは、世の中にごまんとあります。
皆さんもブラウジング中、試しにTabキー + Enterキーでハンバーガーメニューを操作してみてください。下手すると開くことすら出来ないウェブサイトもあるはずです。
よく聞くa11y対応って何?
ちなみに余談ですが、こういったアクセシビリティに関する要件定義書に、よく「a11y対応」っていう表記を見かけます。
この「a11y」って単純に「アクセシビリティ」の略称らしいです。aからyまでの間に11文字あるから、そう略されているだけで、特別な仕様や基準を指すものではないので、あまり深く考える必要はありません。
Micromodal.jsを使ってハンバーガーメニューを作ってみる
ということで、早速デモを作ってみました。
自分もアクセシビリティ領域はまだまだ勉強中で、Micromodal.jsの活用も試行錯誤の真っ最中ですが、とりあえず一通り形にはできたと思います。
Micromodal.jsのアクセシビリティ機能を活かし、キーボード操作への対応やスクリーンリーダーとの親和性を意識して作成しました。
See the Pen Micromodal.jsで作るハンバーガーメニュー by MEMORUKA (@memoruka) on CodePen.
HTML
<button class="g-menu-button" data-micromodal-trigger="g-modal-menu" aria-label="グローバルメニューを開く" aria-expanded="false"
aria-controls="g-modal-menu">
<img src="./img/menu.svg" alt="">
</button>
<div class="mm-modal-menu-wrapper" id="g-modal-menu" aria-hidden="true">
<div class="mm-modal-menu-overlay" tabindex="-1" data-micromodal-close>
<div class="mm-modal-menu-container" role="dialog" aria-modal="true" aria-labelledby="modal-menu-title">
<nav id="g-nav" aria-label="グローバルナビゲーション">
<button class="g-menu-button is-close" aria-label="グローバルメニューを閉じる" data-micromodal-close>
<img src="./img/menu_close.svg" alt="">
</button>
<div id="g-nav-inner">
<h2 id="modal-menu-title">サイトメニュー</h2>
<ul id="g-menu">
<li><a href="">メニュー</a></li>
</ul>
</div>
</nav>
</div><!--/.mm-modal-menu-container-->
</div><!--/.mm-modal-menu-overlay-->
</div><!--/.mm-modal-menu-wrapper-->ハンバーガーメニュー(以下、モーダル)を開くボタン:data-micromodal-trigger
<button class="g-menu-button" data-micromodal-trigger="g-modal-menu" aria-label="グローバルメニューを開く" aria-expanded="false"
aria-controls="g-modal-menu">
<img src="./img/menu.svg" alt="">
</button>モーダルを開くボタンには data-micromodal-trigger を付与します。
値には、Micromodal.jsを使って展開するモーダル領域(後述)の id を指定してください。
また、aria-expanded 属性を追加し、初期状態ではボタンが「閉じている」ことを明示します。
モーダル部分のラッパー領域:aria-hidden=”true”
<div class="mm-modal-menu-wrapper" id="g-modal-menu" aria-hidden="true"></div>Micromodal.jsで展開するモーダルの最も外側のラッパー要素です。
先述したdata-micromodal-triggerのidと、aria-hidden=”true”を付与します。これは、モーダル要素が非表示であることを支援技術に伝えるための属性です。
モーダルのオーバーレイ:tabindex=”-1″ data-micromodal-close
<div class="mm-modal-menu-overlay" tabindex="-1" data-micromodal-close></div>モーダルのオーバーレイに該当する領域。ようは背景部分。
data-micromodal-closeを付与することで、クリックするとモーダルを閉じるトリガーになります。
また、フォーカス制御のために、tabindex="-1" を指定します。
メインコンテンツとの独立性を宣言:role=”dialog”
<div class="mm-modal-menu-container" role="dialog" aria-modal="true" aria-labelledby="modal-menu-title"></div>role="dialog" は、モーダルがページのメインコンテンツとは独立したダイアログであることをスクリーンリーダーなどに伝えます。
ここまでがMicromodal.jsの基本的なHTML構造になります。
このあとに続く部分は、各サイトのデザインに応じて自由に記述できます。
ハンバーガーメニューのデザイン部分
<nav id="g-nav" aria-label="グローバルナビゲーション"></nav>メニューの中身となる部分です。デザインやレイアウトは任意に調整してください。
モーダルを閉じるボタン:data-micromodal-close
<button class="g-menu-button is-close" aria-label="グローバルメニューを閉じる" data-micromodal-close>
<img src="./img/menu_close.svg" alt="">
</button>モーダルを閉じるボタンです。data-micromodal-close 属性を付与します。
さらに aria-label 属性で「閉じる」などの動作説明を記述しておくと、支援技術での読み上げ時に親切です。
メニューを開くボタンと、閉じるボタンが同一のサンプルが多いけど、それじゃダメじゃないか?
Micromodal.jsをハンバーガーメニューに活用しようと考える人は他にもいるようで、デモ付きで解説している記事もいくつか見かけました。
ただし、メニューの開閉を同じボタンで制御しているパターンが多い。いわゆる三本線(≡)が展開後にバツ印(×)に変形する、よくあるUI。自分もハンバーガーメニューを自作していた頃はそういう作り方をしていました。
ただこの作りってMicromodal.jsのアクセシビリティの設計から考えると、どうなんだろう?
というのは、メニューボタンがモーダル(メニュー領域)の「外側」に存在するため、メニューを開いたあとに、Tabキーなどのキーボード操作で閉じるボタンに戻れなくなっているパターンが非常に多いんだよね。
それじゃMicromodal.jsを使う意味が無いんじゃないだろうか?
一応、機能としてはESCキーでも閉じる事は出来るんだけど、ユーザーがその操作を知らなければアウトだし、Tabキーでメニューを操作する以上、閉じる操作もTabで完結すべきだと考えます。
よって、閉じるボタンはモーダル内に明示的に存在させるべきだと判断しました。
CSS
閑話休題。ここからはCSSの解説です。
/* Micromodal.jsを使ったスライドメニュー
--------------------------------------------------------------- */
/* - モーダルオーバーレイ用のアニメーション */
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
/* - モーダルスライドイン用のアニメーション */
@keyframes slideIn-right {
from {
transform: translateX(100%);
}
to {
transform: translateX(0);
}
}
@keyframes slideOut-right {
from {
transform: translateX(0);
}
to {
transform: translateX(100%);
}
}
.mm-modal-menu-wrapper[aria-hidden="false"] .mm-modal-menu-overlay {
animation: fadeIn .3s cubic-bezier(.0, .0, .2, 1) forwards;
}
.mm-modal-menu-wrapper[aria-hidden="false"] .mm-modal-menu-container {
animation: slideIn-right .3s cubic-bezier(.0, .0, .2, 1) forwards;
}
.mm-modal-menu-wrapper[aria-hidden="true"] .mm-modal-menu-overlay {
animation: fadeOut .3s cubic-bezier(.4, .0, 1, 1) forwards;
}
.mm-modal-menu-wrapper[aria-hidden="true"] .mm-modal-menu-container {
animation: slideOut-right .3s cubic-bezier(.4, .0, 1, 1) forwards;
}
/* - モーダル全体ラッパー - 初期値 / 表示時 */
.mm-modal-menu-wrapper {
display: none;
}
.mm-modal-menu-wrapper.is-open {
display: block;
}
/* - モーダルオーバーレイ */
.mm-modal-menu-overlay {
position: fixed;
inset: 0;
background: rgb(0 0 0 / .5);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
overscroll-behavior: contain;
overflow-y: scroll;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}
/* - モーダル - 表示するコンテナ部分 */
.mm-modal-menu-container {
position: relative;
width: 300px;
height: calc(100% + 1px);
/* ※overscroll-behavior用にあえて超過させる */
margin-left: auto;
}
/* - メニュー - スクリーンリーダー専用タイトル ※非表示 */
#modal-menu-title {
position: absolute;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
white-space: nowrap;
}
/* - メニュー - ナビゲーション */
#g-nav {
width: 100%;
height: 100%;
background: #fff;
z-index: 3;
}
/* - メニュー - 親コンテナ */
#g-nav-inner {
box-sizing: border-box;
height: 100%;
padding: 100px 25px;
overflow-y: auto;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}
CSSはデザインに応じて自由に装飾できますが、モーダル部分のラッパー領域:aria-hidden="true"を指定した要素(今回の場合、.mm-modal-menu-wrapper)には、最低限以下のdisplayの切替に関する記述が必要です。
/* - モーダル全体ラッパー - 初期値 / 表示時 */
.mm-modal-menu-wrapper {
display: none;
}
.mm-modal-menu-wrapper.is-open {
display: block;
}この部分以外のCSSは、デザインに応じて自由に装飾して問題ありません。
以下では、今回のデモで使用した主要スタイルについて簡単に解説しておきます。
モーダルを表示する際のアニメーション
/* - モーダルオーバーレイ用のアニメーション */
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
/* - モーダルスライドイン用のアニメーション */
@keyframes slideIn-right {
from {
transform: translateX(100%);
}
to {
transform: translateX(0);
}
}
@keyframes slideOut-right {
from {
transform: translateX(0);
}
to {
transform: translateX(100%);
}
}
.mm-modal-menu-wrapper[aria-hidden="false"] .mm-modal-menu-overlay {
animation: fadeIn .3s cubic-bezier(.0, .0, .2, 1) forwards;
}
.mm-modal-menu-wrapper[aria-hidden="false"] .mm-modal-menu-container {
animation: slideIn-right .3s cubic-bezier(.0, .0, .2, 1) forwards;
}
.mm-modal-menu-wrapper[aria-hidden="true"] .mm-modal-menu-overlay {
animation: fadeOut .3s cubic-bezier(.4, .0, 1, 1) forwards;
}
.mm-modal-menu-wrapper[aria-hidden="true"] .mm-modal-menu-container {
animation: slideOut-right .3s cubic-bezier(.4, .0, 1, 1) forwards;
}モーダルを表示する際のアニメーションをCSSで設定。
今回は右からスライドインしてくる方式を採用しました。
モーダルのオーバーレイ部分
.mm-modal-menu-overlay {
position: fixed;
inset: 0;
background: rgb(0 0 0 / .5);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
overscroll-behavior: contain;
overflow-y: scroll;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}モーダルのオーバーレイ(背景)部分です。半透明かつblurでぼかし。
それと詳しくは後述しますが、
モーダル展開時の背景スクロール固定用に、overscroll-behavior: contain;を指定しています。
表示するコンテナ部分
.mm-modal-menu-container {
position: relative;
width: 300px;
height: calc(100% + 1px);
/* ※overscroll-behavior用にあえて超過させる */
margin-left: auto;
}モーダルのコンテンツ部分を内包する、一番直近の親要素です。
先述した、overscroll-behavior: contain;を効かせるために、height: calc(100% + 1px);であえて超過させています。
JavaScript(Micromodal.jsの設定)
今回使用した、Micromodal.js(および必要だと判断した処理)の設定は以下の通りに行いました。
const bodyClass = document.body;
const menuButton = document.querySelector('.g-menu-button');
// Micromodalの設定
MicroModal.init({
disableScroll: false, // ※今回はCSSで代用
disableFocus: false,
awaitOpenAnimation: true,
awaitCloseAnimation: true,
onShow: function () {
bodyClass.classList.add('is-menu-active'); // ※今回は使ってないけど拡張性のために追加
const gNavInner = document.getElementById('g-nav-inner');
if (gNavInner) gNavInner.scrollTop = 0;
if (menuButton) {
menuButton.setAttribute('aria-expanded', 'true');
}
},
onClose: function () {
bodyClass.classList.remove('is-menu-active');
if (menuButton) {
menuButton.setAttribute('aria-expanded', 'false');
}
}
});
// ページ内リンクでも閉じる
const pageLinks = document.querySelectorAll('#g-modal-menu a[href^="#"]');
pageLinks.forEach(link => {
link.addEventListener('click', function () {
MicroModal.close('g-modal-menu');
});
});ここからは、各オプション・処理の要点について説明します。
disableScroll: false(※通常はtrueでOK!)
disableScroll: false, // ※今回はCSSで代用モーダル展開中に背景を固定するオプションです。
ここ、通常はtrueでOKです!
モーダル展開中はbodyにoverflow:hidden;が適用され背景が固定されます。
もし、trueにしているのに効かない!という人は、CSSにhtml {overflow-y: scroll;}と書いてないかチェックしてください。原因はほぼそれだと思う。ウェブ制作者なら、多分みんなデフォルトで書いてるでしょ。
今回はoverscroll-behavior: contain;を使うのでfalseに
んで、なんで今回のデモではfalseにしているのかというと、このデモでは背景の固定にはCSSのoverscroll-behavior: contain;を使用しています。
overflow:hiddenのやり方だと、Windows環境ではブラウザのスクロールバーが固定中は非表示になるため、その分、画面の横幅がカクっとズレるんですよね。
正直言って、自分は「それで別に良い派」なのですが、この点を指摘してくるクライアントは結構多い。
そのため一時期は、JSでスクロール位置を記憶して、解除時に戻す方式にしていましたが、そうすると今度はiOS端末で固定/解除の一瞬に、ちらつきが発生する(ことがある)。
このような背景から、ハンバーガーメニューの固定には、以前からCSSのoverscroll-behaviorを使っています。
これが最適解なのかどうかは、わからないけど、Micromodal.jsの制御化でも一応意図通りに動いています。
disableFocus: false
disableFocus: false,キーボード操作でモーダルが開いた時に、最初のフォーカス可能な要素に自動でフォーカスするオプションです。
ややこしいけど、falseで自動フォーカスONです。disableをfalseだからね。
これもアクセシビリティ対応ならば、基本的には必須と聞いてます。
bodyClass.classList.add(‘is-menu-active’);
onShow: function () {
bodyClass.classList.add('is-menu-active');
},
onClose: function () {
bodyClass.classList.remove('is-menu-active');
}モーダル展開時にbodyに専用Classを付与しています。
今回は未使用だけど、将来的にデザイン面の装飾に使える余地を残しています。不要なら削除してOK。
if (gNavInner) gNavInner.scrollTop = 0;
const gNavInner = document.getElementById('g-nav-inner');
if (gNavInner) gNavInner.scrollTop = 0;モーダル展開時に、メニューのスクロール位置を最上部の地点に戻します。
disableFocus: falseにより、最初のフォーカス可能な要素に自動でフォーカスするため、スクロール位置も先頭に戻しておいたほうが良いかなと思って実装しました。
まあ、別に無くても問題は無いと思います。任意の処理です。
menuButton.setAttribute(‘aria-expanded’, ‘true’);
menuButton.setAttribute('aria-expanded', 'true');メニューボタンのaria-expandedのtrue / falseを切り替える処理です。
この処理によりモーダル(ボタン)の開閉状態が支援技術に正しく伝わります。
ページ内リンクでも閉じる
const pageLinks = document.querySelectorAll('#g-modal-menu a[href^="#"]');
pageLinks.forEach(link => {
link.addEventListener('click', function () {
MicroModal.close('g-modal-menu');
});
});モーダルメニューで忘れちゃいけないのが、ページ内リンク(アンカーリンク)の処理。
ページ遷移が発生しないので、ちゃんとケアしないとリンクをクリックしてもモーダルが開きっぱなしになってしまいます。
デモコードでは、#から始まるhref要素を取得し、それらが選択された場合はMicromodal.jsが解除されるように設定しています。
ちなみにモーダルを閉じるには、HTML側のaタグにdata-micromodal-closeを付与する方法もありますが、これだとモーダルは閉じてくれますが、リンク処理はキャンセルされ、無効になってしまうので注意。
そのため、JS側で閉じる処理を付加する方が安全です。
まとめ
See the Pen Micromodal.jsで作るハンバーガーメニュー by MEMORUKA (@memoruka) on CodePen.
以上、本記事では、アクセシビリティに配慮したハンバーガーメニューのモーダル実装について、Micromodal.jsを活用した手法を解説しました。
ウェブサイト制作におけるアクセシビリティ対応は、今後ますます「標準」として求められてくるものと思われます。
特に、ハンバーガーメニューのようにUIの中核を担う要素には、細やかな配慮が欠かせません。
今回はMicromodal.jsを利用しましたが、どのような設計が本当に最適なのか、今後も引き続き、よりよい実装方法を模索していきたいと考えています。















