ナビゲーションの合否をキーボードで出す|到達・可視・状態の実測手順


ナビゲーションが使えているかどうかは、マウスを置いてキーボードだけで操作すれば判定できます。Tabキーで全項目に到達できるか、いまどこにフォーカスがあるか見えるか、開いているか閉じているかと現在地が支援技術に伝わるか。この3点で落ちるナビゲーションは、見た目がどれだけ整っていても使えていません。

この記事は、ナビゲーションの作り方を最初から教える入門記事ではありません。すでに自分でナビゲーションを組んだ人が、それを検品して落ちた箇所を直すための手順書です。ブラウザのコンソールに貼るだけのスクリプトを3本用意しました。自分のサイトを開いて貼れば、その場で合否が出ます。

掲載しているコードとスクリプトは、すべて実ブラウザ(Playwright 1.62.1 + Chromium・macOS・ビューポート 320 / 390 / 1200px)で動かし、この記事に書いた数値がそのまま出ることを2026年8月2日に確認しています。動かない状態のコードは載せていません。


ナビゲーションの合否は「到達・可視・状態」の3つで出る

ナビゲーションの検品とは、キーボード操作で次の3つが成立しているかを確かめる作業のことです。3つのうち1つでも落ちていれば、そのナビゲーションはマウスを使わない利用者にとって機能していません。逆にいえば、この3つが通っていれば、配色やアニメーションの好みとは無関係に「操作できるナビゲーション」だと言い切れます。

検査合格ライン落ちると起きること
到達Tabでナビ内の全項目に到達でき、画面外の要素にフォーカスが入らないフォーカスが見えないまま何度もTabを押す羽目になる
可視フォーカス中の項目に枠が出て、背景との比が3:1以上いま何を選んでいるのか分からなくなる
状態開閉ボタンの状態と現在ページが属性で伝わる開いたのか閉じたのか、いまどこにいるのかが読み上げられない

なぜこの3つなのか

この3つは筆者が思いついた区分ではなく、WCAG 2.2の達成基準をナビゲーションに当てはめて整理したものです。到達はSC 2.4.3 Focus Order(レベルA)、可視はSC 2.4.7 Focus Visible(レベルAA)とSC 1.4.11 Non-text Contrast(レベルAA)、状態は支援技術への情報提供にあたります。原文はいずれもW3Cが公開しています。

SC 2.4.3 Focus Order(レベルA)は「If a web page can be navigated sequentially and the navigation sequences affect meaning or operation, focusable components receive focus in an order that preserves meaning and operability.」、SC 2.4.7 Focus Visible(レベルAA)は「Any keyboard operable user interface has a mode of operation where the keyboard focus indicator is visible.」と定めています。出典は W3C「Understanding SC 2.4.3 Focus Order」および W3C「Understanding SC 2.4.7 Focus Visible」(いずれも取得日 2026-08-02)。

見た目のきれいさは判定対象に入れない

ナビゲーションの記事はしばしば「シンプルにする」「ユーザーの視点で考える」といった助言で終わります。これらは反証できないため、読んだあとに自分のサイトが良いのか悪いのか判定できません。この記事は、コンソールに数値が出て合否が分かれる項目だけを扱います。配色・余白・アニメーションの良し悪しは扱いません。

ナビゲーションだけでなくUI全体を数値で検品したい場合は、タップ領域とコントラストを一括で測る手順を別にまとめてあります。この記事はそのうち「キーボードで操作できるか」だけを深掘りする位置づけです。なぜアクセシビリティに取り組むのかという法令・規格の側から知りたい場合は、WCAGとJISの体系をまとめた記事が入口になります。


マウスで確認しても、壊れていることは分からない

マウスはhoverとclickしか使いません。フォーカスの順番も、フォーカスの見え方も、状態が支援技術に伝わっているかも、マウスでは一度も通らない経路です。だから開発中にマウスで何度クリックしても、キーボード操作が壊れていることには気づけません。

開閉メニューを作った瞬間に壊れる

