本文へスキップ

ドロワー

ボタンで右側からパネルがスライドイン。背景クリックか×で閉じる。モバイルではボトムシートになる想定のスタイル。

プレビュー

クリック

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

仕様書

向く場面
  • ハンバーガーメニューの受け皿
  • 項目数が多いナビ・フィルタ・カート
  • 元の画面を横目にできる軽いパネル
避ける場面
  • はい/いいえの決定を求める内容
  • 項目が 2〜3 個のメニュー
  • 常時サイドバーを置ける広い画面
構成要素
  • 開くボタン
  • バックドロップ
  • ドロワー
  • タイトルと閉じるボタン
  • ナビのリンク
持っている状態
  • 通常
  • ホバー
  • フォーカス
  • 押下中
キーボード

Enter/Space で開くと閉じるボタンへ、Tab で 4 つのリンクを循環、Esc・閉じる・背景クリックで閉じて元のボタンへ戻る。閉じている間はリンクに Tab が入らない

支援技術

role=dialog+aria-modal+aria-label「サイドメニュー」、中身は nav。閉じている間は inert+visibility:hidden、フォーカスの移動と復帰。閉じるは aria-label

動き

右から 280ms スライド(影は開いている間だけ)、背景 250ms。reduced-motion で停止

デザインの要点

  • パネルは translateX(100%) で画面外に置き、280ms で滑り込ませる。フェードだと「どこから来たか」が伝わらない
  • 影は開いている間だけ付ける。閉じていても影のぼかしが枠の右端に「暗い帯」として残る
  • 閉じている間は inert+visibility:hidden で Tab 順から外す。画面外のリンクにフォーカスが飛ぶと枠が横スクロールする
  • 開くときの focus は preventScroll:true で行う。スライド中の要素へフォーカスすると枠がずれる
実装メモ

プレビューは枠内に収めるため、オーバーレイを position:absolute(ステージ基準)で実装しています。実際のページで画面全体に重ねるときは position:fixed に変え、body 直下など高い階層へ置いてください。

使いどころ

グローバルナビ・フィルタ設定・カート内容の確認など「一覧やメニューの延長」を画面外から呼び出す用途に向く。モーダルと違って判断を強制する場面ではなく、開いたまま元の画面を横目にできる軽さが持ち味なので、Yes/Noのような決定を求める内容には向かない(それはモーダルの役目)。ハンバーガーメニューの受け皿として使うのが典型で、項目数が多いナビをヘッダーに並べきれないときに有効。逆に、項目が2〜3個しかないメニューにはポップオーバーの方が軽くて動線も短い。また、デスクトップの広い画面で常時表示できるサイドバーがあるなら、わざわざオーバーレイのドロワーにする必要はない。画面幅に応じてサイドバー⇄ドロワーを切り替える設計が定番になる。

仕組み

モーダルと同じ backdrop+パネルの重ね方だが、パネルは transform: translateX(100%) で右側の画面外に置き、開くと translateX(0) へ動かすスライド方式。opacity は使わずtransformだけで隠しているため、閉じている間も要素はレイアウト上に存在し続ける。box-shadow は閉じているときだけ none にしている点が特徴的で、これは影のぼかしが overflow:hidden の右端からはみ出し、パネル本体が完全に隠れているのに縁だけ「暗い帯」として見えてしまう現象を避けるための処理。開くときのフォーカス移動は items[0].focus({ preventScroll: true }) としており、スライドイン中にフォーカスの副作用でステージ自体が横スクロールしてボタン位置がずれることを防いでいる。閉じるときはモーダルと同じく trigger 要素へフォーカスを戻す。

実装の注意

transform だけで隠すと、ドロワーが閉じている状態でも中のリンクはキーボードのTabで到達できてしまう。画面には見えないのに、Tabを連打すると突然「ホーム」「プロフィール」などのリンクにフォーカスが当たり、しかも画面外にあるぶんブラウザが要素を可視域に入れようとしてステージが意図せず横スクロールすることがある。この見本では閉じたときに inert を当て、CSS でも visibility:hidden(スライドが終わってから切り替わるよう transition に visibility 0s linear 0.28s を含める)を当てて二重に防いでいる。非対応環境では tabindex="-1" の一括付与で代替する。もう一つは box-shadow の付け外し忘れで、これを両方の状態に常時適用すると、閉じている間もパネルの縁の影だけがうっすら見え続けるバグになる。影は is-open のときだけ有効化する設計を崩さないこと。

アクセシビリティ

role="dialog" と aria-modal="true" を付け、タイトル文言がテキストノードで aria-labelledby に紐づけにくいぶん aria-label="サイドメニュー" で直接ラベルしている。フォーカストラップ・Escapeでの終了・閉じた後のフォーカス復帰はモーダルと同じ実装で、ナビゲーション用途なので中身は nav 要素でマークアップし、リンクの並び順がそのままTab順になるようにしてある。閉じている間は aria-hidden="true" に加えて inert と visibility:hidden を当てる。aria-hidden を付けた要素に focusable な子要素が残るのは ARIA仕様違反でもあるため、閉じた瞬間に inert 化することが見た目だけでなく支援技術対応としても必須になる。prefers-reduced-motion ではスライドのトランジションを止めて開閉が瞬時に切り替わるようにし、フォーカスは :focus-visible で各リンクとボタンに 2px の輪郭を描く。

調整して使う

主に触るのは3つ。開く方向(右からが定番だが、左メニューのアプリでは左から、RTL言語では逆にする)、幅(デモは68%・最大160pxだが、実運用では280〜320px程度かビューポート幅の75〜85%が読みやすい)、そして背景の扱い。今回はオーバーレイ方式(本体の上に重ねる)だが、メインコンテンツを横に押し出す「プッシュ方式」に変えることもできる。ただしプッシュ方式は本体側に margin や transform を掛けてレイアウトのリフローを伴うぶん実装コストが増えるため、パフォーマンスを優先するならこのままのオーバーレイ方式を維持し、開閉の速さで軽さを出す方が扱いやすい。

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

<div class="stage" id="stage">
  <button class="open-btn" id="openDrawer">メニューを開く</button>

  <div class="backdrop" id="backdrop" aria-hidden="true"></div>
  <div class="drawer" id="drawer" role="dialog" aria-modal="true" aria-label="サイドメニュー" aria-hidden="true" inert>
    <div class="drawer-header">
      <p class="drawer-title">メニュー</p>
      <button class="close-btn" id="closeDrawer" aria-label="閉じる">
        <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
          <line x1="3" y1="3" x2="13" y2="13" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
          <line x1="13" y1="3" x2="3" y2="13" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
        </svg>
      </button>
    </div>
    <nav class="drawer-nav">
      <a class="drawer-item" href="#">ホーム</a>
      <a class="drawer-item" href="#">プロフィール</a>
      <a class="drawer-item" href="#">設定</a>
      <a class="drawer-item" href="#">ヘルプ</a>
    </nav>
  </div>
</div>

タグ

同じカテゴリ「通知・オーバーレイ」のほかのパーツ

関連パーツ