2026年7月23日

2026年7月23日

WordPressのテーマでメガメニューを実装するCSS/JSのサンプル(クラシックテーマ向け)

メガメニューは、プラグインを使わなくても「メニュー項目にクラスを付ける → CSSでサブメニューを横並びの大きなパネルにする → JavaScriptで開閉とキーボード操作を補う」の3段階で作れます。この記事では、クラシックテーマの wp_nav_menu() が出力するHTMLを前提に、子テーマに追加するだけで動くCSSとJavaScriptのサンプルを示し、テーマごとに調整が必要な箇所と動作確認の方法を説明します。

この方法が向いているケース

ここで扱うのは、管理画面の「外観」→「メニュー」で作ったメニューを表示するクラシックテーマです。こうしたテーマでは、メニューが ul.menu の中に li.menu-item が並び、子を持つ項目には menu-item-has-children クラス、子メニューには ul.sub-menu が付くという共通の構造で出力されます。この構造を手がかりにすれば、テーマが違ってもほぼ同じコードが使えます。

ブロックテーマのナビゲーションブロックは出力するHTMLもクラス名も異なるため、このサンプルはそのままでは動きません。ブロックテーマでメガメニューが必要な場合は、ブロックテーマ向けのメガメニュー系プラグインを検討するか、ナビゲーションブロックのサブメニューをCSSで広げる方向で考えます。管理画面から設定したい場合は Max Mega Menu のようなプラグインもありますが、この記事は「自分で書いて仕組みを把握したい」人向けの手作り方式に絞っています。

作業は子テーマで行い、始める前に子テーマのファイルをバックアップしてください。

ステップ1:メニューの構造を作り、クラスを付ける

メガメニューにしたい親項目の下に、2階層目として「列の見出し」になる項目を並べ、さらにその下の3階層目に実際のリンクを入れます。

  • 製品(親項目。ここにクラスを付ける)
    • サーバー(列1の見出し)
    • 共用サーバー / VPS / 専用サーバー
    • ドメイン(列2の見出し)
    • 取得 / 移管 / DNS設定
    • サポート(列3の見出し)
    • よくある質問 / お問い合わせ

次に、親項目に mega-menu クラスを付けます。メニュー編集画面の右上にある「表示オプション」を開き、「CSSクラス」にチェックを入れると、各項目の設定欄にCSSクラスの入力欄が現れます。「製品」の欄に mega-menu と入力して保存します。

ここでつまずきやすいのが、テーマ側でメニューの階層数が制限されている場合です。wp_nav_menu() の depth 引数が 2 になっていると、3階層目は出力されません。保存後にサイトを開き、開発者ツールで li.mega-menu の中に ul.sub-menu が2段入っているか確認してください。3階層目が見当たらないときは、テーマの設定画面かテンプレートの wp_nav_menu() の呼び出しを確認します。

ステップ2:CSSでパネルを組む

子テーマに assets/css/mega-menu.css を作り、次の内容を書きます。

/* パネルの基準をナビゲーション全体にする */
.main-navigation {
	position: relative;
}

.main-navigation .mega-menu {
	position: static;
}

/* 2階層目をパネルとして隠しておく */
.main-navigation .mega-menu > .sub-menu {
	display: none;
	position: absolute;
	top: 100%;
	left: 0;
	right: 0;
	z-index: 100;
	width: auto;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: 1.5rem 2rem;
	margin: 0;
	padding: 2rem;
	list-style: none;
	background: #fff;
	box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
}

/* 開いた状態(JSで is-open を付ける) */
.main-navigation .mega-menu.is-open > .sub-menu {
	display: grid;
}

/* マウス操作ができる画面幅ではホバーとフォーカスでも開く */
@media (hover: hover) and (min-width: 960px) {
	.main-navigation .mega-menu:hover > .sub-menu,
	.main-navigation .mega-menu:focus-within > .sub-menu {
		display: grid;
	}
}

/* 3階層目は常に表示し、テーマのドロップダウン指定を打ち消す */
.main-navigation .mega-menu > .sub-menu .sub-menu {
	display: block;
	position: static;
	box-shadow: none;
	padding: 0;
	margin-top: 0.5rem;
	list-style: none;
}

.main-navigation .mega-menu > .sub-menu > li > a {
	font-weight: bold;
}

/* スマートフォン幅では縦に積む */
@media (max-width: 959px) {
	.main-navigation .mega-menu > .sub-menu {
		position: static;
		grid-template-columns: 1fr;
		padding: 1rem;
		box-shadow: none;
	}
}

.mega-toggle {
	margin-left: 0.25rem;
	padding: 0.25rem 0.5rem;
	background: none;
	border: 0;
	cursor: pointer;
}

.mega-toggle::before {
	content: "▾";
}

.mega-toggle[aria-expanded="true"]::before {
	content: "▴";
}

ポイントは位置の基準です。通常のドロップダウンは親の li を基準に開きますが、メガメニューはナビゲーションの横幅いっぱいに広げたいので、li.mega-menu を position: static にして、外側の .main-navigation を基準にしています。.main-navigation の部分は、使っているテーマでメニューを囲んでいる要素のクラス名に置き換えてください。開発者ツールで ul.menu の親要素を見れば分かります。

テーマ自身がドロップダウン用に .sub-menu へ width や left、opacity、visibility などを指定していると、上のCSSが効かないことがあります。パネルが見えないときは、開発者ツールで .sub-menu に当たっているCSSを一覧し、打ち消すべき指定を同じ詳細度以上のセレクターで上書きします。

ステップ3:JavaScriptで開閉ボタンとキーボード操作を補う