素のリンクを並べただけのナビゲーションは、何もしなくてもTabキーで移動できます。壊れるのは「開閉するメニュー」を作った瞬間です。ハンバーガーメニュー、ドロップダウン、モバイル用のドロワー。閉じている状態を作った途端に、次の3つが同時に発生します。

  1. 閉じているはずのメニューがフォーカス順に残る。画面の外に押し出しただけでは、フォーカスは律儀にそこへ入っていきます。
  2. 開いているか閉じているかが支援技術に伝わらない。ボタンの見た目が変わっても、それは目で見える人にしか届きません。
  3. デザインを整える過程で outline: none を書いてしまい、フォーカスの枠が消える。マウス操作では枠が出ないので、消したことに気づけません。

「隠したつもり」で隠れていない書き方がある

1番の「フォーカス順に残る」は、隠し方の選択で決まります。実際に8通りの隠し方を並べたページを作り、Tabキーを押し続けてどこに到達するかを実測しました。結果は次のとおりです(2026-08-02・Chromium・390px幅)。

隠し方フォーカス順実測
display: none外れる到達しない
hidden 属性外れる到達しない
visibility: hidden外れる到達しない
inert 属性外れる到達しない。見えたままでも外れる
transform で画面外へ残るx座標 -272 の位置に到達
left: -9999px残るx座標 -9999 の位置に到達
opacity: 0残る透明なまま到達
height: 0 と overflow: hidden残る潰れた状態で到達

下4つはアクセシビリティツリーにも残り続けます。同じ実測でツリーを取得したところ、上4つのリンクは1つも現れず、下4つは全て「link」として列挙されました。つまり「見えないのに読み上げられ、しかもフォーカスが入る」状態です。

アニメーション付きのドロワーを作ると transform は避けにくくなります。その場合は inert を併用します。MDNは inert を付けた要素とその子孫について「Cannot be focused」「Are hidden from assistive technologies as they are excluded from the accessibility tree」と説明しています(出典: MDN「inert」・取得日 2026-08-02)。見た目のアニメーションを保ったまま、フォーカスとアクセシビリティツリーの両方から外せます。

なお aria-hidden="true" だけを付けるのは解決になりません。フォーカスできる要素が残ったまま読み上げからだけ消えると、キーボード利用者はフォーカスが行方不明になった要素に閉じ込められます。隠すなら、フォーカス順からも外してください。


実測①:Tabで全項目に到達できるか

自分のサイトを開き、ブラウザの開発者ツールでコンソールを開いて、次のスクリプトを貼り付けて実行します。ページ上でいまフォーカス順に残っている要素を一覧にし、画面外にあるものと24px未満のものを印付きで返します。

(() => {
  const sel = 'a[href], button, input, select, textarea, [tabindex]:not([tabindex="-1"])';
  const rows = [...document.querySelectorAll(sel)]
    .filter(el => el.offsetParent !== null || getComputedStyle(el).position === 'fixed')
    .map(el => {
      const r = el.getBoundingClientRect();
      return {
        ラベル: (el.innerText || el.getAttribute('aria-label') || '').trim().slice(0, 16),
        幅: Math.round(r.width), 高さ: Math.round(r.height),
        x: Math.round(r.left), y: Math.round(r.top),
        画面外: (r.right <= 0 || r.bottom <= 0 || r.left >= innerWidth) ? 'NG' : '',
        小さい: (r.width < 24 || r.height < 24) ? 'NG' : ''
      };
    });
  console.table(rows);
  console.log('フォーカス順に残っている要素:', rows.length + '件',
    '/画面外NG:', rows.filter(r => r.画面外).length + '件',
    '/24px未満:', rows.filter(r => r.小さい).length + '件');
})();

出力の読み方と合否ライン

表が出力されます。見るのは末尾の2列です。

意味NGになる条件
画面外フォーカスは入るのに表示領域の外にあるスキップリンク以外で1件でも出たら不合格
小さいタップ領域が24px四方を下回る間隔が詰まっている箇所で出たら要修正

実行するタイミングは2回です。メニューを閉じた状態で1回、メニューを開いた状態でもう1回。閉じた状態で「画面外NG」が出るなら、それが幽霊フォーカスです。読者はフォーカスリングが画面の外に消えたまま、何度もTabを押すことになります。

