CSS Grid でアイテムを並べたとき、grid-column や grid-row で位置を指定していないものは、ブラウザーが自動で場所を決めて配置します。この「自動配置」のルールを切り替えるのが grid-auto-flow です。行方向に埋めるか列方向に埋めるか、そして空きマスができたときに後ろのアイテムで詰めるかどうかを、1 行の記述でコントロールできます。この記事では row / column / dense の違いと、grid-auto-rows / grid-auto-columns との関係を、動かせるデモ付きで解説します。
目次
grid-auto-flow が担当しているのは「自動配置」だけ
Grid コンテナーの中にあるアイテムは、大きく 2 種類に分かれます。ひとつは grid-column: 2 / 4 のように置き場所を自分で書いたアイテム、もうひとつは何も書いていないアイテムです。前者は書いたとおりの位置に固定されますが、後者はブラウザーが空いている場所を探して順番に埋めていきます。この後者の処理を、仕様では自動配置アルゴリズムと呼びます。
grid-auto-flow は、この自動配置のときにどちらの向きに進んでいくかと、途中にできた空きマスを埋めにいくかの 2 点を決めるプロパティです。逆に言えば、位置を明示しているアイテムの配置には影響しません。初期値は row なので、何も書かなければ「左上から右へ、行がいっぱいになったら次の行へ」という、多くの人が思い浮かべる並び方になります。
適用対象は Grid コンテナー、つまり display: grid または display: inline-grid を指定した要素です。アイテム側に書いても意味がない点に注意してください。
基本の使い方|row と column で進む向きが変わる
もっとも分かりやすいのが row と column の違いです。3 列 2 行の枠を用意して、6 個のアイテムを並べてみます。
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(2, 64px);
gap: 8px;
/* 既定値。1 行目を左から右へ埋めて、次の行へ移る */
grid-auto-flow: row;
}
この状態では 1・2・3 が 1 行目、4・5・6 が 2 行目に並びます。ここを grid-auto-flow: column に変えると、進む向きが 90 度変わり、1・2 が 1 列目、3・4 が 2 列目……と縦に埋めてから右へという順番になります。HTML の書き順はまったく同じまま、見た目の並びだけが変わるのがポイントです。
下のデモでボタンを切り替えて、番号の並び方を見比べてみてください。
<div class="demo">
<div class="demo__controls">
<button type="button" class="is-active" data-flow="row">grid-auto-flow: row</button>
<button type="button" data-flow="column">grid-auto-flow: column</button>
</div>
<div class="grid" id="grid">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
<div class="item">5</div>
<div class="item">6</div>
</div>
<p class="demo__note">番号は HTML に書いた順番です。並ぶ向きだけが変わります。</p>
</div>
.grid {
display: grid;
/* 3 列 2 行の枠をあらかじめ作っておく */
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(2, 64px);
gap: 8px;
/* 既定値。1 行目を左から右へ埋めて、いっぱいになったら次の行へ */
grid-auto-flow: row;
}
/* ボタンで付け外しするクラス */
.grid.is-column {
/* 1 列目を上から下へ埋めて、いっぱいになったら次の列へ */
grid-auto-flow: column;
}
/* ここから下は見た目を整えるだけのスタイル */
.demo {
font-family: sans-serif;
padding: 16px;
}
.demo__controls {
display: flex;
gap: 8px;
margin-bottom: 16px;
}
.demo__controls button {
padding: 8px 14px;
font-size: 13px;
font-family: monospace;
border: 1px solid #0059b3;
border-radius: 4px;
background: #fff;
color: #0059b3;
cursor: pointer;
}
.demo__controls button.is-active {
background: #0059b3;
color: #fff;
}
.item {
display: flex;
align-items: center;
justify-content: center;
background: #e5f1ff;
border: 1px solid #99c7f0;
border-radius: 4px;
font-size: 22px;
font-weight: bold;
color: #0059b3;
}
.demo__note {
margin-top: 16px;
font-size: 13px;
color: #555;
}
const grid = document.getElementById('grid');
const buttons = document.querySelectorAll('.demo__controls button');
buttons.forEach((button) => {
button.addEventListener('click', () => {
// is-column クラスの付け外しで grid-auto-flow を切り替える
grid.classList.toggle('is-column', button.dataset.flow === 'column');
buttons.forEach((b) => b.classList.remove('is-active'));
button.classList.add('is-active');
});
});
grid-auto-flow に指定できる値
指定できる値は、向きを表す row / column と、詰め方を表す dense の組み合わせです。向きと dense は同時に書けるので、実際に使う値は次の 5 パターンになります。
| 値 | 並び方 |
|---|---|
row | 初期値。行を順番に埋めていき、足りなくなったら行を追加する |
column | 列を順番に埋めていき、足りなくなったら列を追加する |
dense | row dense と同じ扱い。行方向に埋めつつ、空きマスを後ろのアイテムで詰める |
row dense | 行方向に埋めながら、前のほうにできた空きマスを後ろの小さいアイテムで埋める |
column dense | 列方向に埋めながら、前のほうにできた空きマスを後ろの小さいアイテムで埋める |
dense を書かなかったときは「疎(sparse)」なアルゴリズムが使われます。これは配置の起点を後戻りさせない方式で、一度通り過ぎたマスは空いていても埋めにいきません。あとで詳しく見ていきます。
暗黙のトラックと grid-auto-rows / grid-auto-columns
grid-auto-flow を理解するうえで欠かせないのが、暗黙のトラックという考え方です。grid-template-columns や grid-template-rows で自分が書いた行・列を「明示的なトラック」と呼ぶのに対し、枠が足りなくなったときにブラウザーが自動で作り足す行・列を「暗黙のトラック」と呼びます。
ここで重要なのが、どちらの方向に暗黙のトラックが作られるかを決めているのが grid-auto-flow だという点です。row のときは行が作り足されるので、そのサイズを決める grid-auto-rows が効きます。column のときは列が作り足されるので、今度は grid-auto-columns のほうが効きます。「grid-auto-columns を書いたのに何も変わらない」というときは、たいてい grid-auto-flow: column を書き忘れています。
.cards {
display: grid;
/* 列方向に流すので、アイテムの数だけ暗黙の「列」が作られる */
grid-auto-flow: column;
/* その暗黙の列の幅を決めるのが grid-auto-columns */
grid-auto-columns: 160px;
gap: 12px;
}
この書き方は、カードを横一列に並べるレイアウトでよく使います。grid-template-columns でカードの枚数ぶんの列を毎回書き直す必要がなく、アイテムを増やしても列が勝手に増えていくのが利点です。
下のデモでは grid-auto-columns の値だけを切り替えています。auto のままだと中身の文字数に合わせて幅がバラバラになりますが、160px にすればきっちり揃い、minmax(120px, 1fr) にすれば最低幅を確保しつつ余った幅を均等に分け合うようになります。
<div class="demo">
<p class="demo__label">grid-auto-columns の値を切り替える</p>
<div class="demo__controls">
<button type="button" class="is-active" data-value="auto">auto</button>
<button type="button" data-value="160px">160px</button>
<button type="button" data-value="minmax(120px, 1fr)">minmax(120px, 1fr)</button>
</div>
<div class="cards" id="cards">
<div class="card">お知らせ</div>
<div class="card">キャンペーン情報</div>
<div class="card">新着</div>
<div class="card">イベントのご案内</div>
<div class="card">FAQ</div>
</div>
</div>
.cards {
display: grid;
/* 列方向に流すので、アイテムの数だけ「暗黙の列」が作られる */
grid-auto-flow: column;
/* その暗黙の列の幅を決めるのが grid-auto-columns */
grid-auto-columns: auto;
gap: 12px;
}
/* ここから下は見た目を整えるだけのスタイル */
.demo {
font-family: sans-serif;
padding: 16px;
}
.demo__label {
margin: 0 0 8px;
font-size: 13px;
color: #555;
}
.demo__controls {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 16px;
}
.demo__controls button {
padding: 8px 14px;
font-size: 13px;
font-family: monospace;
border: 1px solid #0059b3;
border-radius: 4px;
background: #fff;
color: #0059b3;
cursor: pointer;
}
.demo__controls button.is-active {
background: #0059b3;
color: #fff;
}
.card {
display: flex;
align-items: center;
justify-content: center;
height: 80px;
padding: 8px;
background: #e5f1ff;
border: 1px solid #99c7f0;
border-radius: 6px;
font-size: 13px;
color: #0059b3;
text-align: center;
}
const cards = document.getElementById('cards');
const buttons = document.querySelectorAll('.demo__controls button');
buttons.forEach((button) => {
button.addEventListener('click', () => {
// 暗黙の列の幅を書き換える
cards.style.gridAutoColumns = button.dataset.value;
buttons.forEach((b) => b.classList.remove('is-active'));
button.classList.add('is-active');
});
});
なお、暗黙のトラックには grid-template-columns で書いたサイズは適用されません。grid-auto-flow: column と grid-template-columns: repeat(3, 200px) を同時に書くと、最初の 3 列だけ 200px、4 列目以降は grid-auto-columns のサイズという、ちぐはぐな見た目になります。列方向に流すレイアウトでは grid-template-columns を書かず、行側を grid-template-rows で決めるのが基本の組み立て方です。
dense で空きマスを詰める
幅の違うアイテムが混ざったギャラリーを作ると、どうしても中途半端な空きマスができます。たとえば 4 列のグリッドで、いくつかのアイテムに grid-column: span 2 を指定した場合を考えてみてください。残りの空きが 1 マスしかないところに 2 マスぶんのアイテムが来ると、それは入りきらないので次の行へ送られ、1 マスだけぽっかり空いた穴が残ります。
既定の疎(sparse)アルゴリズムでは、この穴は埋まりません。配置の起点は前へ進むだけで戻らないため、後ろに 1 マスで収まるアイテムがあっても、そこには置かれないからです。
そこで dense を付けます。dense が指定されていると、アイテムを 1 つ置くたびにグリッドの先頭から空きを探し直すようになり、後ろにある小さいアイテムを前の穴に移動させて隙間を減らします。
.gallery {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-auto-rows: 64px;
gap: 8px;
/* 空きマスができたら、後ろのアイテムを前に詰めて埋める */
grid-auto-flow: row dense;
}
/* 2 列ぶんの幅を取るアイテム */
.tile--wide {
grid-column: span 2;
}
ここで使っている grid-column: span 2 は「開始位置は自動で決めてよいので、2 列ぶんの幅を取る」という指定です。開始する列番号を書いていないので、このアイテムも自動配置の対象のままになります。もし grid-column: 1 / 3 のように列番号を書いてしまうと位置が固定され、dense の影響を受けなくなります。
下のデモで切り替えてみてください。既定のままだと右端に空きマスが残りますが、dense を付けると後ろの番号が前に飛んできて隙間が埋まります。
<div class="demo">
<div class="demo__controls">
<button type="button" class="is-active" data-dense="off">grid-auto-flow: row</button>
<button type="button" data-dense="on">grid-auto-flow: row dense</button>
</div>
<div class="gallery" id="gallery">
<div class="tile tile--wide">1</div>
<div class="tile tile--wide">2</div>
<div class="tile">3</div>
<div class="tile tile--wide">4</div>
<div class="tile tile--wide">5</div>
<div class="tile">6</div>
<div class="tile tile--wide">7</div>
<div class="tile">8</div>
<div class="tile">9</div>
<div class="tile tile--wide">10</div>
</div>
<p class="demo__note">濃い色が 2 列ぶんのアイテムです。dense を付けると、番号の並びが飛んで空きマスが埋まります。</p>
</div>
.gallery {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-auto-rows: 64px;
gap: 8px;
/* 既定値。前から順に置くので、幅が足りない場所は空きマスとして残る */
grid-auto-flow: row;
}
/* ボタンで付け外しするクラス */
.gallery.is-dense {
/* 空きマスが出たら、後ろのアイテムを前に詰めて埋める */
grid-auto-flow: row dense;
}
/* 2 列ぶんの幅を取るアイテム */
.tile--wide {
grid-column: span 2;
background: #0059b3;
color: #fff;
border-color: #0059b3;
}
/* ここから下は見た目を整えるだけのスタイル */
.demo {
font-family: sans-serif;
padding: 16px;
}
.demo__controls {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 16px;
}
.demo__controls button {
padding: 8px 14px;
font-size: 13px;
font-family: monospace;
border: 1px solid #0059b3;
border-radius: 4px;
background: #fff;
color: #0059b3;
cursor: pointer;
}
.demo__controls button.is-active {
background: #0059b3;
color: #fff;
}
.tile {
display: flex;
align-items: center;
justify-content: center;
background: #e5f1ff;
border: 1px solid #99c7f0;
border-radius: 4px;
font-size: 20px;
font-weight: bold;
color: #0059b3;
}
.demo__note {
margin-top: 16px;
font-size: 13px;
color: #555;
}
const gallery = document.getElementById('gallery');
const buttons = document.querySelectorAll('.demo__controls button');
buttons.forEach((button) => {
button.addEventListener('click', () => {
// is-dense クラスの付け外しで dense を切り替える
gallery.classList.toggle('is-dense', button.dataset.dense === 'on');
buttons.forEach((b) => b.classList.remove('is-active'));
button.classList.add('is-active');
});
});
dense は見た目の順番と HTML の順番をずらす
デモで確認したとおり、dense を付けると番号の並びが 1・2・3・4・6・5……のように前後します。これは dense の副作用ではなく、仕様どおりの動作です。MDN でも「穴を埋めるためにアイテムが順番どおりでなく表示されることがある」と明記されています。
問題は、変わるのは見た目の位置だけで、HTML の並び順(DOM 順)はそのままだという点です。キーボードの Tab キーによるフォーカス移動も、スクリーンリーダーの読み上げも、CSS の配置ではなく DOM 順をたどります。つまり画面上は 4 の次に 6 が見えていても、Tab キーで移動するとフォーカスは 4 の次に 5 へ、そのあと 6 へ戻る、という動きになります。
アイテムがただの画像や装飾であれば実害はほとんどありません。しかし中にリンクやボタンが入っている場合、フォーカスの移動が視覚的な並びと一致せず、キーボードで操作する人には順番が飛んでいるように感じられます。dense を使うときは、次のどちらかで折り合いをつけるのが現実的です。
- 操作対象を含まない、画像中心のギャラリーやサムネイル一覧に限って使う
- 操作対象を含む場合は、見た目の順序と HTML の順序が大きく食い違わないよう、
spanするアイテムの数や位置をあらかじめ調整しておく
「隙間が埋まるから便利」という理由だけで全体に付けてしまうと、あとから原因の分かりにくい操作しづらさにつながります。埋めたい穴が実際にできているかを確認してから使うようにしてください。
思った順に並ばないときに見直すところ
grid-auto-flow は 1 行書くだけのプロパティですが、他のプロパティとの組み合わせ次第で結果が大きく変わります。期待どおりにならないときは、次の順で確認すると原因が絞り込めます。
grid-template-columns を書いていないので縦一列になる
display: grid と書いただけで列を定義していない場合、グリッドは 1 列として扱われます。既定の grid-auto-flow: row は 1 行ぶん(=ここでは 1 マス)を埋めたら次の行へ進むので、アイテムは縦に 1 列で積み上がります。横に並べたいなら、grid-template-columns: repeat(3, 1fr) のように列を用意するか、grid-auto-flow: column で列方向に流すかのどちらかが必要です。
column にしたのに 2 行目ができてしまう
grid-auto-flow: column は「1 列ぶんを縦に埋めきってから次の列へ」という動きです。そのため grid-template-rows で 2 行以上を定義していると、アイテムは 2 個ずつ縦に積まれてから右へ進みます。カードを横一列にきれいに並べたいだけなら、grid-template-rows は書かないでおくのが正解です。行が定義されていなければ 1 行だけのグリッドとして扱われ、アイテムは横に一列で並びます。
grid-auto-rows を書いたのにサイズが変わらない
grid-auto-rows が効くのは暗黙の行、つまり grid-template-rows で定義していない行だけです。すべての行を grid-template-rows で書ききっている場合、暗黙の行は 1 つも作られないので grid-auto-rows の出番はありません。逆に grid-auto-flow: column にしている状態で grid-auto-rows を書いても、作り足されるのは列なので変化しません。このときは grid-auto-columns に書き換えます。
dense を付けたのに隙間が埋まらない
dense は「後ろにある、その穴に収まるサイズのアイテム」を前に持ってくる仕組みです。したがって、穴より後ろに小さいアイテムが 1 つも無ければ何も起こりません。すべてのアイテムが同じ大きさで穴自体ができていない場合も同様です。
もうひとつ多いのが、grid-column: 2 / 4 のように開始位置まで書いてしまっているケースです。行や列の番号を指定したアイテムは自動配置の対象から外れるため、dense があっても動きません。幅だけを広げたいときは grid-column: span 2 と書きます。
書く場所がアイテム側になっている
grid-auto-flow は Grid コンテナーに指定するプロパティです。.item { grid-auto-flow: column; } のようにアイテム側へ書いても、そのアイテム自身が Grid コンテナーになっていない限り何も起きません。値は保持されるだけでエラーにもならないため気づきにくく、セレクターを 1 つ間違えただけ、というのは意外とよくあります。
まとめ
grid-auto-flow は、位置を明示していないアイテムをブラウザーがどう並べるかを決めるプロパティです。既定の row は行方向に埋めていき、column にすると列方向に埋めていきます。そして向きが変わると作り足されるトラックの方向も変わるため、サイズ指定は row なら grid-auto-rows、column なら grid-auto-columns とセットで覚えておくと迷いません。
dense は幅違いのアイテムが混ざったギャラリーで隙間を減らせる便利な値ですが、見た目の順序と HTML の順序がずれるという代償があります。リンクやボタンを含むレイアウトでは使いどころを選び、画像中心の一覧などに絞って使うのが安全です。まずは row と column の違いを手元で切り替えてみるところから試してみてください。