ホバーだけで開くメニューは、タッチ端末やキーボード操作では使いにくくなります。そこで、親項目の横に開閉ボタンを追加し、aria-expanded で状態を伝えるようにします。子テーマに assets/js/mega-menu.js を作ります。

document.addEventListener('DOMContentLoaded', function () {
	var items = document.querySelectorAll('.mega-menu.menu-item-has-children');
	if (!items.length) {
		return;
	}

	function setOpen(item, open) {
		var button = item.querySelector(':scope > .mega-toggle');
		item.classList.toggle('is-open', open);
		if (button) {
			button.setAttribute('aria-expanded', open ? 'true' : 'false');
		}
	}

	function closeAll(except) {
		items.forEach(function (item) {
			if (item !== except) {
				setOpen(item, false);
			}
		});
	}

	items.forEach(function (item, index) {
		var link = item.querySelector(':scope > a');
		var panel = item.querySelector(':scope > .sub-menu');
		if (!link || !panel) {
			return;
		}

		panel.id = panel.id || 'mega-panel-' + index;

		var button = document.createElement('button');
		button.type = 'button';
		button.className = 'mega-toggle';
		button.setAttribute('aria-expanded', 'false');
		button.setAttribute('aria-controls', panel.id);

		// 読み上げ用のラベル。textContent で入れるのでHTMLとして解釈されない
		var label = document.createElement('span');
		label.className = 'screen-reader-text';
		label.textContent = link.textContent.trim() + 'のサブメニュー';
		button.appendChild(label);

		link.after(button);

		button.addEventListener('click', function () {
			var willOpen = !item.classList.contains('is-open');
			closeAll(item);
			setOpen(item, willOpen);
		});
	});

	// Escキーで閉じ、フォーカスを開閉ボタンに戻す
	document.addEventListener('keydown', function (event) {
		if (event.key !== 'Escape') {
			return;
		}
		items.forEach(function (item) {
			if (item.classList.contains('is-open')) {
				setOpen(item, false);
				var button = item.querySelector(':scope > .mega-toggle');
				if (button) {
					button.focus();
				}
			}
		});
	});

	// メニューの外をクリックしたら閉じる
	document.addEventListener('click', function (event) {
		if (!event.target.closest('.mega-menu')) {
			closeAll(null);
		}
	});
});

ボタンのラベルを innerHTML ではなく textContent で入れているのは、メニュー名に記号が含まれていてもHTMLとして解釈させないためです。screen-reader-text は多くのテーマが用意している「画面には出さず読み上げだけさせる」ためのクラスですが、テーマにない場合は次のCSSを追加します。

.screen-reader-text {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
	white-space: nowrap;
	border: 0;
}

テーマによっては、すでにサブメニュー用の開閉ボタンを自前のJavaScriptで追加しています。その場合ボタンが二重になるので、テーマのボタンを使い、このサンプルからはボタン生成部分を外して is-open の付け外しだけに絞る、といった調整が必要です。

ステップ4:子テーマで読み込む

CSSとJavaScriptは wp_enqueue_scripts フックで読み込みます。ファイルの場所は子テーマを指す get_stylesheet_directory_uri() を使います(親テーマを指す get_template_directory_uri() と取り違えると404になります)。

<?php
// 子テーマの functions.php に追記
add_action( 'wp_enqueue_scripts', 'mychild_enqueue_mega_menu' );

function mychild_enqueue_mega_menu() {
	$dir = get_stylesheet_directory();
	$uri = get_stylesheet_directory_uri();

	wp_enqueue_style(
		'mychild-mega-menu',
		$uri . '/assets/css/mega-menu.css',
		array(),
		filemtime( $dir . '/assets/css/mega-menu.css' )
	);

	wp_enqueue_script(
		'mychild-mega-menu',
		$uri . '/assets/js/mega-menu.js',
		array(),
		filemtime( $dir . '/assets/js/mega-menu.js' ),
		true
	);
}

バージョン番号に filemtime() でファイルの更新時刻を渡しているので、ファイルを編集するとURLの ver= が変わり、ブラウザの古いキャッシュが使われ続ける問題を避けられます。最後の true はスクリプトをフッターで読み込む指定です。

ステップ5:動作確認のチェックリスト

実装後は、次の操作をひととおり試します。

  • パソコンでマウスを乗せるとパネルが開き、外すと閉じる
  • Tabキーで「製品」→開閉ボタン→パネル内のリンクと移動でき、Escで閉じる
  • スマートフォン幅で開閉ボタンをタップすると縦に並んだパネルが開く
  • 開発者ツールで開閉ボタンの aria-expanded が true / false に切り替わる
  • パネルが本文やスライダーの下に隠れない(隠れる場合は z-index を調整)

キャッシュ系プラグインやCDNを使っている場合、CSSやJavaScriptを更新しても古いファイルが配信されることがあります。反映されないときはキャッシュを削除してから確認します。

まとめ

  • メガメニューは、親項目に mega-menu クラスを付け、2階層目を列見出し、3階層目をリンクにする構成で作ります。
  • CSSでは li.mega-menu を position: static にして、ナビゲーション全体を基準にパネルを広げます。セレクターはテーマの構造に合わせて置き換えます。
  • JavaScriptで開閉ボタンと aria-expanded、Escキーでのクローズを補うと、タッチ端末やキーボードでも使えます。
  • 読み込みは子テーマの functions.php から wp_enqueue_scripts で行い、変更前にバックアップを取っておきます。
  • このサンプルはクラシックテーマ向けです。ブロックテーマのナビゲーションブロックには別の方法が必要です(2026年10月現在)。

お気軽にご相談ください

お見積りへ お問い合わせへ
▲