mcpField
S
styling-system

styling-system

github.com/mgd34msuデザイン・UI647 回インストール10 週間前に更新
リポジトリ

styling-system について

styling-systemは、フロントエンド開発者向けにCSSの構成選択と実装を支援するスキルです。デザイントークン、テーマ、レスポンシブレイアウト、コンポーネントのバリアントを扱い、比較資料と検証スクリプトも備えています。

主な機能

  1. 01Tailwind、CSS Modules、CSS-in-JS、素のCSSを比較
  2. 02スタイル構成と設定の計画
  3. 03デザイントークンの作成と統合
  4. 04ダークモードとレスポンシブレイアウトの設計
  5. 05コンポーネントのバリアントと状態スタイルの構築
  6. 06スクリプトによるスタイル実装の検証

活用シーン

  1. 01新規プロジェクトのスタイル方式を選択
  2. 02既存インターフェースにダークモードを追加
  3. 03コンポーネントのトークンとバリアントを統一
  4. 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 を参照してください。

コメント