本文へスキップ

ページネーション

前へ/次へと番号ボタンで構成する定番ページャ。ページ数が多いときは「…」で省略し、現在のページを藍色で示す。クリックでページが切り替わる。

プレビュー

クリック

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

仕様書

向く場面
  • 検索結果・アーカイブなど特定ページへ飛びたい一覧
  • 総件数と現在位置を把握させたい
  • SEOでクロールさせる一覧
避ける場面
  • 連続して流し読みするフィード
  • 2〜3ページで収まる少件数(もっと見るで足りる)
  • 番号を URL に結び付けない実装
構成要素
  • ページ番号ボタン
  • 前へ/次へ
  • 省略記号
  • 現在ページ
  • ステータス文
持っている状態
  • 通常
  • ホバー
  • 押下中
  • 無効
キーボード

Tab で前へ・各番号・次へへ移動・Enter/Space でページ移動(button 標準)

支援技術

nav aria-label=ページ送り、現在ページに aria-current=page、両端は disabled、…は aria-hidden、変更は role=status で「N / 12 ページ」を告知。再描画後は現在ページへフォーカスを戻す

動き

ボタンの色・枠 0.15s。reduced-motion で無効

デザインの要点

  • 出す番号は「先頭・末尾・現在±1」の集合で作り、飛んだ区間だけ … にする。全ページを並べると 12 ページで 320px に収まらない
  • 再描画で押したボタンが消えるので、新しい現在ページへ focus() を当て直す。放っておくとフォーカスが body へ落ち、次の Tab が先頭からになる
  • 現在ページは押せないが Tab で止まる button のままにする。span にすると読み上げで「今何ページ目」が飛ばされる
  • 本番では番号を ?page=N の実リンクにする。JS の状態だけで切り替えると、戻る・ブックマーク・クロールが全部効かない
実装メモ

表示するページ番号は「先頭・末尾・現在地の前後」を集合で作り、飛んだ区間だけ「…」に畳んでいます。現在ページは aria-current="page"、両端では前へ/次へを disabled にします。ページ変更は aria-live の status で読み上げます。

使いどころ

検索結果や一覧ページのように、総件数が多く『特定のページへ直接飛びたい』ニーズがある画面に向く。無限スクロールや『もっと見る』ボタンと違い、番号を見て狙ったページへ一気に移動できるのが利点で、SEOでクロールされる一覧アーカイブとも相性がよい。逆に、SNSのタイムラインのように連続して流し読みする体験や、件数が数十件程度でページが2〜3枚に収まる場合は無限スクロールや単純な『もっと見る』の方が操作数が少なく済む。総ページ数が変動する動的な一覧(フィルタで絞り込むたびに件数が変わる等)では、TOTAL を都度再計算してから pageModel() を作り直す必要がある点もこの実装のまま流用する際の前提になる。

仕組み

ページ番号ボタンをすべて並べるのではなく、pageModel() が『先頭・末尾・現在ページの前後1つずつ』を Set で集めてソートし、番号が1つ以上飛んだ区間だけを null(=…)として差し込むことで省略表示を作っている。render() はこのモデルを毎回 numsEl.textContent = "" から作り直すシンプルな設計で、状態(current)さえ更新すればDOM全体が正しい見た目に再構築される。現在ページのボタンだけクリックイベントを付けず is-current クラスと aria-current="page" を与え、それ以外のボタンには go(n) を仕込む。前へ/次へは current が1またはTOTALのときに disabled を付けるだけで端の処理をしており、ページ送りのたびに render() が prevBtn/nextBtn の disabled も含めて丸ごと再計算する。

実装の注意

このデモはページ番号ボタンにクリックイベントを仕込んでいるだけで、実際のURL(href)には紐づいていない。本番で使う場合は各ページ番号を <a href="?page=3"> のような実リンクにしないと、ブラウザの戻る/進む・ブックマーク・検索エンジンのクロールが機能しなくなる(JSの状態だけで見た目のページが変わる作りは避ける)。もう1つの落とし穴は、render() が numsEl.textContent = "" で全ボタンを毎回作り直していること。ページ番号ボタンをクリックした直後、そのボタン自身がDOMから消えて新しいボタン群に置き換わるため、何もしないとキーボード操作でフォーカスしていた要素が消滅し、フォーカスがbodyへ飛んでしまう。この見本は消す前に numsEl.contains(document.activeElement) でフォーカスが番号群の中にあったかを覚えておき、作り直した後に新しい現在ページのボタンへ focus() を当て直している。前へ/次へは作り直さないので、そちらのフォーカスはそのまま残る。

アクセシビリティ

ページ送り全体を <nav aria-label="ページ送り"> で囲み、現在のページボタンには aria-current="page" を付けている。これによりスクリーンリーダーは『いま何ページ目にいるか』をランドマークとページ番号の両方から把握できる。省略記号(…)はクリックできないただの飾りなので button ではなく span にし aria-hidden="true" を付けて読み上げ・フォーカス対象から外しているのも適切な判断。ページが切り替わるたびに role="status" aria-live="polite" の .pg-status を『N / 12 ページ』へ書き換えており、視覚的なページ番号を目で追えない利用者にもページ移動の結果が伝わる。再描画後に現在ページのボタンへフォーカスを戻す処理と、各ボタンの focus-visible の輪郭が揃っているので、キーボード操作だけで一覧を隅々まで辿れる。

調整して使う

前後に表示するページ数(現在は current±1)を2や3に広げると、ジャンプ先の見通しは良くなるが番号の総数が増えて横幅を圧迫する。スマホ幅では番号ボタンを出さず、常時表示している .pg-status(『1 / 12 ページ』)と前へ/次への矢印だけに絞る構成にすると省スペースになる。先頭・末尾へワンタップで飛べる『最初へ』『最後へ』ボタンを両端に足す構成も、ページ数が数十を超えるアーカイブでは有効。1ページあたりの件数を切り替えるセレクタを添える場合は、件数変更時に current を1へリセットしてから TOTAL を再計算する必要がある。番号ボタンではなく『前へ/次へ』のみのシンプルな構成にする場合も、.pg-status のような現在地の明記は必ず残す。

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

<div class="pg-wrap">
  <p class="pg-status" id="pg-status" role="status" aria-live="polite">1 / 12 ページ</p>
  <nav class="pg" aria-label="ページ送り">
    <button class="pg-btn pg-arrow" id="pg-prev" aria-label="前のページ" disabled>
      <svg width="14" height="14" viewBox="0 0 14 14" fill="none"><polyline points="9,3 5,7 9,11" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>
    </button>
    <div class="pg-nums" id="pg-nums"></div>
    <button class="pg-btn pg-arrow" id="pg-next" aria-label="次のページ">
      <svg width="14" height="14" viewBox="0 0 14 14" fill="none"><polyline points="5,3 9,7 5,11" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>
    </button>
  </nav>
</div>

タグ

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

関連パーツ