コンポーネントファースト戦略
zudo-docがカスタムCSSクラス名ではなく、ユーティリティクラスを持つコンポーネントを使用する理由。
zudo-docはコンポーネントファースト戦略に従います:UIは常にTailwindユーティリティクラスを持つコンポーネントとして表現します。カスタムCSSクラス名を別のスタイルシートで作成することはしません。
問題
ユーティリティCSSフレームワークとコンポーネントフレームワークを併用するプロジェクトでは、開発者は従来のCSSパターンに戻りがちです。コンポーネント内でユーティリティクラスを組み合わせる代わりに、.profile-card、.btn-primary、.sidebar-navといったカスタムCSSクラス名を別のスタイルシートやCSSモジュールで作成します。
これにより、コードベースが断片化します:
ユーティリティをインラインで使うコンポーネント
カスタムCSSクラスを導入するコンポーネント
両方のアプローチを混在させるコンポーネント
ルール
コンポーネント自体が抽象化です。 .cardや.btn-primaryのようなCSSクラス名は不要です。コンポーネントがカプセル化を、ユーティリティクラスがスタイリングを担当します。
カードが必要? → ユーティリティクラスを持つ
<Card>コンポーネントを作成ボタンバリアント? →
<Button variant="primary">コンポーネントレイアウトパターン? →
<PageLayout>コンポーネント
zudo-docでの実践
zudo-docはzfb上で動作するPreactコンポーネント(.tsx)を、Tailwind CSS v4ユーティリティとともに使用します。プロジェクトが所有する .tsx ファイルでは、class ではなく className を書いてください。scaffold は react を preact/compat にマッピングするため、作成した JSX は React 互換の型を使い、class は zfb check に失敗します。サーバーレンダリングのみのコンポーネントでも、クライアントでハイドレートされるアイランドでも、同じコンポーネントファーストのルールが適用されます。
サーバーレンダリングコンポーネント
ほとんどのUIはサーバーレンダリングです。JavaScriptを出力せず、ユーティリティクラスをインラインで使います。パッケージの packages/ はサーバーレンダリングの例です。プロジェクトが所有するコンポーネントは次の形で書いてください。
export function Footer({ copyright }: Props) {
return (
<footer className="border-t border-muted bg-surface px-hsp-xl py-vsp-xl">
<div
className="text-center text-caption text-muted"
dangerouslySetInnerHTML={{ __html: copyright }}
/>
</footer>
);
}.footerクラスなし。footer.module.cssなし。コンポーネントが抽象化です。
クライアントでハイドレートされるアイランド
インタラクティブなモジュールは "use client" で始め、ホストが @takazudo/zfb の Island() でコンポーネントをラップします。Island({ when, children }) はハイドレーションを始めるタイミング("load"、"idle"、"visible")を選び、必要なら ssrFallback でサーバーレンダリング時のマークアップを提供します。パッケージの packages/ は、既存の SidebarToggle アイランドにこの呼び出し形式を使っています。必須の静的インポート経路と実験的なスキャナーの制約については、完全なインタラクティブアイランドのレシピに従ってください。
import { Island } from "@takazudo/zfb";
// Counter is imported from a module that starts with "use client".
const CounterIsland = () =>
Island({
when: "visible",
ssrFallback: <span className="text-caption">Loading counter...</span>,
children: <Counter />,
});アンチパターン
zudo-docプロジェクトでCSSクラス名を作成してはいけません:
/* WRONG — don't create custom CSS classes */
.profile-card {
display: flex;
gap: 1rem;
padding: 1.5rem;
}
.profile-card__name {
font-size: 1.25rem;
font-weight: 600;
}// WRONG — custom class names bypass the design system
<div className="profile-card">
<h3 className="profile-card__name">{name}</h3>
</div>代わりに:
// RIGHT — utility classes, the component is the abstraction
<div className="flex gap-hsp-md p-hsp-lg">
<h3 className="text-body font-semibold">{name}</h3>
</div>バリアントはPropsで
CSSモディファイアクラス(.btn--primary、.btn--secondary)ではなく、コンポーネントプロップスを使用:
function Button({ variant = "primary", children }) {
const styles = {
primary: "bg-accent text-bg hover:bg-accent-hover",
secondary: "bg-surface text-fg border border-muted",
};
return (
<button className={`${styles[variant]} font-semibold py-vsp-xs px-hsp-md rounded`}>
{children}
</button>
);
}使い方:
<Button variant="primary">Save</Button>
<Button variant="secondary">Cancel</Button>維持すべき.btn-primaryクラスはありません。variantプロップは型安全で、補完が効き、自己文書化されています。
コンポーネントの組み合わせ
複雑なレイアウトは、より多くのCSSを追加するのではなく、より小さなコンポーネントを組み合わせて構築します:
<div className="divide-y divide-muted">
{users.map((user) => (
<div className="flex items-center gap-hsp-md py-vsp-sm">
<Avatar src={user.avatar} size="sm" />
<div className="flex-1 min-w-0">
<p className="text-small font-medium text-fg truncate">{user.name}</p>
<p className="text-caption text-muted truncate">{user.email}</p>
</div>
</div>
))}
</div>各要素(<Avatar>、リストレイアウト)はコンポーネントです。.user-list__itemや.user-list__avatarといったクラス名は不要です。
デザイントークンを使用
任意の値ではなく、プロジェクトトークンを常に使用:
// WRONG — arbitrary values bypass the design system
<div className="p-[1.2rem] text-[0.875rem] text-[#6b7280]">
// RIGHT — use design tokens
<div className="p-hsp-md text-small text-muted">利用可能なトークンについてはデザインシステムを参照してください。
カスタムCSSが許容される場合
プロジェクトレベルのCSSは src/ に置きますが、パッケージが所有するスタイルをそこに複製してはいけません:
コンテンツタイポグラフィ —
.zd-contentはpackages/で一度だけ定義され、zudo- doc/ src/ content. css @takazudo/として配布されます。scaffold は MDX タイポグラフィのルールをコピーせず、このパッケージスタイルシートをインポートしますzudo- doc/ content. css プロジェクトのカスタマイズ —
@themeのオーバーライド、プロジェクト固有の機能スタイル、明示的なコンポーネントスロットのスタイルはsrc/に置きますstyles/ global. css
それ以外のすべて(プロジェクトが所有するコンポーネント、レイアウト、通常のUI要素)はユーティリティクラスを直接使用します。
ルールのまとめ
常にコンポーネントを作成 — CSSクラスではなく
ユーティリティクラスを直接使用 — コンポーネントマークアップ内で
CSSモジュールファイルやカスタムクラス名を作成しない
バリアントにはプロップスを使用 — CSSモディファイアではなく
コンポーネントを組み合わせる — より多くのCSSではなく、小さなコンポーネントから複雑なUIを構築
プロジェクトトークンを使用 —
text-fg、bg-surface、p-hsp-md、任意の値ではなく