styling-system について
styling-systemは、フロントエンド開発者向けにCSSの構成選択と実装を支援するスキルです。デザイントークン、テーマ、レスポンシブレイアウト、コンポーネントのバリアントを扱い、比較資料と検証スクリプトも備えています。
主な機能
- 01Tailwind、CSS Modules、CSS-in-JS、素のCSSを比較
- 02スタイル構成と設定の計画
- 03デザイントークンの作成と統合
- 04ダークモードとレスポンシブレイアウトの設計
- 05コンポーネントのバリアントと状態スタイルの構築
- 06スクリプトによるスタイル実装の検証
活用シーン
- 01新規プロジェクトのスタイル方式を選択
- 02既存インターフェースにダークモードを追加
- 03コンポーネントのトークンとバリアントを統一
- 04スタイル構成を移行して実装を確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ mgd34msu/ goodvibes-plugin --skill styling-systemインストール後、Claude Code で/styling-systemを入力して読み込まれたか確認します。
同梱ファイルreferences/styling-comparison.mdscripts/validate-styling.sh
詳細
リソース
scripts/
validate-styling.sh
references/
styling-comparison.md
スタイリングシステム
このスキルでは、GoodVibes の高精度ツールを使用して、CSS アーキテクチャの判断と実装を進めます。スタイリング基盤の構築、デザインシステムの作成、テーマ設定やレスポンシブパターンの実装に、このワークフローを使用してください。
このスキルを使用するタイミング
以下の場合にこのスキルを読み込んでください。
- 新しいプロジェクトのスタイリング基盤を構築するとき
- Tailwind、CSS Modules、CSS-in-JS、またはバニラ CSS のいずれかを選ぶとき
- デザイントークンシステムを実装するとき
- ダークモードのサポートを追加するとき
- レスポンシブレイアウトやブレークポイント戦略を作成するとき
- スタイリング手法を移行するとき
- コンポーネントのバリアントシステムを構築するとき
- アニメーションパターンを設定するとき
トリガーフレーズ:「スタイリングを設定」「Tailwind を追加」「ダークモードを実装」「デザイントークン」「レスポンシブデザイン」「CSS アーキテクチャ」「テーマシステム」。
基本的なワークフロー
フェーズ 1:現状の把握
スタイリングについて判断する前に、プロジェクトの現在の状態を把握してください。
ステップ 1.1:既存のスタイリング手法を検出する
discover を使って、コードベース全体のスタイリングパターンを見つけます。
discover:
queries:
- id: tailwind_usage
type: grep
pattern: "(className=|class=).*\".*\\b(flex|grid|text-|bg-|p-|m-)"
glob: "**/*.{tsx,jsx,vue,svelte}"
- id: css_modules
type: glob
patterns: ["**/*.module.css", "**/*.module.scss"]
- id: css_in_js
type: grep
pattern: "(styled\\.|styled\\(|css`|makeStyles|createStyles)"
glob: "**/*.{ts,tsx,js,jsx}"
- id: design_tokens
type: glob
patterns: ["**/tokens.{ts,js,json}", "**/design-tokens.{ts,js,json}", "**/theme.{ts,js}"]
- id: dark_mode
type: grep
pattern: "(dark:|data-theme|ThemeProvider|useTheme|darkMode)"
glob: "**/*.{ts,tsx,js,jsx,css,scss}"
verbosity: count_only
ここからわかること:
- 主なスタイリング手法(Tailwind、CSS Modules、CSS-in-JS、バニラ CSS)
- デザイントークンの有無
- ダークモードの実装状況
- コードベース全体の一貫性
ステップ 1.2:設定ファイルを確認する
既存の設定を読み、構成を把握します。
precision_read:
files:
- path: "tailwind.config.js"
extract: content
- path: "tailwind.config.ts"
extract: content
- path: "postcss.config.js"
extract: content
- path: "src/styles/globals.css"
extract: outline
verbosity: minimal
ステップ 1.3:コンポーネントのスタイリングパターンを分析する
代表的なコンポーネントを読み、規約を把握します。
precision_read:
files:
- path: "src/components/Button.tsx" # or discovered component
extract: content
- path: "src/components/Card.tsx"
extract: content
output:
max_per_item: 100
verbosity: standard
フェーズ 2:判断
プロジェクトのニーズに合ったスタイリング手法を選びます。判断ツリーの全容については、references/styling-comparison.md を参照してください。
クイック判断ガイド
次の場合は Tailwind CSS を使用します。
- ユーティリティクラスを使って迅速にプロトタイピングしたい
- チーム全体での一貫性を優先したい
- 完全な自由よりもデザイン上の制約を重視する
- コンポーネントベースの UI(React、Vue、Svelte)を構築している
次の場合は CSS Modules を使用します。
- ビルドの複雑さを増やさずに、スコープ化されたスタイルを使いたい
- 従来の CSS を記述したい
- 既存のグローバルスタイルとの共存が必要
- バンドルサイズが重要な懸念事項である(Tailwind より小さい)
次の場合は CSS-in-JS を使用します。
- 実行時の動的なテーマ設定が必要
- スタイルの props に TypeScript の型を付けたい
- JavaScript のロジックを使うコンポーネントスコープのスタイルが必要
- styled-components または Emotion を使用している
次の場合はバニラ CSS を使用します。
- インタラクティブ要素が少ないシンプルなサイトを構築している
- 最大限の制御と最小限の依存関係を求めている
- プログレッシブエンハンスメントが重要である
- カスケードと継承のパターンを活用したい
フレームワーク固有の詳細なパターンについては、references/styling-comparison.md を参照してください。
フェーズ 3:設定
ステップ 3.1:依存関係をインストールする
選択した手法に応じて、必要なパッケージをインストールします。
Tailwind CSS:
precision_exec:
commands:
- cmd: "npm install -D tailwindcss postcss autoprefixer"
expect:
exit_code: 0
- cmd: "npx tailwindcss init -p"
expect:
exit_code: 0
verbosity: minimal
CSS-in-JS(styled-components):
precision_exec:
commands:
- cmd: "npm install styled-components"
- cmd: "npm install -D @types/styled-components"
verbosity: minimal
ステップ 3.2:設定を作成する
ベストプラクティスに従って設定ファイルを記述します。
Tailwind 設定の例:
import type { Config } from 'tailwindcss';
const config: Config = {
content: [
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
'./src/app/**/*.{js,ts,jsx,tsx,mdx}',
],
darkMode: 'class', // or 'media' for system preference
theme: {
extend: {
colors: {
primary: {
50: '#f0f9ff',
100: '#e0f2fe',
// ... full scale
900: '#0c4a6e',
},
},
fontFamily: {
sans: ['var(--font-inter)', 'system-ui', 'sans-serif'],
},
spacing: {
'18': '4.5rem',
},
},
},
plugins: [],
};
export default config;
ベストプラクティス:
- 設定ファイルには TypeScript を使用する(型安全性のため)
- デフォルト設定を置き換えずに、テーマを拡張する
- 動的な値には CSS 変数を使用する
- ビルドの遅延を避けるため、コンテンツパスを具体的にする
- ダークモードには
class戦略を使用する(より細かく制御できる)
ステップ 3.3:設定ファイルを記述する
precision_write:
files:
- path: "tailwind.config.ts"
content: |
import type { Config } from 'tailwindcss';
// ... [full config]
- path: "src/styles/globals.css"
content: |
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
/* ... design tokens */
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
}
}
verbosity: count_only
フェーズ 4:デザイントークンシステム
ステップ 4.1:トークンの構造を定義する
一貫性を保つため、トークンを一元管理するシステムを作成します。
トークンのカテゴリ:
- 色:プライマリ、セカンダリ、ニュートラル、セマンティック(成功、エラー、警告)
- タイポグラフィ:フォントファミリー、サイズ、ウェイト、行の高さ
- スペーシング:一貫したスケール(ベースは 4px、8px 刻み)
- シャドウ:エレベーションシステム
- ボーダー:角丸、幅
- ブレークポイント:レスポンシブデザインシステム
- アニメーション:継続時間、イージング関数
実装パターン:
// src/styles/tokens.ts
export const tokens = {
colors: {
primary: {
light: '#3b82f6',
DEFAULT: '#2563eb',
dark: '#1d4ed8',
},
semantic: {
success: '#10b981',
error: '#ef4444',
warning: '#f59e0b',
},
},
spacing: {
xs: '0.25rem', // 4px
sm: '0.5rem', // 8px
md: '1rem', // 16px
lg: '1.5rem', // 24px
xl: '2rem', // 32px
},
typography: {
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
mono: ['Fira Code', 'monospace'],
},
fontSize: {
xs: ['0.75rem', { lineHeight: '1rem' }],
sm: ['0.875rem', { lineHeight: '1.25rem' }],
base: ['1rem', { lineHeight: '1.5rem' }],
lg: ['1.125rem', { lineHeight: '1.75rem' }],
xl: ['1.25rem', { lineHeight: '1.75rem' }],
},
},
} as const;
export type Tokens = typeof tokens;
// Integration with ThemeProvider (React Context)
type Theme = {
tokens: Tokens;
mode: 'light' | 'dark';
};
// Integration with styled-components
import 'styled-components';
declare module 'styled-components' {
export interface DefaultTheme extends Tokens {
mode: 'light' | 'dark';
}
}
// Usage in components
import { useTheme } from 'styled-components';
const Button = styled.button`
background: ${props => props.theme.colors.primary};
padding: ${props => props.theme.spacing[4]};
`;
ステップ 4.2:トークンを Tailwind に統合する
// tailwind.config.ts
import { tokens } from './src/styles/tokens';
const config: Config = {
theme: {
extend: {
colors: tokens.colors,
spacing: tokens.spacing,
fontFamily: tokens.typography.fontFamily,
fontSize: tokens.typography.fontSize,
},
},
};
フェーズ 5:ダークモードの実装
ステップ 5.1:ダークモード戦略を選択する
クラスベース(推奨):
- 切り替えの動作をより細かく制御できます
- ユーザー設定を保存できます
- next-themes などのライブラリで動作します
メディアクエリベース:
- システム設定のみを反映します
- JS は不要です
- 柔軟性が低くなります
ステップ 5.2:next-themes をセットアップする(React/Next.js)
// src/components/ThemeProvider.tsx
import { ThemeProvider as NextThemesProvider } from 'next-themes';
import type { ReactNode } from 'react';
interface ThemeProviderProps {
children: ReactNode;
}
export function ThemeProvider({ children }: ThemeProviderProps) {
return (
<NextThemesProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
{children}
</NextThemesProvider>
);
}
ステップ 5.3:ダークモードのカラースケールを定義する
スムーズにテーマを切り替えるため、CSS 変数を使用します。
/* globals.css */
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--primary: 221.2 83.2% 53.3%;
--primary-foreground: 210 40% 98%;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--primary: 217.2 91.2% 59.8%;
--primary-foreground: 222.2 47.4% 11.2%;
}
}
ベストプラクティス:
- 値を簡単に調整できるよう、HSL 値を使用します
- セマンティックな命名(background、foreground、primary)を維持します
- アクセシビリティのため、コントラスト比をテストします(WCAG AA:4.5:1)
- ダークモードでは純粋な黒(#000)を避けます(暗いグレーを使用します)
フェーズ 6:レスポンシブデザインのパターン
ステップ 6.1:ブレークポイント戦略を定義する
モバイルファーストのアプローチ(推奨):
/* Base styles for mobile */
.container {
padding: 1rem;
}
/* Tablet and up */
@media (min-width: 768px) {
.container {
padding: 2rem;
}
}
/* Desktop and up */
@media (min-width: 1024px) {
.container {
padding: 3rem;
}
}
Tailwind のブレークポイント:
<div className="p-4 md:p-8 lg:p-12">
{/* padding increases with screen size */}
</div>
ステップ 6.2:コンテナクエリ(モダン)
コンポーネント単位のレスポンシブ対応には、コンテナクエリを使用します。
.card-container {
container-type: inline-size;
}
@container (min-width: 400px) {
.card {
display: grid;
grid-template-columns: 1fr 2fr;
}
}
Tailwind のコンテナクエリ:
// tailwind.config.ts
plugins: [require('@tailwindcss/container-queries')]
<div className="@container">
<div className="@md:grid @md:grid-cols-2">
{/* Responsive to container, not viewport */}
</div>
</div>
フェーズ 7:コンポーネントのバリアントシステム
ステップ 7.1:class-variance-authority(CVA)をインストールする
precision_exec:
commands:
- cmd: "npm install class-variance-authority clsx tailwind-merge"
verbosity: minimal
ステップ 7.2:cn ユーティリティを作成する
// src/lib/utils.ts
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
ステップ 7.3:バリアントコンポーネントを構築する
// src/components/Button.tsx
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
import type { ButtonHTMLAttributes } from 'react';
const buttonVariants = cva(
// Base styles
'inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50',
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
outline: 'border border-input bg-background hover:bg-accent',
ghost: 'hover:bg-accent hover:text-accent-foreground',
},
size: {
default: 'h-10 px-4 py-2',
sm: 'h-9 rounded-md px-3',
lg: 'h-11 rounded-md px-8',
icon: 'h-10 w-10',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
}
);
interface ButtonProps
extends ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}
export function Button({
className,
variant,
size,
...props
}: ButtonProps) {
return (
<button
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
);
}
メリット:
- 型安全なバリアントの props
- Tailwind クラスの自動マージ
- 一貫性のあるコンポーネント API
- 新しいバリアントを簡単に追加できます
フェーズ 8:エラー状態のスタイル設定
ステップ 8.1:フォームの検証状態
// src/components/Input.tsx
import { cva } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const inputVariants = cva(
'w-full rounded-md border px-3 py-2 text-sm transition-colors',
{
variants: {
state: {
default: 'border-input focus:border-primary focus:ring-2 focus:ring-primary/20',
error: 'border-destructive focus:border-destructive focus:ring-2 focus:ring-destructive/20',
success: 'border-green-500 focus:border-green-500 focus:ring-2 focus:ring-green-500/20',
},
},
defaultVariants: {
state: 'default',
},
}
);
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
error?: string;
success?: boolean;
}
export function Input({ error, success, className, ...props }: InputProps) {
const state = error ? 'error' : success ? 'success' : 'default';
return (
<div className="space-y-1">
<input
className={cn(inputVariants({ state }), className)}
aria-invalid={!!error}
aria-describedby={error ? 'input-error' : undefined}
{...props}
/>
{error && (
<p id="input-error" className="text-sm text-destructive" role="alert">
{error}
</p>
)}
</div>
);
}
ステップ 8.2:トースト通知
// src/components/Toast.tsx
import { cva } from 'class-variance-authority';
const toastVariants = cva(
'rounded-lg border p-4 shadow-lg',
{
variants: {
variant: {
default: 'bg-background border-border',
success: 'bg-green-50 border-green-200 text-green-900 dark:bg-green-950 dark:border-green-800 dark:text-green-100',
error: 'bg-red-50 border-red-200 text-red-900 dark:bg-red-950 dark:border-red-800 dark:text-red-100',
warning: 'bg-yellow-50 border-yellow-200 text-yellow-900 dark:bg-yellow-950 dark:border-yellow-800 dark:text-yellow-100',
},
},
}
);
ステップ 8.3:読み込み中/スケルトン状態
/* Skeleton pulse animation */
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.skeleton {
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
background-color: hsl(var(--muted));
border-radius: 0.375rem;
}
// src/components/Skeleton.tsx
export function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
return <div className={cn('skeleton', className)} {...props} />;
}
// Usage in loading states
export function CardSkeleton() {
return (
<div className="space-y-3">
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-3/4" />
<Skeleton className="h-20 w-full" />
</div>
);
}
フェーズ 9:アニメーションのパターン
ステップ 9.1:CSS トランジション(軽量)
.button {
transition: background-color 200ms ease-in-out;
}
.button:hover {
background-color: var(--primary-hover);
}
Tailwind:
<button className="transition-colors duration-200 hover:bg-primary-hover">
Click me
</button>
ステップ 9.2:Framer Motion(高度)
複雑なアニメーションやジェスチャーに使用します。
// src/components/AnimatedCard.tsx
import { motion } from 'framer-motion';
export function AnimatedCard() {
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3 }}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
>
<div className="card">Content</div>
</motion.div>
);
}
ベストプラクティス:
- UI のフィードバック用アニメーションは 300ms 未満にします
- イージング関数(ease-in-out、ease-out)を使用します
prefers-reduced-motionを尊重します- レイアウトをトリガーするプロパティ(width、height)のアニメーションを避けます
- transform と opacity を優先します(GPU で高速化されます)
フェーズ 10:検証
ステップ 10.1:スタイル検証スクリプトを実行する
bash scripts/validate-styling.sh .
検証スイートの全容については、scripts/validate-styling.sh を参照してください。
ステップ 10.2:ビルド出力を確認する
CSS バンドルサイズを確認し、未使用のスタイルがないことを確かめます。
precision_exec:
commands:
- cmd: "npm run build"
expect:
exit_code: 0
verbosity: standard
確認項目:
- CSS バンドルサイズ(Tailwind では gzip 圧縮後 50KB 未満が目安です)
- 重複するユーティリティクラスがないこと
- 適切なツリーシェイキング
- ダークモードのクラスが正しく生成されていること
ステップ 10.3:アクセシビリティを確認する
色のコントラストが WCAG の基準を満たしていることを確認します。
precision_exec:
commands:
- cmd: "npx @axe-core/cli http://localhost:3000"
verbosity: standard
ステップ 10.4:ビジュアルリグレッションテスト
自動スクリーンショット比較で、意図しない表示の変更を検出します。
ビジュアルテストでの Playwright:
// tests/visual.spec.ts
import { test, expect } from '@playwright/test';
test('button variants match snapshots', async ({ page }) => {
await page.goto('/components/button');
// Take screenshot and compare
await expect(page).toHaveScreenshot('button-variants.png', {
maxDiffPixels: 100,
});
});
test('dark mode toggle', async ({ page }) => {
await page.goto('/');
// Light mode
await expect(page).toHaveScreenshot('home-light.png');
// Toggle to dark
await page.click('[data-testid="theme-toggle"]');
await expect(page).toHaveScreenshot('home-dark.png');
});
Chromatic(Storybook 用):
# Install and setup
npm install --save-dev chromatic
# Run visual tests
npx chromatic --project-token=<token>
Percy(クロスブラウザー):
import percySnapshot from '@percy/playwright';
test('responsive layout', async ({ page }) => {
await page.goto('/dashboard');
await percySnapshot(page, 'Dashboard - Desktop');
await page.setViewportSize({ width: 375, height: 667 });
await percySnapshot(page, 'Dashboard - Mobile');
});
ステップ 10.5:CSS のパージ設定
JIT を使用する Tailwind v3+:
tailwind.config.ts の content パスがパージ設定として機能します。Tailwind は、本番ビルドで未使用のクラスを自動的に削除します。
// tailwind.config.ts
export default {
content: [
'./src/pages/**/*.{js,ts,jsx,tsx}',
'./src/components/**/*.{js,ts,jsx,tsx}',
'./src/app/**/*.{js,ts,jsx,tsx}',
],
// JIT mode is default in v3+
};
本番ビルドの最適化:
# Build with minification
npx tailwindcss -i ./src/styles/globals.css -o ./dist/output.css --minify
# Check final size
du -h dist/output.css
動的クラスをセーフリストに登録する:
// tailwind.config.ts
export default {
content: ['./src/**/*.{js,ts,jsx,tsx}'],
safelist: [
// Dynamic classes that can't be detected statically
'bg-red-500',
'bg-green-500',
'bg-blue-500',
// Or use patterns
{
pattern: /bg-(red|green|blue)-(400|500|600)/,
},
],
};
バンドルサイズを監視する:
precision_exec:
commands:
- cmd: "npm run build"
- cmd: "ls -lh dist/**/*.css"
verbosity: standard
目標:パージ後の一般的な Tailwind プロジェクトで、gzip 圧縮後 50KB 未満。
よくあるアンチパターン
避けること:
- 同じコンポーネントでスタイル設定の手法を混在させる(Tailwind と CSS-in-JS など)
- 静的な値にインラインスタイルを使用する
- 色や余白をハードコードする(デザイントークンを使用してください)
- ダークモードのコントラスト比を無視する
- Tailwind を上書きするために
!importantを使用する(代わりに詳細度を修正してください) - 幅や高さをアニメーションする(レイアウトの再計算が頻発します)
- モバイルファーストのレスポンシブデザインを省略する
- すべての箇所でピクセル値を使用する(アクセシビリティのため、rem を優先してください)
実践すること:
- 主なスタイル設定の手法を一つ選び、一貫性を保つ
- 動的な値には CSS 変数を使用する
- 最初からデザイントークンを実装する
- ダークモードのコントラストと読みやすさをテストする
- Tailwind のユーティリティファーストの理念に従う
- パフォーマンス向上のため、transform と opacity をアニメーションする
- モバイルファーストで設計し、大きな画面向けに拡張する
- 拡張性のために相対単位(rem、em、%)を使用する
セキュリティに関する考慮事項
CSP(コンテンツセキュリティポリシー)とスタイル設定:
デフォルトで安全(ビルド時):
- Tailwind CSS - ビルド時にコンパイルされ、実行時のインジェクションはありません
- CSS Modules - ビルド時にコンパイルされ、CSP に安全です
- Vanilla CSS / SCSS - 静的ファイルとして配信されます
CSP の設定が必要(実行時):
- styled-components - 実行時に
<style>タグを挿入します - Emotion - 実行時にスタイルを挿入します
- インラインスタイル -
unsafe-inlineまたは nonce が必要になる場合があります
CSP のベストプラクティス:
// Next.js example with CSP for styled-components
import { getCspNonce } from './lib/csp';
export default function RootLayout({ children }) {
const nonce = getCspNonce();
return (
<html>
<head>
<meta
httpEquiv="Content-Security-Policy"
content={`style-src 'self' 'nonce-${nonce}';`}
/>
</head>
<body>
<StyleSheetManager nonce={nonce}>
{children}
</StyleSheetManager>
</body>
</html>
);
}
CSS インジェクションの防止:
- サニタイズされていないユーザー入力を CSS の値に展開しないでください
- ユーザーが設定できる色やテーマには許可リストを使用してください
- 適用する前に色の値を検証してください
// DON'T: Unsafe user input interpolation
const BadButton = ({ userColor }) => (
<div style={{ backgroundColor: userColor }} /> // CSS injection risk!
);
// DO: Validate against allowlist
const ALLOWED_COLORS = ['primary', 'secondary', 'accent'] as const;
type AllowedColor = typeof ALLOWED_COLORS[number];
const SafeButton = ({ color }: { color: AllowedColor }) => (
<div className={`bg-${color}`} />
);
// DO: Validate hex colors
function isValidHexColor(color: string): boolean {
return /^#[0-9A-Fa-f]{6}$/.test(color);
}
クイックリファレンス
調査フェーズ:
discover: { queries: [tailwind, css_modules, css_in_js, tokens, dark_mode], verbosity: count_only }
precision_read: { files: [tailwind.config.ts, globals.css], verbosity: minimal }
設定フェーズ:
precision_exec: { commands: [{ cmd: "npm install -D tailwindcss postcss autoprefixer" }] }
precision_write: { files: [tailwind.config.ts, globals.css, tokens.ts], verbosity: count_only }
検証フェーズ:
bash scripts/validate-styling.sh .
npm run build
詳細な判断ツリー、トークンの例、フレームワーク固有のパターンについては、references/styling-comparison.md を参照してください。