概要
Tailwind CSSで複雑なGridレイアウトを書こうとして、クラス名が長くなりすぎて困ったことはありませんか?
私は結構あります。
具体的にはgrid-template-(columns|rows)でauto-fitやminmax()を組み合わせたり、grid-template-areasを使いたいときなどです。
一度目に書く分にはまだ大丈夫ですが、後から読み解いたり継続的にメンテナンスをするのはかなり厳しいかも……と感じています。
そんな中、複雑なレイアウトだけはScoped Styleで書くというアプローチを試してみたので、その方法を紹介します。
前提
この記事で紹介するアプローチは、Svelte、Vue、AstroなどのScoped Styleをサポートするフレームワークを使っていることが前提です。
これらのフレームワークでは、コンポーネント内に<style>ブロックを書くだけで、自動的にそのコンポーネント内にスコープされたスタイルが適用されます。
素のReactの場合でも、CSS Modulesなどを使えば似たようなことはできますが、記法が異なるためこの記事のコード例をそのまま使うことはできません。ご了承ください。
想定読者
この記事は以下のような方を想定して書いています。
- Tailwind CSSを普段使っていて、基本的な使い方は理解している
- Gridレイアウトを実装することがある
- Svelte、Vue、Astroなどのフレームワークを使っている
- コンポーネント単位でのスタイル実装を理解している
問題:Tailwind CSSだけで複雑なGridを書くのは大変
grid-template-(columns|rows)の複雑な定義
例えば、以下のようにレスポンシブなグリッドレイアウトを実装したいとします。
.foo {
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
}
これをTailwindのクラスだけで実現しようとすると、以下のように長いクラス名になります。
<div class="grid grid-cols-[repeat(auto-fit,minmax(--spacing(80),1fr))]">
<!-- ... -->
</div>
上記の例であればまだ読めるかと思いますが、もっと複雑なカラムを指定したり、他のクラスがたくさんついていたりするとかなり苦しいです。
また読みづらいだけでなく、HTMLが肥大化するためマークアップの意図が読み取りにくくもなってしまいます。
grid-template-areasの実装
セマンティックな領域定義ができるgrid-template-areasを使いたい場合も大変です。
通常のCSSで書く場合はこのようになります。
.foo {
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
grid-template-columns: 200px 1fr;
grid-template-rows: auto 1fr auto;
}
これをTailwind CSSで書こうとすると以下のようになります。
<div class="grid [grid-template-areas:'header_header'_'sidebar_main'_'footer_footer'] [grid-template-columns:200px_1fr] [grid-template-rows:auto_1fr_auto]">
<!-- ... -->
</div>
素のCSSでは改行を含めて書けるため視覚的にも領域を理解しやすいのですが、Tailwind CSSではそれができません。
もちろんglobals.cssのようなエントリーポイントにカスタムユーティリティとして定義しても良いのですが、使う頻度が少ないレイアウトの場合グローバルに定義するのもやや憚られます。
試してみたアプローチ:Scoped Styleとの併用
そこで思いついたのが、Scoped Styleを使って複雑な部分だけCSSで書くという方法です。
Svelte、Vue、Astroなどのフレームワークはコンポーネント単位でスコープされたCSSが書けるので、この機能を活用してみました。
これ以降のコードは、直近私がSvelteを書いていたのでSvelte前提になっていますが、VueでもAstroでもほぼ同じ書き方で実現できます。
詳細はそれぞれのフレームワークのドキュメントを参考にしてください。
基本的なやり方
- 複雑なGridレイアウトが必要な要素に
data-*属性などを付与 -
<style>ブロック内でセレクタを使ってスタイルを定義 - 他のユーティリティクラスはTailwind CSSを使い続ける
こうすることで、以下のような利点がありました。
- レイアウトのロジックがスコープされたCSSとして明示的に定義される
- HTMLがシンプルになる
- Tailwind CSSのユーティリティと素のCSSの良いとこ取りができる
実装例
grid-template-(columns|rows)の例
Before:Tailwindのarbitrary valuesのみ
<div class="grid grid-cols-[repeat(auto-fit,minmax(--spacing(80),1fr))] gap-4">
<!-- ... -->
</div>
After:Scoped Styleとの組み合わせ
<div class="grid gap-4" data-grid>
<!-- ... -->
</div>
<style lang="postcss">
@reference "tailwindcss";
[data-grid] {
grid-template-columns: repeat(auto-fit, minmax(--spacing(80), 1fr));
}
</style>
HTMLがかなりすっきりしました。
グリッドレイアウトの定義はstyleブロックに移動していますが、--spacing()というTailwind v4の関数を使っているので、theme変数は引き続き使えます。
grid-template-areasの例
Before:Tailwindのarbitrary valuesのみ
<div class="grid [grid-template-areas:'header_header'_'sidebar_main'_'footer_footer'] [grid-template-columns:200px_1fr] [grid-template-rows:auto_1fr_auto] gap-4">
<header class="[grid-area:header]">Header</header>
<aside class="[grid-area:sidebar]">Sidebar</aside>
<main class="[grid-area:main]">Main Content</main>
<footer class="[grid-area:footer]">Footer</footer>
</div>
After:Scoped Styleとの組み合わせ
<div class="grid gap-4" data-layout>
<header data-area="header">Header</header>
<aside data-area="sidebar">Sidebar</aside>
<main data-area="main">Main Content</main>
<footer data-area="footer">Footer</footer>
</div>
<style lang="postcss">
@reference "tailwindcss";
[data-layout] {
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
grid-template-columns: 200px 1fr;
grid-template-rows: auto 1fr auto;
}
[data-area="header"] {
grid-area: header;
}
[data-area="sidebar"] {
grid-area: sidebar;
}
[data-area="main"] {
grid-area: main;
}
[data-area="footer"] {
grid-area: footer;
}
</style>
grid-template-areasを使った実装も、こうすればシンプルに書けます。
この例ではdata-*属性を使っていますが、クラス名を使っても構いません。
ただし、Tailwind CSSのユーティリティクラスと区別しやすくするため、data-*属性を使うのが良いかと考えています。
使ってみた感想
良かった点
- HTMLがシンプルで読みやすくなった
- 複雑なレイアウトロジックをCSSとして明示的に管理できる
- Tailwind CSSのtheme変数は引き続き使える
- スコープがあるので他のコンポーネントに影響しない
気になった点
- Tailwind CSSの、すべてクラスで完結するという思想からは外れる
- Scoped Styleをサポートしないフレームワークでは使えない(使いづらい)
- プロジェクト内で統一したルールを決めておく必要がある
おわりに
Tailwindで複雑なGridレイアウトを書く際の課題に対して、Scoped Styleを併用するアプローチを試してみました。
個人的にはHTMLがシンプルになって可読性が上がったと感じています。
ただし「デフォルトのTailwind CSSの書き方ですべて完結させたい」という考え方はあると思いますし、プロジェクトによって向き不向きもあるかもしれません。
同じような課題に直面したことがある方、別のアプローチを試している方がいれば、ぜひコメントで教えてください。