【WordPress】wp_nav_menuの徹底解説|ナビメニューの表示方法とカスタマイズ手順

【WordPress】wp_nav_menuの徹底解説|ナビメニューの表示方法とカスタマイズ手順 WordPress

WordPress のテーマでナビゲーションを出力する中核 API が wp_nav_menu() です。メニューの登録、設置、クラス名の調整、アクセシビリティ対応、マークアップの置き換え(walker)まで、制作現場でよく使う手順を順番にまとめます。

メニューの「設置場所」を登録する

まずは管理画面の「外観 → メニュー」で割り当てられる「テーマの場所」を用意します。functions.php に登録処理を書き、ヘッダーやフッターなど複数のロケーションを定義します。

<?php
add_action('after_setup_theme', function () {
    register_nav_menus([
        'header' => 'ヘッダーナビ',
        'footer' => 'フッターナビ',
    ]);
});

テンプレートで出力する(最小構成)

登録した場所に紐づくメニューを、テンプレート(例:header.php)で出力します。最小限なら以下の一行で OK です。

<?php
wp_nav_menu([
    'theme_location' => 'header',
]);

HTML 構造とクラス名を整える

BEM などの命名に合わせたいときは、ラッパー要素やリストのクラス、コンテナ有無を指定します。containerfalse にすると余計な div を出しません。

<?php
wp_nav_menu([
    'theme_location' => 'header',
    'container'      => false,
    'menu_class'     => 'gnav__list',
    'menu_id'        => 'gnav',
    'depth'          => 2, // ドロップダウンまで
]);

メニューが未設定のときの挙動を制御する

本番で空のナビを見せないために、未割り当て時の「フォールバック」を止めたり、任意のメッセージを表示します。

<?php
wp_nav_menu([
    'theme_location' => 'header',
    'container'      => false,
    'fallback_cb'    => '__return_empty_string', // 何も出さない
]);

任意のラッパーマークアップに置き換える(items_wrap)

デフォルトは <ul id="%1$s" class="%2$s">%3$s</ul> です。必要なら nav と組み合わせた独自構造に差し替えます。

<?php
wp_nav_menu([
    'theme_location' => 'header',
    'container'      => 'nav',
    'container_class'=> 'gnav',
    'items_wrap'     => '<ul class="gnav__list" role="list">%3$s</ul>',
]);

現在ページのハイライトを制御する

WordPress は自動で current-menu-item などを付与します。追加で自前のクラスを足したいときはフィルターで調整します。

<?php
add_filter('nav_menu_css_class', function ($classes, $item, $args, $depth) {
    if (in_array('current-menu-item', $classes, true)) {
        $classes[] = 'is-active';
    }
    return $classes;
}, 10, 4);

サブメニュー(ドロップダウン)のマークアップを整える

サブメニューは .menu-item-has-children.sub-menu が自動で出力されます。アクセシビリティのためにトグルボタンと aria-expanded を持たせると操作が明確になります。最も簡単なのは「リンクの後ろにトグルボタンを差し込む」方法です。

<?php
add_filter('walker_nav_menu_start_el', function ($item_output, $item, $depth, $args) {
    if (in_array('menu-item-has-children', $item->classes ?? [], true) && $depth === 0) {
        $button = '<button class="gnav__toggle" aria-expanded="false" aria-label="サブメニューを開閉"><span aria-hidden="true">▼</span></button>';
        $item_output = preg_replace('/<a[^>]*>.*?<\/a>/s', '$0' . $button, $item_output, 1);
    }
    return $item_output;
}, 10, 4);

独自マークアップが必要な場合の最終手段(walker)

Walker_Nav_Menu を継承して「各 li / a / ul の出力」を完全に制御できます。まずは最小限の骨格を作り、必要な断面だけ上書きします。

