本文へスキップ

サイドバーナビ

セクション見出し付きの縦型ナビ。折りたたみグループはクリックで開閉し、現在の項目は藍色でハイライトされる。管理画面やダッシュボードの定番。

プレビュー

クリック

操作クリック/タップで動きます。

実装メモ

折りたたみは高さアニメを grid-template-rows: 0fr→1fr で行い、開閉状態は aria-expanded に持たせています(サブメニューを aria-controls で関連付け)。現在地の項目には aria-current="page" を付けるのが定石です。

使いどころ

管理画面・ダッシュボードのように、機能ごとにグルーピングされた10項目前後のメニューを常時左側に出しておきたい場面に向く。セクション見出し(このデモでは『メニュー』『管理』)で関連項目をまとめ、使用頻度の低いサブ項目(『コンテンツ』配下の記事・カテゴリ・タグ)は折りたたんでおくことで、縦の情報量を抑えつつ必要なときだけ展開できる。逆に、項目数が5個以下で階層化するほどでもないシンプルな管理画面や、モバイル幅でそもそもサイドバーを常時出せない画面では、ハンバーガーメニューやタブバーの方が適する。折りたたみグループが1つしかないこのデモの構成は、実運用ではグループを複数持たせ、それぞれ独立して開閉できるようにする想定になる。

仕組み

折りたたみの高さアニメーションはaccordionと同じgrid-template-rows: 0fr→1frの手法で、sn-toggleのaria-expanded属性の値をCSSセレクタ([aria-expanded="true"] + .sn-sub)で拾って行の高さを切り替えている。開閉の状態そのものはaria-expandedだけが単一のソースで、JSのtoggle.addEventListenerは真偽を反転させるだけの1行で完結する。現在地のハイライトはsn-itemだけがクラスと属性の対象で、リンクをクリックするとdocument.querySelectorAllで全sn-itemからis-active/aria-currentを一旦外し、クリックされた要素がsn-itemなら改めて両方を付け直すという単純な総入れ替え方式。デモ内のリンクはhref="#"のため、実際の遷移ではなくpreventDefaultしてハイライトだけを移す動作になっており、実サイトでは各リンクを実URLに差し替えたうえでSPAならルーティング後に現在地を再計算する処理に置き換える必要がある。

実装の注意

最大の抜けは、現在地ハイライトの対象がsn-itemだけに限定されており、sn-subitem(折りたたみ内のリンク)をクリックしても、そのリンク自身にはis-activeもaria-currentも一切付かない点。JSのif (link.classList.contains("sn-item"))という分岐がsn-subitemを弾いてしまうため、サブメニューの奥にあるページを開いても『今どこにいるか』がサイドバー上のどこにも表れなくなる。CSS側にも.sn-subitem.is-activeのスタイル定義自体が存在しないため、この分岐を直しても見た目のルールを追加しないとハイライトは表示されない。実サイトで折りたたみメニュー配下のページを現在地表示に含めたいなら、対象の判定をsn-item, sn-subitemの両方に広げ、CSSにも対応するハイライトスタイルを足す必要がある。もう一つ、ページ読み込み時に現在地がsn-subitem配下にある場合、対応するsn-groupを自動的に開いた状態で初期化する処理もこのデモには無く、実装時は現在URLと照合してグループの初期展開状態を決めるロジックを追加することになる。

アクセシビリティ

sn-item(現在地)にaria-current="page"を付ける実装と、折りたたみのsn-toggleにaria-expandedとaria-controls="sn-sub"を付けて対応するsn-subへ関連付ける実装は、どちらもWAI-ARIAのDisclosureパターンに沿った適切な作りになっている。sn-toggleはbutton要素なのでTabキーでの到達とEnter/Spaceでの開閉という基本操作も標準で成立する。一方で、前述のとおりsn-subitemには現在地を示す手段が全く無いため、折りたたみの奥のページを開いているスクリーンリーダー利用者は、サイドバーのどこにも自分の現在地を確認できない。実装時はsn-subitemにもaria-current="page"を付ける処理を足すべき。またページ読み込み時にサブ項目が現在地であるにもかかわらずグループが閉じたままだと、視覚的にもハイライトが見えない状態になるため、現在地を含むグループはaria-expanded="true"で初期表示する配慮も併せて必要になる。

調整して使う

サブメニューを1階層(現在の構成)からさらにもう1階層深くネストする拡張もできるが、その場合はgrid-template-rowsの入れ子で高さアニメーションが競合しないよう、各階層のtransition対象を独立させる必要がある。見出し(.sn-heading)を省いてフラットな1本のリストにする、逆に見出しごとに色分けや区切り線を強めるといった調整もよくある。折りたたみグループを常に1つだけ開く排他制御(アコーディオンと同じ挙動)に変えることもでき、その場合はトグルのクリック時に他の全グループのaria-expandedをfalseへ戻す処理を1行足すだけで実現できる。アイコンを外してテキストだけの密度の高いリストにする、逆にアイコンを大きくして視認性を優先するなど、情報量と省スペース性のバランスは用途に応じて調整する。

コード(コピーして使えます)

<nav class="sn" aria-label="サイドバー">
  <p class="sn-heading">メニュー</p>
  <a class="sn-item is-active" href="#" aria-current="page">
    <svg class="sn-ic" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>
    ダッシュボード
  </a>
  <a class="sn-item" href="#">
    <svg class="sn-ic" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 3v18h18"/><rect x="7" y="11" width="3" height="6"/><rect x="13" y="7" width="3" height="10"/></svg>
    分析
  </a>

  <p class="sn-heading">管理</p>
  <div class="sn-group">
    <button class="sn-toggle" aria-expanded="false" aria-controls="sn-sub">
      <span class="sn-toggle-in">
        <svg class="sn-ic" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 7 4 7"/><path d="M20 12 4 12"/><path d="M20 17 4 17"/></svg>
        コンテンツ
      </span>
      <svg class="sn-caret" width="14" height="14" viewBox="0 0 14 14" fill="none"><polyline points="4,3 8,7 4,11" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>
    </button>
    <div class="sn-sub" id="sn-sub">
      <div class="sn-sub-inner">
        <a class="sn-subitem" href="#">記事</a>
        <a class="sn-subitem" href="#">カテゴリ</a>
        <a class="sn-subitem" href="#">タグ</a>
      </div>
    </div>
  </div>
  <a class="sn-item" href="#">
    <svg class="sn-ic" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19 12a7 7 0 0 0-.1-1.2l2-1.5-2-3.4-2.3 1a7 7 0 0 0-2-1.2L14.2 3H9.8l-.4 2.5a7 7 0 0 0-2 1.2l-2.3-1-2 3.4 2 1.5A7 7 0 0 0 5 12a7 7 0 0 0 .1 1.2l-2 1.5 2 3.4 2.3-1a7 7 0 0 0 2 1.2l.4 2.5h4.4l.4-2.5a7 7 0 0 0 2-1.2l2.3 1 2-3.4-2-1.5A7 7 0 0 0 19 12z"/></svg>
    設定
  </a>
</nav>

タグ

同じカテゴリ「ナビ・リスト操作」のほかのパーツ

関連パーツ