`grid`だけで子コンポーネントを高さいっぱいにする
結論
ページを高さいっぱいにしたいときは layout で <div className="grow grid">{children}</div>
と書くと良い。
コンポーネントの高さを広げる方法
一般的に知られているように CSS において、ブロック要素はデフォルトで親コンテナの幅いっぱいになります。しかし高さはそうではなく、コンテンツの高さになります。
コンテンツを縦に並べていくだけであればそれでも困らないのですが、親コンテナ側で決まった高さがありそれにコンテンツの高さを合わせたいときが存在します。代表的な例はコンテンツの最小の高さを画面に合わせてフッターをページ下に固定したいときです。
そのようなとき子要素の高さを広げる方法としてコンテナに flex flex-col
コンテンツに grow
を指定する方法がよく使われます。コンテンツ領域を広げてフッターをページ下に固定するときはこのようになるでしょう。
export default function RootLayout({ children }) {
return (
<html lang="ja">
<body className="flex flex-col min-h-screen">
<Header />
<div className="grow">{children}</div>
<Footer />
</body>
</html>
)
}
しかしときにはこれだけでは不十分な場合があります。各ページに書かれる実際のコンテンツの高さが変わらないのです。あくまで grow
したのは layout にある div
であり、その {children}
配下には全く影響しません。ページ側に <div class="bg-gray-400">content</div>
と書くことで内容の一行分しか背景が設定されないことを確認できます。
複数の要素があるコンテナ内で、要素を選択して広げたいときに flex
はとても便利ですが、子コンポーネントに対しても使おうとするとカプセル化の問題が発生します。つまり同じ方法で layout に <div className="flex">
子コンポーネント側に <div className="grow">
と書くことで、それらが相互の依存を発生させます。
コンポーネントは margin
を持つべきではないと言われるのと同じように、レイアウトに関する指定は親側が決めるべきことで、子コンポーネント側はその領域に合わせて表示されることが理想形といえるでしょう。
grid
コンテンツのサイズを最大化する そんなときにはコンテナに grid
を指定するだけで解決します。通常 grid
はレイアウトを指定してコンテンツを配置するときに使われますが、デフォルトで自身のサイズに合わせてコンテンツを配置するのです。
export default function RootLayout({ children }) {
return (
<html lang="ja">
<body className="flex flex-col min-h-screen">
<Header />
- <div className="grow flex">{children}</div>
+ <div className="grow grid">{children}</div>
<Footer />
</body>
</html>
)
}
export default function Page() {
return (
- <div className="grow">
+ <div>
<Content />
</div>
)
}
grow
を使わずに要素をページ下に固定するテクニック
おまけ:ページコンテンツを高さいっぱいにしたり、フッターをページ下に固定するには前述した方法で十分ですが、ときには grow
が必要ではないときもあります。そんなときに flex
を追加せずに要素をページ下にテクニックを紹介します。
フッター固定のためにメインコンテンツを flex-grow: 1;
で広げてフッターを押し出す代わりに、フッター自身を position: sticky;
で固定する方法です。一見すると sticky
なので画面下に張り付きそうですが、しっかりページコンテンツの下に表示できます。
この方法では position: sticky; top: 100%;
を使います。ただしそのコンテナは min-height: 100vh;
相当である必要があります。結局コンテナの高さを設定する必要があるので単体で使える理想的な方法ではないですが、 flex
, grow
を省くことができるため、コンテナの高さだけを考えればよくなりシンプルになると思います。
ここでは RootLayout では共通のヘッダーを表示するが、フッターは各ページによって出しわけることを考えます。まずは RootLayout で前述した gird
を使う方法でページコンテンツを高さいっぱいにします。
export default function RootLayout({ children }) {
return (
<html lang="ja">
<body className="flex flex-col min-h-screen">
<Header />
<div className="grow grid">{children}</div>
</body>
</html>
)
}
そしてページ側ではフッターを内包するコンテナに sticky top-full
を指定することでフッターを固定します。
export default function Page({ children }) {
return (
<div>
<Content />
<div className="sticky top-full">
<Footer />
</div>
</div>
)
}
この方法は sticky
の動作を理解するものとしても面白い例です。これらの動作が組み合わさって機能しています。
-
sticky
要素は通常の要素のように高さを確保すること -
sticky
要素はコンテナの中でしか固定されないこと -
top-full
によってそのコンテナの一番下まで押し出していること
つまり sticky top-full
を指定した要素は、スクロール領域に対してそのコンテナの高さまで押し出そうとするが、実際にはコンテナ内でしか固定されないためコンテナ下部に張り付くという動作をします。
まとめ
- layout では
<div className="grow grid">{children}</div>
と書くと良い -
grow
を使うまでもないところでフッターを固定したいときはsticky top-full
を使うと良い
Discussion