「小さい」の判定は、24px未満なら即不合格というわけではありません。SC 2.5.8 Target Size (Minimum)(レベルAA)は「The size of the target for pointer inputs is at least 24 by 24 CSS pixels, except when:」としたうえで、間隔の例外を認めています。「Undersized targets (those less than 24 by 24 CSS pixels) are positioned so that if a 24 CSS pixel diameter circle is centered on the bounding box of each, the circles do not intersect another target or the circle for another undersized target」です(出典: W3C「Understanding SC 2.5.8 Target Size (Minimum)」・取得日 2026-08-02)。つまり十分に離れていれば通ります。判断に迷うなら、素直に min-height: 44px を与えるほうが早いです。

スキップリンクだけは例外として扱う

このスクリプトは、スキップリンクを「画面外NG」として拾います。スキップリンクは普段は画面外に置き、フォーカスされたときだけ画面内に現れる部品なので、これは誤検出です。切り分け方は簡単で、Tabキーを1回押してスキップリンクにフォーカスした状態で、もう一度スクリプトを実行します。

この記事の後半で示すコードで実測すると、フォーカス前は x = -9999、Tabを1回押した後は x = 8 / y = 8 / 143×48px になりました。フォーカスすると画面内に入ってくるなら正常です。逆に、フォーカスしても x が負のままなら、それは本物の幽霊フォーカスです。

そもそもスキップリンクが1つも無いなら、それ自体が欠落です。SC 2.4.1 Bypass Blocks(レベルA)は「A mechanism is available to bypass blocks of content that are repeated on multiple web pages.」と定めています(出典: W3C「Understanding SC 2.4.1 Bypass Blocks」・取得日 2026-08-02)。全ページ共通のナビゲーションは、まさにここで言う「繰り返されるブロック」です。


実測②:フォーカスが見えているか

到達できても、いまどこにフォーカスがあるか見えなければ操作できません。フォーカスの枠が消える原因はほぼ1つで、CSSのどこかに outline: noneoutline: 0 が書かれていることです。ページ全体のスタイルシートを走査して、該当するルールを洗い出します。

(() => {
  const hits = [];
  for (const sheet of document.styleSheets) {
    let rules;
    try {
      rules = sheet.cssRules;
    } catch (e) {
      hits.push('(読めないCSS: ' + (sheet.href || '別ドメイン') + ')');
      continue;
    }
    for (const r of rules) {
      if (!r.selectorText || !r.style) continue;
      const o = (r.style.outline || '') + ' ' + (r.style.outlineWidth || '') + ' ' + (r.style.outlineStyle || '');
      if (/(^|\s)(none|0px|0)(\s|$)/.test(o.trim())) hits.push(r.selectorText);
    }
  }
  console.log(hits.length ? hits : 'outlineを消しているルールは見つかりません');
})();

出力の読み方と合否ライン

セレクタの配列が返ってきたら、その中にナビゲーションのリンクやボタンに当たるものが混ざっていないかを見ます。*abutton:focus を含むセレクタが出たら要注意です。「outlineを消しているルールは見つかりません」と出れば、この項目は合格です。

「(読めないCSS: 別ドメイン)」が混ざることがあります。これは別ドメインから配信されたスタイルシートで、ブラウザのセキュリティ制約により中身を読めません。その場合は、実際にTabキーを押して目視で枠が出るかを確かめてください。

枠を消さずにデザインを整える

outline: none を書きたくなる理由は、マウスでクリックしたときにも枠が出るのが嫌だから、というのがほとんどです。それは :focus ではなく :focus-visible を使えば解決します。:focus-visible はキーボード操作など「枠を出すべきだ」とブラウザが判断した場面でだけ一致します。

.site-nav :focus-visible {
  outline: 3px solid #0b5cab;
  outline-offset: -3px;
}

枠の色は背景に対して3:1以上のコントラストが必要です。SC 1.4.11 Non-text Contrast(レベルAA)は「The visual presentation of the following have a contrast ratio of at least 3:1 against adjacent color(s)」として、その対象に「Visual information required to identify user interface components and states」を挙げています(出典: W3C「Understanding SC 1.4.11 Non-text Contrast」・取得日 2026-08-02)。フォーカス中であることを示す枠は、まさにこの「状態を識別するための視覚情報」にあたります。

上の #0b5cab は白背景に対して 6.70:1 でした。文字色の #1f2937 は 14.68:1 で、こちらは文字用の4.5:1(SC 1.4.3)も満たします。いずれも本記事の実測値です。

