2026年5月20日

2026年5月20日

WordPress のメガメニューをプラグインなしで作る方法|CSS クラスと CSS Grid で複数列表示

メガメニューは、グローバルメニューの子メニューを 1 列のドロップダウンではなく、横幅いっぱいのパネルに複数列で並べる表示です。子メニューの数が多いサイトで、項目を「列見出し+その下のリンク」に分けて見せられます。

この記事では、クラシックテーマ(「外観 → メニュー」でメニューを作るテーマ) を前提に、プラグインを使わずに作る方法を説明します。やることは 3 つです。

  • 管理画面で、メガメニューにしたい親項目に CSS クラス has-mega-menu を付ける
  • CSS で、その親項目の子メニューを横幅いっぱいのパネルにし、CSS Grid で列に分ける
  • スマートフォンとタッチ操作のために、開閉ボタンを JavaScript で足す

ブロックテーマ(ナビゲーションブロックでメニューを作るテーマ)の場合は、最後の「ブロックテーマやプラグインを使う場合」を見てください。

メニューの組み方

メガメニューは、メニューの階層をそのまま列に割り当てます。

階層 役割 例
1 階層目 グローバルメニューの項目。ここに has-mega-menu を付ける サービス
2 階層目 パネルの各列の見出し 制作、保守、集客、相談
3 階層目 列の中のリンク 新規制作、リニューアル、…

2 階層目の項目数がそのまま列の数になります。この記事の CSS は 4 列で並べるので、列見出しは 4 つ前後にしておくと見やすくなります。

手順 1: 親項目に CSS クラスを付ける

  1. 管理画面の「外観 → メニュー」を開きます。
  2. 右上の「表示オプション」を開き、「CSS クラス」にチェックを入れます。列見出しに説明文を出したい場合は「説明」にもチェックを入れます。
  3. メガメニューにしたい 1 階層目の項目を開き、「CSS クラス(オプション)」に has-mega-menu と入力します。
  4. 子項目を 2 階層目(列見出し)と 3 階層目(リンク)にドラッグで並べ、「メニューを保存」を押します。

WordPress はメニューを出力するとき、子メニューの ul に sub-menu、子を持つ項目の li に menu-item-has-children というクラスを付けます。以下の CSS はこのクラスと、手順 3 で付けた has-mega-menu を使います。

手順 2: テーマのクラス名を確かめる

CSS のセレクターはテーマによって変わります。サイトを開き、ブラウザーの開発者ツールでグローバルメニューを調べて、次の 2 つのクラス名を確認します。

  • メニュー全体の ul のクラス(この記事では nav-menu。テーマによっては menu や primary-menu)
  • メニューを囲む nav などの要素のクラス(この記事では main-navigation)

以下のコードの .nav-menu と .main-navigation は、確認したクラス名に置き換えてください。

自作テーマでメニューを出力する場合は、functions.php でメニュー位置を登録し、テンプレートで wp_nav_menu() を呼びます。

<?php
add_action( 'after_setup_theme', function () {
	register_nav_menus( array( 'primary' => 'メインメニュー' ) );
} );

function my_theme_primary_nav() {
	wp_nav_menu( array(
		'theme_location'  => 'primary',
		'container'       => 'nav',
		'container_class' => 'main-navigation',
		'menu_class'      => 'nav-menu',
	) );
}

header.php のメニューを出したい場所で my_theme_primary_nav(); を呼びます。

手順 3: CSS でパネルと列を作る

CSS は子テーマの style.css、または「外観 → カスタマイズ → 追加 CSS」に追加します。スマートフォンを基準に書き、画面幅が 769px 以上のときだけドロップダウンとメガメニューのパネルにしています。

/* 共通(スマートフォン基準): 子メニューは閉じておき、is-open で開く */
.nav-menu,
.nav-menu .sub-menu {
	list-style: none;
	margin: 0;
	padding: 0;
}
.nav-menu .sub-menu {
	display: none;
}
.nav-menu li.is-open > .sub-menu {
	display: block;
}
/* メガメニューの 3 階層目は、パネルが開いていれば常に表示 */
.nav-menu > li.has-mega-menu .sub-menu .sub-menu {
	display: block;
	padding-left: 1rem;
}
.mega-menu-desc {
	margin: 0.25rem 0 0.5rem;
	font-size: 0.85rem;
	color: #50575e;
}

