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 クラスを付ける
- 管理画面の「外観 → メニュー」を開きます。
- 右上の「表示オプション」を開き、「CSS クラス」にチェックを入れます。列見出しに説明文を出したい場合は「説明」にもチェックを入れます。
- メガメニューにしたい 1 階層目の項目を開き、「CSS クラス(オプション)」に
has-mega-menuと入力します。 - 子項目を 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