もう1つ、WCAG 2.2で新設されたSC 2.4.11 Focus Not Obscured (Minimum)(レベルAA)にも触れておきます。「When a user interface component receives keyboard focus, the component is not entirely hidden due to author-created content.」と定められています(出典: W3C「Understanding SC 2.4.11 Focus Not Obscured (Minimum)」・取得日 2026-08-02)。追従するヘッダーやCookieバナーがフォーカス中の項目を完全に覆っていないかも、Tabを押しながら目視で確認してください。


実測③:開閉と現在地が伝わっているか

3つ目は、目で見て分かることが目で見えない人にも届いているかの検査です。ボタンの見た目が変わっても、現在ページのリンクが太字になっていても、それだけでは支援技術には何も伝わりません。次のスクリプトで、必要な属性が付いているかを確かめます。

(() => {
  const navs = [...document.querySelectorAll('nav')];
  console.log('nav要素:', navs.length + '個');
  navs.forEach(n => console.log('  名前:',
    n.getAttribute('aria-label') || n.getAttribute('aria-labelledby') || '(なし)NG'));

  const t = [...document.querySelectorAll('[aria-expanded]')];
  console.log('aria-expanded を持つ要素:', t.length + '個');
  t.forEach(b => console.log('  ',
    (b.innerText || b.getAttribute('aria-label') || '').trim().slice(0, 16),
    '| expanded=' + b.getAttribute('aria-expanded'),
    '| controls=' + (b.getAttribute('aria-controls') || '(なし)NG')));

  const cur = [...document.querySelectorAll('[aria-current]')];
  console.log('aria-current:', cur.length + '件', cur.map(e => e.getAttribute('aria-current')));
})();

合格ラインは3行ある

出力を上から順に見ます。合格の条件は次の3つです。

  1. ページ内の nav が2つ以上あるなら、全てに名前が付いている。1つしかないなら名前は必須ではありませんが、複数あって片方だけ無名なら区別できません。
  2. 開閉ボタンに aria-expanded があり、開閉に合わせて true と false が入れ替わる。ボタンをクリックしてから再実行して、値が変わることを確かめます。
  3. aria-current が1件以上あり、値が page になっている。0件なら現在地が支援技術に伝わっていません。

MDNは aria-expanded を「The aria-expanded attribute is set on an element to indicate if a control is expanded or collapsed, and whether or not the controlled elements are displayed or hidden.」と定義し、「A button that toggles a widget should have aria-controls set to the id of the toggled widget and aria-expanded set to the current state of the widget.」として aria-controls との対での使用を推奨しています(出典: MDN「aria-expanded」・取得日 2026-08-02)。

aria-current については、MDNが「When you have a group of related elements … with one element in the group styled differently from the others to indicate to the sighted user that this is the current element within its group, the aria-current should be used to inform the assistive technology user what has been indicated via styling.」と述べています(出典: MDN「aria-current」・取得日 2026-08-02)。現在ページを太字や下線で示しているなら、同じことを属性でも伝える必要がある、という関係です。

このサイト自身を実測した結果

手順の説得力は自分のサイトで示すのが早いので、CodeQuest.work のトップページを同じ手順で実測しました(2026-08-02・Chromium・390px と 1200px)。結果は合格と不合格が混在しています。

検査結果実測値
到達合格390px でTabを14回押して、画面外に着地したのは0回
状態(nav名)合格2つの nav に「メインメニュー」「フッターカテゴリー」の名前あり
状態(開閉)一部不足aria-expanded はあるが aria-controls が無い
状態(現在地)合格カテゴリーページでサイドバーとフッターの該当項目に aria-current="page"

到達が合格している理由は、閉じたドロワーを inert の内側に置いているからです。実測では .drawer-close ボタンが x = -264 の位置にあり display: flex かつ visibility: visible のまま、つまり画面外に押し出されているだけの状態でした。それでもフォーカスが一度も入らなかったのは、祖先に inert が付いているためです。前掲の表で「transform で画面外へ」が不合格だったのに、このサイトが合格しているのは、この一手の差です。

