【CSS新仕様】CSSだけで横スクロールと上部見出し固定を両立!「単一軸スクロールコンテナー(overflow: auto clip)」の仕組みと実践テーブル実装

Web技術

Webサイトや社内管理画面、SaaSのダッシュボードを制作する際、必ずと言っていいほど直面する難題のひとつが「横に長く、縦にも長い巨大なデータテーブル(表)のUI設計」です。

スマートフォンやノートPCなど画面幅が狭いデバイスでは、表全体を横スクロールさせて全項目を確認できるようにするのが一般的です。その際、誰が見ても直感的にデータを追えるよう、「行の左端にある見出し(行見出し)」は表の横スクロールに合わせて左端に固定しておきたいところです。

さらに、縦に長い表であれば、ページ全体の縦スクロールに合わせて「上部の列見出し(カラムヘッダー)」を画面最上部にピタッと追従・固定(Sticky)させたいと考えるのが自然なUXの要望でしょう。

しかし従来のCSSでは、「表の内部だけを横スクロールさせつつ、上見出しはページ全体の縦スクロールに追従させて画面上部に固定する」という挙動をCSSだけで実現することは不可能でした。どうしてもJavaScriptでスクロール量を監視して座標を再計算するか、表の親要素に固定の高さを設けて「枠内での縦横スクロール」に閉じ込めるしかありませんでした。

この長年のCSSの限界を鮮やかに打ち破る画期的な新仕様が、Chrome・Edge 156よりサポートされた「単一軸スクロールコンテナー(Single-axis scroll containers)」です!

本記事では、なぜこれまで縦横の見出し固定を両立できなかったのかという技術的背景から、新仕様 overflow: auto clip; が解決した仕組み、工程表(ガントチャート)や比較テーブルでの実践コード、最新の scroll-state() コンテナクエリーとの組み合わせテクニックまで、フロントエンドエンジニア向けに余すところなく徹底解説します!


1. 従来のCSSで「縦横の見出し固定」が両立できなかった根本原因

なぜ、これまでのCSSでは横スクロールとページ追従の縦見出し固定を両立できなかったのでしょうか?

その理由は、CSSの position: sticky; が持つ「スクロール基準コンテナーの解決ルール」にありました。

① 意図しない「縦方向スクロールコンテナー」の生成

表を横スクロールさせるため、ラッパー要素(親要素)に overflow-x: auto; を指定したとします。このとき、従来のCSS仕様では、たとえ縦方向にはスクロールバーが出ていなくても、あるいは overflow-y: visible; と指定していたとしても、ブラウザの内部仕様により「縦方向も自動的にスクロールコンテナー(または overflow: hidden 相当)として扱われてしまう」という制限がありました。

② position: sticky の基準が親コンテナーに奪われる

position: sticky; は、要素自身から最も近い「祖先のスクロールコンテナー」を基準にして固定位置(top や left)を計算します。

表の親要素が(横スクロールのために)スクロールコンテナーになってしまうと、上見出し(thead th)に position: sticky; top: 0; を指定しても、その固定基準は「ページ全体(Viewport)」ではなく「表の親要素」になってしまいます。

その結果、親要素に高さを固定して表の“内側”を縦スクロールさせない限り、ページ全体を下へスクロールしたとき、上見出しは表の親要素ごと画面の外へ流れていってしまっていたのです。


2. 単一軸スクロールコンテナー(overflow: auto clip)がもたらした革命

この問題を一撃で解決したのが、overflow プロパティにおけるスクロール値と clip の組み合わせです。

新仕様に対応したブラウザでは、次のように指定します。

.table-scroll {
  /* 横方向はスクロール、縦方向は溢れた部分を単に切り抜く(スクロールコンテナーにしない) */
  overflow: auto clip;
  
  /* 以下と同等の意味 */
  /* overflow-x: auto; */
  /* overflow-y: clip; */
}

hidden と clip の決定的な違い

ここで極めて重要になるのが、hidden ではなく clip を使う点です。

指定方法 縦方向(Y軸)の扱い position: sticky (top: 0) の固定基準
overflow: auto hidden;(従来) 縦方向もスクロールコンテナーとして扱われる 表の親要素が基準になるため、ページ縦スクロール時に画面上部に残らず流れてしまう。
overflow: auto clip;(新仕様) 縦方向はスクロールコンテナーにならない 親を素通りし、さらに祖先にあるページ全体(Viewport)が基準になるため、画面上部にピタッと固定される!

overflow-y: clip; は、単に領域外を描画しない(切り抜く)だけであり、スクロール可能なコンテナーを形成しません。そのため、横方向(X軸)のみがスクロールコンテナーとなり、縦方向(Y軸)はページ全体のルートスクロールコンテナーへ透過されます。

これにより、「表の左見出しはテーブル内の横スクロールで左端に固定」されつつ、「上見出しはページ全体の縦スクロールに合わせて画面最上部に固定」されるという、理想的な二軸分離がCSSだけで完結するようになりました。


3. 実践デモ①:工程表(ガントチャート)の実装

月ごとのタスクが横に長く、工程数が縦にも膨大な「プロジェクト工程表(ガントチャート)」を題材に、具体的なHTML/CSS設計を見ていきましょう。

HTML構造

1列目(フェーズ)と2列目(タスク名)を行見出しとし、1行目を列見出し(月)とします。

