a-blog cms の develop テーマを Tailwind CSS v4 の CSS-first 構成に寄せた話
a-blog cms の develop テーマでは、Vite と Tailwind CSS v4 を使って CSS と JavaScript をビルドしています。
今回見直したかったのは、Tailwind CSS v4 を使っているにもかかわらず tailwind.config.js が残っている点でした。Tailwind CSS v4 では、テーマトークンやユーティリティ、プラグインの読み込みを CSS 側で扱う CSS-first な構成が基本になります。もちろん JavaScript config が完全に使えなくなったわけではありませんが、必要がないなら CSS 側へ寄せた方が、設定の所在が分かりやすくなります。
この記事では、a-blog cms の develop テーマで tailwind.config.js を使わない構成へ変更した内容をまとめます。
変更前の構成
対象のテーマディレクトリは次の場所です。
web/themes/develop
Vite の設定では、Tailwind CSS v4 用の Vite プラグインを読み込んでいました。
import tailwindcss from '@tailwindcss/vite';
メイン CSS もすでに Tailwind v4 らしい書き方になっていました。
@import 'tailwindcss';
@import './_theme.css';
@import './editor.css';
@utility container {
max-width: 64rem;
padding-inline: 1rem;
margin-inline: auto;
}
つまり、テーマ全体が古い Tailwind v3 構成だったわけではありません。
残っていた主な依存は、src/style/editor.css のこの部分でした。
@plugin "@tailwindcss/typography";
@config '../../tailwind.config.js';
@config は JavaScript の Tailwind config を CSS から読み込むためのディレクティブです。今回のテーマでは、tailwind.config.js の中に @tailwindcss/typography の .prose 向けカスタマイズが大量に書かれていました。
なぜ tailwind.config.js が残っていたのか
理由は @tailwindcss/typography です。
このプラグインは .prose クラスに対して、記事本文や CMS から出力される HTML に読みやすいタイポグラフィスタイルを付けてくれます。develop テーマでは、a-blog cms のブロックエディターやカラムユニットに合わせて、次のような要素を .prose の中で細かく調整していました。
- 画像、動画、figure、caption
- テーブルと横スクロール
- リンクボタン
- ファイルブロック
- 埋め込みカード
- カラムレイアウト
- 地図、ストリートビュー
- 目次
- a-blog cms のテーブル用クラス
Tailwind Typography の raw CSS カスタマイズは、現状では JavaScript config の theme.extend.typography に書く形が案内されています。そのため、Tailwind v4 に移行していても、この部分だけ tailwind.config.js が残りやすい状態でした。
ただし今回のカスタマイズは、Tailwind の theme API に深く依存しているというより、ほとんどが通常の CSS と CSS カスタムプロパティで表現できる内容でした。
そこで、tailwind.config.js の役割を CSS 側へ移すことにしました。
方針
今回の方針はシンプルです。
@tailwindcss/typography 自体は引き続き使います。ただし、プラグインの raw CSS カスタマイズを tailwind.config.js 経由で注入するのではなく、テーマ側の CSS として src/style/editor.css に書きます。
変更後は、editor.css がこのような構成になります。
/* 管理画面側では、preflight を読み込まないようにする */
@layer theme, base, components, utilities;
@import 'tailwindcss/theme.css' layer(theme);
@import 'tailwindcss/utilities.css' layer(utilities);
@import './_theme.css';
@plugin "@tailwindcss/typography";
@layer utilities {
.prose :where(...):not(:where(.not-prose, .not-prose *)) {
...
}
}
ポイントは、.prose の中にだけ効くようにスコープしていることです。
Typography プラグインは、.prose 内の要素に対して次のようなセレクタを生成します。
.prose :where(p):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
...
}
今回追加した CSS でも同じ考え方を踏襲し、.not-prose の中には影響しないようにしています。
.prose :where([data-type='linkButton']):not(:where(.not-prose, .not-prose *)) {
margin-top: 1.25em;
margin-bottom: 1.25em;
}
これにより、Typography プラグインの基本スタイルはそのまま使いながら、a-blog cms 固有の HTML だけをテーマ側で上書きできます。
実際に変更したファイル
主に変更したのは次のファイルです。
src/style/editor.css
src/style/_theme.css
stylelint.config.js
tailwind.config.js
tailwind.config.js を削除
まず、tailwind.config.js は削除しました。
もともとこのファイルには、theme.extend.typography.DEFAULT.css として .prose 用の CSS-in-JS が書かれていました。そこにあった内容を、通常の CSS として src/style/editor.css に移しています。
JavaScript で書かれていた次のような値も、
color: theme('colors.gray.500')
Tailwind v4 の CSS 変数へ置き換えました。
color: var(--color-gray-500);
また、ブレイクポイント参照も JavaScript の theme() ではなく、CSS 側で直接扱う形にしました。
@media (width >= 48rem) {
...
}
.prose カスタマイズを editor.css に移動
src/style/editor.css には、Typography プラグインの読み込みに続けて、a-blog cms 向けの .prose カスタマイズを追加しました。
例えば、リンクボタンは次のように定義しています。
.prose :where([data-type='linkButton'] a, [data-type='fileBlock'][data-display-type='button'] a):not(
:where(.not-prose, .not-prose *)
) {
display: inline-flex;
gap: 0.375em;
align-items: center;
padding: 0.5em 0.75em;
font-size: var(--text-sm);
font-weight: var(--font-weight-semibold);
line-height: 1.3;
color: var(--color-gray-900) !important;
text-decoration: none !important;
background-color: var(--color-indigo-50) !important;
border: 1px solid var(--color-gray-200);
border-radius: var(--radius-md);
transition-property: opacity;
}
埋め込みカードも、CMS の出力クラスに合わせて .prose の中でスタイルしています。
.prose :where([class*='column-embed'] .acms-embed-link):not(:where(.not-prose, .not-prose *)) {
display: block;
padding: 0;
overflow: hidden;
font-weight: 400;
color: inherit;
text-decoration: none;
background-color: var(--color-white);
border: 1px solid var(--color-gray-200);
border-radius: var(--radius-md);
transition-property: opacity;
}
このように、tailwind.config.js の中に閉じ込められていた CMS 固有の見た目を、テーマの CSS として読める場所へ戻したのが今回の一番大きな変更です。
_theme.css の breakpoint 参照を v4 らしく修正
src/style/_theme.css では、もともと次のように書かれていました。
@media (width >= theme(breakpoint.sm)) {
--unit-gap-x: 2em;
}
これを Tailwind v4 の CSS 変数トークンに合わせて、次のように変更しました。
@media (width >= theme(--breakpoint-sm)) {
--unit-gap-x: 2em;
}
@theme で定義した値や Tailwind が提供するトークンは、CSS 側から theme(--breakpoint-sm) のように参照できます。
stylelint.config.js から @config の許可を削除
@config を使わない構成にしたので、Stylelint 側でも config at-rule の許可を外しました。
これにより、今後うっかり @config を戻した場合に lint で気づきやすくなります。
ignoreAtRules: [
'theme',
'source',
'utility',
'variant',
'custom-variant',
'plugin',
'apply',
],
注意したところ
今回の移行では、単に tailwind.config.js を消すだけでは不十分です。
tailwind.config.js 経由で Typography プラグインに渡していた CSS は、プラグイン側で .prose :where(...):not(...) の形に変換されていました。そのため、CSS に移すときも同じスコープを保たないと、テーマ全体にスタイルが漏れる可能性があります。
特に CMS の本文エリアでは、not-prose を使って Typography の影響を受けない領域を作ることがあります。そのため、追加したルールは基本的に次の形に揃えています。
.prose :where(...):not(:where(.not-prose, .not-prose *)) {
...
}
また、a-blog cms が出力するクラスには js-edit_inplace や columnIcon のように、Stylelint の kebab-case ルールに合わないものがあります。これは自分たちで命名している CSS クラスではなく、CMS 側の出力に合わせる必要があるため、その部分だけ selector-class-pattern を無効化しています。
/* stylelint-disable selector-class-pattern */
.prose :where(:first-child.js-edit_inplace > :first-child):not(:where(.not-prose, .not-prose *)) {
margin-block-start: 0;
}
/* stylelint-enable selector-class-pattern */
確認したこと
変更後、次の確認を行いました。
npm run stylelint
Stylelint は成功しました。
npm run build
Vite の production build も成功しました。
ビルド後の CSS は次のように生成されています。
dist/assets/admin-Bp1MALJ0.css
dist/assets/bundle-IRhKuCPZ.css
また、ローカルサイトにもアクセスして確認しました。
https://tailwind.ddev.site/
トップページは HTTP 200 で応答し、HTML 側も新しいビルド済みファイルを参照していました。
<link rel="stylesheet" href="/themes/develop/dist/assets/bundle-IRhKuCPZ.css?...">
<script type="module" src="/themes/develop/dist/assets/bundle-CbAxGCho.js?..." async></script>
この変更で良くなったこと
今回の変更で、Tailwind CSS v4 の CSS-first 構成にかなり近づきました。
tailwind.config.js を見に行かなくても、テーマの見た目に関する設定は src/style 配下で追えるようになっています。
特に editor.css は、a-blog cms のブロックエディターや本文表示に関わる CSS なので、Typography のカスタマイズもそこにまとまっていた方が自然です。
変更前は次のような構成でした。
editor.css
-> @plugin "@tailwindcss/typography"
-> @config "../../tailwind.config.js"
-> typography.DEFAULT.css
変更後は次のようになります。
editor.css
-> @plugin "@tailwindcss/typography"
-> @layer utilities に .prose カスタマイズを書く
設定の流れが短くなり、Tailwind v4 の書き方としても素直になりました。
まとめ
Tailwind CSS v4 では、できるだけ CSS 側に設定を寄せることで、テーマトークンやユーティリティ、プラグインの読み込みを一箇所で管理しやすくなります。
今回の develop テーマでは、tailwind.config.js が残っていた理由は主に @tailwindcss/typography の raw CSS カスタマイズでした。その内容を src/style/editor.css に移すことで、@config を使わずに同等のスタイルを維持できるようにしました。
結果として、tailwind.config.js なしで stylelint と vite build が通り、ローカルサイトでも新しい CSS/JS が読み込まれる状態になっています。
Tailwind v4 への移行では、「JavaScript config を全部消せるか」よりも、「その config が本当に Tailwind の設定である必要があるか」を見直すのが大事だと感じました。今回のように、実態がテーマ固有の CSS であれば、CSS に戻してしまった方が読みやすく、保守もしやすくなります。
と、今回は Codex くんが教えてくれました。
修正した、ファイルは
develop/src/style/editor.css
develop/src/style/_theme.css
で、develop/stylelint.config.js から @config の許可を削除し、当初の目標であった tailwind.config.js を削除することができました。
修正したファイルをアップしておきます。