現在地については、この記事を書いた時点では aria-current が付いておらず未対応でした。記事で「現在地を伝えよう」と書きながら自分が対応できていないのは片手落ちなので、公開にあわせて実装しています。カテゴリーページを開くと、サイドパーとフッターのカテゴリー一覧のうち表示中のものに aria-current="page" が付き、トップページではロゴに付きます。あわせて [aria-current="page"] を太字にして、音声だけでなく目でも現在地が分かるようにしました。色を変えるのではなく太さを変えているのは、色だけに頼らないためです。

なお、記事ページを同じ手順で測ると aria-current は0件になります。これは付け忘れではなく、ナビゲーションの中に「いま読んでいる記事」を指すリンクが存在しないためです。現在地を示せるのは、ナビゲーションが現在のページを指している場合だけです。自分のサイトで0件だったときは、まず「そのナビにいまのページへのリンクがあるか」を確かめてください。


落ちた箇所を直す:実測で合格したコード

3つの実測で落ちた項目を全て通すナビゲーションを、HTML・CSS・JavaScriptの3つに分けて示します。この記事の下部に実測結果を載せてあります。そのまま貼れば、同じ数値が出ます。

HTML

<a class="skip-link" href="#main">本文へスキップ</a>

<button class="nav-toggle" type="button" aria-expanded="false" aria-controls="site-menu">
  <span></span><span></span><span></span>
  <span class="visually-hidden">メニュー</span>
</button>

<nav class="site-nav" aria-label="メインナビゲーション">
  <ul class="nav-panel" id="site-menu">
    <li><a href="/" aria-current="page">ホーム</a></li>
    <li>
      <button class="nav-btn" type="button" aria-expanded="false" aria-controls="submenu-services">サービス</button>
      <ul class="nav-panel submenu" id="submenu-services">
        <li><a href="/services/web-design">ウェブデザイン</a></li>
        <li><a href="/services/seo">SEO</a></li>
      </ul>
    </li>
    <li><a href="/about">会社情報</a></li>
    <li><a href="/contact">お問い合わせ</a></li>
  </ul>
</nav>

<main id="main" tabindex="-1">
  <!-- 本文 -->
</main>

サブメニューを開くのが a ではなく button になっている点が重要です。押しても移動しないものはリンクではありません。button にすればEnterとSpaceの両方で起動し、支援技術にも「ボタン」として伝わります。ハンバーガーの三本線は装飾なので、読み上げ用のテキストを visually-hidden で別に持たせています。

CSS

*, *::before, *::after { box-sizing: border-box; }

.skip-link { position: absolute; left: -9999px; }
.skip-link:focus {
  left: 8px; top: 8px; z-index: 10;
  background: #ffffff; padding: 12px 16px; outline: 3px solid #0b5cab;
}
.visually-hidden { position: absolute; left: -9999px; }