/* PC(769px 以上) */
@media (min-width: 769px) {
	/* パネルの横幅の基準になる要素 */
	.main-navigation {
		position: relative;
	}
	.nav-menu {
		display: flex;
		gap: 1.5rem;
	}
	.nav-menu > li {
		position: relative;
	}
	/* 通常のドロップダウン */
	.nav-menu .sub-menu {
		position: absolute;
		top: 100%;
		left: 0;
		z-index: 100;
		min-width: 200px;
		padding: 0.5rem 1rem;
		background: #fff;
		border: 1px solid #dcdcde;
		box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
	}
	.nav-menu li:hover > .sub-menu,
	.nav-menu li:focus-within > .sub-menu {
		display: block;
	}

	/* メガメニュー: 親項目を static にして、.main-navigation の幅でパネルを広げる */
	.nav-menu > li.has-mega-menu {
		position: static;
	}
	.nav-menu > li.has-mega-menu > .sub-menu {
		right: 0;
		padding: 1.5rem 2rem;
	}
	.nav-menu > li.has-mega-menu:hover > .sub-menu,
	.nav-menu > li.has-mega-menu:focus-within > .sub-menu,
	.nav-menu > li.has-mega-menu.is-open > .sub-menu {
		display: grid;
		grid-template-columns: repeat(4, minmax(0, 1fr));
		gap: 1.5rem;
	}
	/* 列見出し(2 階層目) */
	.nav-menu > li.has-mega-menu > .sub-menu > li > a {
		display: block;
		margin-bottom: 0.5rem;
		font-weight: 700;
		border-bottom: 1px solid #dcdcde;
	}
	/* 列の中のリンク(3 階層目)はパネル内にそのまま並べる */
	.nav-menu > li.has-mega-menu .sub-menu .sub-menu {
		position: static;
		min-width: 0;
		padding: 0;
		background: none;
		border: 0;
		box-shadow: none;
	}
}

要点は次の 3 つです。

  • パネルの幅の基準: position: absolute の要素は、position が static 以外の最も近い祖先を基準に配置されます。親の li を static にし、.main-navigation を relative にすることで、パネルの左右がメニュー全体の幅にそろいます。画面の端から端まで広げたい場合は、ヘッダー全体を囲む要素に position: relative を付けます。
  • 表示の切り替え: 通常のドロップダウンは display: block、メガメニューのパネルだけ display: grid にしています。メガメニュー側のセレクターのほうが詳細度が高いので、こちらが優先されます。
  • キーボード操作: :focus-within を書いているので、Tab キーで親項目にフォーカスしたときもパネルが開きます。:hover だけだとキーボードでは開きません。

確認時につまずきやすい点もあります。

  • 親要素のどこかに overflow: hidden があると、パネルが切れて見えなくなります。開発者ツールで祖先の要素を順に確認してください。
  • パネルの上端は .main-navigation の下端にそろいます。メニュー項目とパネルの間にすき間があると、マウスを動かす途中で :hover が外れて閉じます。すき間ができる場合は、.main-navigation の下側の余白を li 側の padding に移します。
  • テーマが持つメニューの CSS(display や position)が残っていると、ここでの指定と競合します。開発者ツールで打ち消されている指定を探して調整します。

手順 4: スマートフォンとタッチ操作用の開閉ボタンを足す

タッチ操作では :hover が当てにならず、スマートフォンでは上の CSS がパネルを閉じたままにします。親項目の横に開閉ボタンを入れ、押すと is-open クラスを付け外しする JavaScript を足します。

子テーマに js/mega-menu.js を作ります。

document.querySelectorAll('.nav-menu > li.has-mega-menu').forEach(function (item) {
	var link = item.querySelector(':scope > a');
	var panel = item.querySelector(':scope > .sub-menu');
	if (!link || !panel) {
		return;
	}
	var button = document.createElement('button');
	button.type = 'button';
	button.className = 'mega-menu-toggle';
	button.setAttribute('aria-expanded', 'false');
	button.setAttribute('aria-label', link.textContent.trim() + 'のサブメニューを開閉');
	button.textContent = '+';
	link.after(button);

	button.addEventListener('click', function () {
		var open = item.classList.toggle('is-open');
		button.setAttribute('aria-expanded', open ? 'true' : 'false');
		button.textContent = open ? '-' : '+';
	});
});