<div class="table-scroll">
  <table>
    <thead>
      <tr>
        <th scope="col">Phase</th>
        <th scope="col">Task</th>
        <th scope="col">April</th>
        <th scope="col">May</th>
        <th scope="col">June</th>
        <!-- 続く -->
      </tr>
    </thead>
    <tbody>
      <tr>
        <th scope="rowgroup">Planning</th>
        <th scope="row">Concept Development</th>
        <td>...</td>
        <td>...</td>
      </tr>
      <!-- 続く -->
    </tbody>
  </table>
</div>

CSSスタイル指定

.table-scroll {
  /* 【最重要】横方向のみスクロールコンテナー化 */
  overflow: auto clip;

  thead {
    th {
      /* 列見出し(上部)をページスクロール上端に固定 */
      position: sticky;
      top: 0;
      z-index: 2; /* 行見出しより上に表示 */
      background-color: #f8fafc;
    }
  }

  tbody {
    /* 1列目の固定(一番左端) */
    th[scope="rowgroup"] {
      position: sticky;
      left: 0;
      z-index: 1;
      background-color: #ffffff;
    }

    /* 2列目の固定(1列目の幅分だけ右にずらして固定) */
    th[scope="row"] {
      position: sticky;
      left: 160px; /* 1セル分の幅 */
      z-index: 1;
      background-color: #ffffff;
    }
  }
}

実装のポイント:
左固定列が複数ある場合は、重なりを防ぐために2列目の left プロパティに「1列目のセル幅」を指定します。また、最左上のセル(交差部分)が重なった際に綺麗に見えるよう、thead th 側の z-index を高く設定しておくのがコツです。


4. 実践デモ②:最新機能 scroll-state() と組み合わせた高度なUI演出

単一軸スクロールコンテナーは、最新のコンテナクエリー機能である scroll-state() と組み合わせることで、さらにリッチなマイクロインタラクションを実現できます。

scroll-state() は、「要素が現在スクロール端にあるか」「position: sticky で画面端に貼り付いている(Stuck)状態か」をCSSだけでリアルタイムに検知できるプロパティです。

固定された瞬間にスタイルを切り替える比較テーブル

.table-scroll {
  overflow: auto clip;
  container-type: scroll-state;
  container-name: table-scroll;

  table {
    thead {
      position: sticky;
      top: 0;
      container-type: scroll-state;
      container-name: column-heading;

      th {
        /* 上部にStuck(貼り付き)した瞬間、文字サイズや国旗の表示をコンパクトに縮小! */
        @container column-heading scroll-state(stuck: top) {
          .country-heading {
            padding-block: 0.5rem;
            font-size: 0.875rem;
            box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
          }
        }
      }
    }

    tbody th {
      position: sticky;
      left: 0;
    }
  }

  /* 右方向にまだスクロールできる余地がある場合のみ、スクロールを促すシャドウを表示 */
  .scroll-shadow-right {
    opacity: 0;
    transition: opacity 0.2s ease;

    @container table-scroll scroll-state(scrollable: right) {
      opacity: 1;
    }
  }
}

JavaScriptの ScrollMagic や IntersectionObserver を一切書くことなく、「スクロールして画面上に吸着したらヘッダーを小さくしてシャドウを付ける」「横スクロールの余白がある時だけ右端にグラデーションを出す」といった極めて洗練されたUI演出が、CSSネイティブで超高速に動作します。


5. 対応ブラウザとフォールバックの考え方

気になるブラウザのサポート状況と、実務での取り入れ方を整理しておきましょう。

ブラウザ 対応バージョン 対応状況
Google Chrome Chrome 156(2026年10月リリース)以降 完全サポート
Microsoft Edge Edge 156(2026年10月リリース)以降 完全サポート
Safari / Firefox 標準化策定中 今後のアップデートで実装予定

非対応ブラウザにおける挙動と注意点

未対応の古いブラウザに overflow: auto clip; を指定した場合、CSSの文法エラーとして無視されるか、あるいは片軸のみの clip がサポートされていないため overflow: auto hidden; と同等にフォールバック(解釈)されます。

つまり、「横スクロール自体は正常に動き、左見出しも固定されるが、上見出しだけが従来通りページ上端には追従せず流れる」という挙動になります。表示が破綻して読めなくなるような致命的なレイアウト崩れにはならないため、プログレッシブ・エンハンスメントとして今すぐ導入しやすい仕様と言えます。


6. まとめ:CSSの進化がWebのテーブル体験を劇的に変える

本記事では、CSSの画期的な新機能「単一軸スクロールコンテナー」について解説しました。

  1. overflow: auto clip; で、横方向のみをスクロールコンテナー化できる。
  2. 縦方向がスクロールコンテナーにならないため、position: sticky; top: 0; がページ全体に追従する。
  3. 表内部の横スクロール(左見出し固定)と、ページ全体の縦スクロール(上見出し固定)が完全両立。
  4. scroll-state() と組み合わせることで、吸着時のヘッダー縮小やスクロールシャドウもCSSだけで実装可能。
  5. Chrome・Edge 156以降で利用可能であり、非対応環境でも自然なフォールバックが働く。

業務システムや大規模なデータ表示において、テーブルUIの視認性と操作性はユーザーの作業効率に直結します。これまでJavaScriptによる力技に頼らざるを得なかった領域が、CSSのたった2単語(auto clip)で美しく解決できるようになったのは、Web標準の素晴らしい進化です。
ぜひ皆さんのプロジェクトでも、この進化したスクロールコンテナーを取り入れて、快適なテーブル体験をユーザーに届けてみてください!

(Visited 1 times, 1 visits today)
タイトルとURLをコピーしました