<?php
class My_Gnav_Walker extends Walker_Nav_Menu {
    public function start_lvl(&$output, $depth = 0, $args = null) {
        $indent = str_repeat("\t", $depth);
        $output .= "\n$indent<ul class=\"gnav__submenu\" role=\"list\">\n";
    }
    public function start_el(&$output, $item, $depth = 0, $args = null, $id = 0) {
        $classes = implode(' ', array_filter($item->classes));
        $output .= "<li class=\"gnav__item {$classes}\">";
        $output .= '<a class="gnav__link" href="' . esc_url($item->url) . '">'
                . esc_html($item->title) . '</a>';
    }
    public function end_el(&$output, $item, $depth = 0, $args = null) {
        $output .= "</li>\n";
    }
}

作成した Walker は 'walker' => new My_Gnav_Walker() を渡して有効化します。

<?php
wp_nav_menu([
    'theme_location' => 'header',
    'container'      => false,
    'walker'         => new My_Gnav_Walker(),
]);

SVG アイコンや外部リンク判定を差し込みたい

メニュー項目が外部サイトならアイコンを付けたい、といった要件は walker_nav_menu_start_el フィルターで実現できます。parse_url() でホストを比較し、該当時のみ SVG を追記します。

<?php
add_filter('walker_nav_menu_start_el', function ($item_output, $item) {
    $host = wp_parse_url(home_url(), PHP_URL_HOST);
    $link = wp_parse_url($item->url, PHP_URL_HOST);
    if ($link && $link !== $host) {
        $icon = '<svg class="icon-external" width="12" height="12" aria-hidden="true">...</svg>';
        $item_output = str_replace('</a>', ' ' . $icon . '</a>', $item_output);
        $item_output = str_replace('<a ', '<a target="_blank" rel="noopener" ', $item_output);
    }
    return $item_output;
}, 10, 2);

ブロックテーマ(FSE)でも使えるのか

サイトエディター主体のブロックテーマでも、PHP テンプレートを併用するなら wp_nav_menu() は利用できます。完全にブロック化した構成では「ナビゲーション」ブロックが推奨ですが、細かい制御が必要な場合や後方互換を考えるなら従来 API を使う設計も現実的です。

パフォーマンスとアクセシビリティの注意点

巨大な多階層メニューは DOM が重くなりやすく、モバイルでは展開制御の JavaScript が描画を遅らせることがあります。初期表示は 1 階層に留め、サブメニューはトグルで遅延展開する方針が安全です。キーボード操作の配慮として、トグルボタンは aria-expanded の状態変化を伴わせ、フォーカスリングを CSS で隠しすぎないようにします。

よく使う引数の早見(説明のみ)

theme_location は登録した場所を指定、menu は特定 ID/名前のメニューを直接出力、containercontainer_class はラッパー要素の制御、menu_classmenu_id は ul の属性、depth は階層の深さ、fallback_cb は未割り当て時のコールバック、items_wrap は ul テンプレート、walker は完全カスタムを行うクラス指定、という役割です。

まとめと実用テンプレート

最低限の設置、BEM 風クラス、フォールバック抑止、アクセシビリティ配慮を兼ねた現場向けテンプレートは次の通りです。まずはこれをベースに、必要になった段階で items_wrap とフィルターや walker を追加していくと、安全に拡張できます。

<?php
// functions.php
add_action('after_setup_theme', function () {
    register_nav_menus(['header' => 'ヘッダーナビ']);
});
add_filter('nav_menu_css_class', function ($classes) {
    if (in_array('current-menu-item', $classes, true)) $classes[] = 'is-active';
    return $classes;
}, 10, 1);

// header.php
?>
<nav class="gnav" aria-label="グローバルナビ">
  <?php
  wp_nav_menu([
      'theme_location' => 'header',
      'container'      => false,
      'items_wrap'     => '<ul class="gnav__list" role="list">%3$s</ul>',
      'menu_class'     => 'gnav__list',
      'fallback_cb'    => '__return_empty_string',
      'depth'          => 2,
  ]);
  ?>
</nav>

wp_nav_menu() は「登録 → 出力 → 微調整 → 必要なら walker」の順に組み立てると迷いません。まずはコンテナやクラスの整形から着手し、挙動の変更はフィルター、構造の置き換えは walker で段階的に行うのがメンテナンス性の高い実装です。