// Esc キーで開いているパネルを閉じる
document.addEventListener('keydown', function (event) {
	if (event.key !== 'Escape') {
		return;
	}
	document.querySelectorAll('.nav-menu > li.has-mega-menu.is-open').forEach(function (item) {
		item.classList.remove('is-open');
		var button = item.querySelector(':scope > .mega-menu-toggle');
		if (button) {
			button.setAttribute('aria-expanded', 'false');
			button.textContent = '+';
		}
	});
});

子テーマの functions.php で読み込みます。wp_enqueue_script() の 5 つ目の引数に配列で strategy を渡す書き方は WordPress 6.3 以降で使えます(2026年11月時点の現行版でも同じです)。

<?php
add_action( 'wp_enqueue_scripts', function () {
	wp_enqueue_script(
		'my-mega-menu',
		get_stylesheet_directory_uri() . '/js/mega-menu.js',
		array(),
		'1.0.0',
		array(
			'in_footer' => true,
			'strategy'  => 'defer',
		)
	);
} );

Esc キーで閉じるのは、ボタンで開いた(is-open が付いた)パネルです。マウスを載せている間やフォーカスがパネル内にある間は、CSS の :hover と :focus-within で開いたままになります。

手順 5: 列見出しに説明文を出す(任意)

メニュー項目の「説明」欄に入れた文は、標準ではテーマが出力しないことが多いです。walker_nav_menu_start_el フィルターを使うと、メニュー項目の HTML の後ろに文を足せます。次のコードは、primary の位置にあるメニューの 2 階層目($depth は 0 から数えるので 1)だけに説明文を出します。

<?php
add_filter( 'walker_nav_menu_start_el', function ( $item_output, $menu_item, $depth, $args ) {
	if ( 'primary' !== $args->theme_location || 1 !== $depth || '' === trim( $menu_item->description ) ) {
		return $item_output;
	}
	return $item_output . '<p class="mega-menu-desc">' . esc_html( $menu_item->description ) . '</p>';
}, 10, 4 );

primary はテーマが登録しているメニュー位置の名前です。テーマによって違うので、テーマの functions.php などにある register_nav_menus() の記述で確認して置き換えます。

Walker_Nav_Menu を継承した独自のウォーカーを作る方法もありますが、クラスを足す・文を足す程度であれば、この記事の CSS クラスとフィルターで足ります。画像やウィジェットを列に入れるなど、HTML の構造そのものを変えたいときに独自ウォーカーを検討します。

ブロックテーマやプラグインを使う場合

  • ブロックテーマ: Twenty Twenty-Four などのブロックテーマでは、メニューはサイトエディターのナビゲーションブロックで作ります。テーマがメニューの位置を登録していなければ「外観 → メニュー」自体が表示されません。ナビゲーションブロックの出力する HTML とクラス名はこの記事のものと違うため、上の CSS と JavaScript はそのままでは使えません。
  • プラグイン: 列の中に画像やウィジェットを入れたい、管理画面で列の数や幅を決めたい、という場合は、メガメニュー用のプラグインを使う方法があります。たとえば wordpress.org で配布されている Max Mega Menu は、2026年11月時点でブロックテーマでも使えると説明されています。プラグインを入れる前に、テーマのメニューの CSS と競合しないかをテスト環境で確認してください。

メニューの基本的な作り方や表示の調整は「WordPress のナビゲーションメニューをカスタマイズする方法」で説明しています。

まとめ

  • クラシックテーマでは、「外観 → メニュー」で親項目に has-mega-menu クラスを付け、2 階層目を列見出し、3 階層目をリンクとして並べます。
  • CSS では親の li を position: static、メニュー全体を囲む要素を position: relative にし、パネルを display: grid で列に分けます。:focus-within も書いてキーボードで開けるようにします。
  • スマートフォンとタッチ操作には、is-open クラスを付け外しする開閉ボタンを JavaScript で足します。
  • ブロックテーマでは HTML の構造が違うため、この記事のコードはそのまま使えません。

参考: wp_nav_menu()、walker_nav_menu_start_el、wp_enqueue_script()、MDN – :focus-within

お気軽にご相談ください

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