.site-nav ul { list-style: none; margin: 0; padding: 0; }
.site-nav a,
.site-nav .nav-btn {
  display: flex; align-items: center;
  min-height: 44px; padding: 10px 16px; width: 100%;
  font-size: 16px; font-family: inherit; text-align: left;
  color: #1f2937; text-decoration: none;
  background: none; border: 0; cursor: pointer;
}
.site-nav a[aria-current="page"] { font-weight: 700; box-shadow: inset 4px 0 0 #0b5cab; }
.site-nav :focus-visible { outline: 3px solid #0b5cab; outline-offset: -3px; }
.submenu a { padding-left: 32px; }

.nav-panel { display: none; }
.nav-panel.is-open { display: block; }

.nav-toggle {
  display: flex; flex-direction: column; justify-content: center; gap: 5px;
  width: 44px; height: 44px; padding: 0 10px;
  border: 0; background: none; cursor: pointer;
}
.nav-toggle span:not(.visually-hidden) {
  display: block; height: 3px; background: #1f2937; border-radius: 2px;
}
.nav-toggle:focus-visible { outline: 3px solid #0b5cab; outline-offset: 2px; }

@media (min-width: 769px) {
  .nav-toggle { display: none; }
  #site-menu, #site-menu.is-open { display: flex; }
  .site-nav a, .site-nav .nav-btn { width: auto; }
}

開閉に hidden 属性ではなく .is-open クラスと display の切り替えを使っています。理由は、デスクトップ幅ではメニューを常に表示したいからです。hidden 属性を付けたままCSSで display: flex に打ち消すと、属性が実態と食い違う状態になります。クラスで切り替えれば、JavaScriptが動かない環境でもデスクトップ幅ではメニューがそのまま表示されます。

JavaScript

document.querySelectorAll('[aria-expanded][aria-controls]').forEach(function (btn) {
  var panel = document.getElementById(btn.getAttribute('aria-controls'));
  if (!panel) return;

  btn.addEventListener('click', function () {
    var open = btn.getAttribute('aria-expanded') === 'true';
    btn.setAttribute('aria-expanded', String(!open));
    panel.classList.toggle('is-open', !open);
  });

  panel.addEventListener('keydown', function (e) {
    if (e.key !== 'Escape') return;
    e.stopPropagation();
    btn.setAttribute('aria-expanded', 'false');
    panel.classList.remove('is-open');
    btn.focus();
  });
});

ハンバーガーもサブメニューも「ボタンとパネルの対」でしかないので、aria-expandedaria-controls の両方を持つ要素を一括で処理しています。ボタンを増やしてもJavaScriptを書き足す必要はありません。

Escapeの扱いは、W3CのWAI-ARIA Authoring Practicesにある Disclosure Navigation の例に合わせています。同例のキーボード仕様は「If a dropdown is open, closes it and sets focus on the button that controls that dropdown.」です(出典: W3C WAI-ARIA APG「Disclosure Navigation Menu Example」・取得日 2026-08-02)。閉じるだけでなく、フォーカスを開閉ボタンに戻すところまでが仕様です。e.stopPropagation() を入れているのは、サブメニューでEscapeを押したときに親メニューまで一緒に閉じないようにするためです。

このコードの実測結果

上の3つを1枚のHTMLに結合し、320 / 390 / 1200px の3つの幅で実際に動かした結果です。数値は本記事のために取得したもので、同じ手順を踏めば再現できます。

検証項目320 / 390px1200px
横スクロールの発生なしなし
閉じた状態でフォーカスが画面外へ0回0回
Enterでの開閉false から true に切り替わり表示常時表示
タップ領域24px未満の不合格0件0件
Escapeでサブメニューのみ閉じる合格合格
Escape後のフォーカス復帰先開閉ボタン開閉ボタン
JavaScriptエラー0件0件

ナビゲーション内の各項目の実寸は、320px幅で開閉ボタンが44×44px、メニュー項目が幅いっぱい×44pxでした。1200px幅では「ホーム」が79×44px、「サービス」が96×44px、「お問い合わせ」が128×44pxで、いずれも高さ44pxを確保しています。文字色のコントラストは14.68:1、フォーカスリングは6.70:1です。

アクセシビリティツリーも1200px幅で取得しました。「本文へスキップ」「メインナビゲーション」「ホーム」「サービス(expanded の状態つき)」「会社情報」「お問い合わせ」「ウェブデザイン」「SEO」が全て露出しており、デスクトップ幅で display を切り替えてもリンクが消えていないことを確認しています。

ただし、ここで確認したのはアクセシビリティツリー上のロール・名前・状態までです。実際のスクリーンリーダー(VoiceOver / NVDA)でどう読み上げられるかは検証していません。属性が正しく付いていることと、実際に意図どおり読み上げられることは別の話なので、そこまで求めるなら手元の環境で聞いてください。

よくある3つの落ち方と、その1手

3つの実測で落ちたときに、まず何をすればよいかをまとめます。この記事では直し方を1手までにとどめます。1手で直らなければ、その先は個別のマークアップの事情なので、この記事の範囲を超えます。

落ちた項目最初の1手
閉じたメニューにフォーカスが入る閉じている間は display: none にする。アニメーションを残したいなら inert を併用する
フォーカスの枠が見えないoutline を消しているルールを外し、:focus-visible で3px以上の枠を引く
開閉と現在地が伝わらない開閉ボタンに aria-expanded と aria-controls、現在ページのリンクに aria-current=”page” を付ける

見た目のバリエーションを増やしたい場合は、ハンバーガーメニューのCSSをコピペできるツール記事を用意しています。見た目を選んだあとに、この記事の3つの実測を通してください。ナビゲーションの階層設計そのものを見直したいなら、見出しのアウトラインを検証するツールで、ページ構造とナビゲーションの分類が食い違っていないかを確認できます。


よくある質問(FAQ)

Q. メニュー項目は7個以内に絞るべきですか?

その根拠はありません。Nielsen Norman Groupのヤコブ・ニールセン氏は「Short-Term Memory and Web Usability」(2009年12月6日)で「The entire idea of a menu is to rely on recognition rather than recall」「It’s fine to have longer menus (if needed), because users don’t have to memorize the full list of menu items」と述べており、短期記憶の限界をメニュー項目数に当てはめるのは誤用だとしています。同社のページ・ラウブハイマー氏による「How Many Items in a Navigation Menu?」(2019年5月24日)も、項目数を決めるのは4つの要因であって「it’s not 7, despite a common myth」としています(いずれも取得日 2026-08-02)。

Q. モバイルでハンバーガーメニューにしても問題ありませんか?

使用率が落ちることを承知したうえでなら選べます。Nielsen Norman Groupのカーラ・パーニス氏とラルカ・ブディウ氏による「Hamburger Menus and Hidden Navigation Hurt UX Metrics」(2016年6月26日・取得日 2026-08-02)は、デスクトップでは隠されたメニューが使われたのは27%で、可視のナビゲーションは48%、可視と隠しの併用型は50%だったと報告しています。モバイルでは隠しが57%、併用型が86%でした。ナビゲーション操作にかかった時間もデスクトップで31秒(隠し)対23〜26秒(併用・可視)です。画面が狭いモバイルでも、主要な入口だけは常に見える形で残す併用型が有利です。

Q. nav要素に role=”navigation” は付けたほうがよいですか?

付けないでください。nav 要素はそれ自体が navigation のロールを持つため、冗長です。W3Cの「Using ARIA」は第一原則として「If you can use a native HTML element or attribute with the semantics and behavior you require already built in, instead of re-purposing an element and adding an ARIA role, state or property to make it accessible, then do so.」と定めています(2026年2月24日版・取得日 2026-08-02)。付けるべきなのはロールではなく、複数の nav を区別するための aria-label です。

Q. ナビゲーションのSEO効果はどこにありますか?

効果の本体は「発見経路」です。Googleの検索スターターガイド(英語版)(取得日 2026-08-02)は「In fact, the vast majority of the new pages Google finds every day are through links, making links a crucial resource you need to consider to help your pages be discovered by Google」と述べています。ナビゲーションはサイト内の全ページに置かれるリンク集なので、そこに載っていないページはクロールされる経路が細くなります。なお「リンクジュース」という語はGoogleの公式用語ではないため、この記事では使いません。

Q. パンくずリストは検索結果に表示されますか?

デスクトップのみです。Google検索セントラルのBreadcrumb構造化データのドキュメント(取得日 2026-08-02)は、機能の提供状況について「This feature is available on desktop in all regions and languages where Google Search is available.」と明記しています。モバイル検索での表示は対象外なので、「パンくずを入れれば検索結果に出る」と一括りに説明すると誤りになります。構造化データとしての実装自体はサイト構造をGoogleに伝えるうえで有用です。

Q. グローバルナビゲーションとローカルナビゲーションの違いは?

グローバルナビゲーションはサイト全体で共通のメインメニューで、主要セクションへの入口を並べます。ローカルナビゲーションは特定セクション内の下層ページへの案内で、サイドバーやパンくずリストが代表例です。両方を1ページに置く場合、nav 要素が2つ以上になるので、それぞれに aria-label を付けて区別できるようにしてください。この記事の3つの実測は、どちらのナビゲーションにもそのまま適用できます。

Q. スクリーンリーダーでの読み上げまで確認する必要はありますか?

属性が揃ってから確認してください。この記事の3つの実測は、属性とフォーカス順という「読み上げの前提」が整っているかを見るものです。前提が崩れたままスクリーンリーダーを立ち上げても、原因の切り分けができません。逆に、属性が揃ったあとの読み上げは環境差が大きく、macOSのVoiceOverとWindowsのNVDAでも挙動が違います。この記事の検証もアクセシビリティツリーの確認までで、実機の読み上げは行っていません。