jpskill.com
📦 その他 コミュニティ

web-styling-cva

Class Variance Authorityというツールを使い、コンポーネントのデザインパターンを型安全に定義し、様々なバリエーションを効率的に管理・適用することで、柔軟で一貫性のあるUIを構築するSkill。

📜 元の英語説明(参考)

Class Variance Authority - type-safe component variant styling with cva(), compound variants, and VariantProps

🇯🇵 日本人クリエイター向け解説

一言でいうと

Class Variance Authorityというツールを使い、コンポーネントのデザインパターンを型安全に定義し、様々なバリエーションを効率的に管理・適用することで、柔軟で一貫性のあるUIを構築するSkill。

※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。

⚡ おすすめ: コマンド1行でインストール(60秒)

下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。

🍎 Mac / 🐧 Linux
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o web-styling-cva.zip https://jpskill.com/download/10308.zip && unzip -o web-styling-cva.zip && rm web-styling-cva.zip
🪟 Windows (PowerShell)
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/10308.zip -OutFile "$d\web-styling-cva.zip"; Expand-Archive "$d\web-styling-cva.zip" -DestinationPath $d -Force; ri "$d\web-styling-cva.zip"

完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。

💾 手動でダウンロードしたい(コマンドが難しい人向け)
  1. 1. 下の青いボタンを押して web-styling-cva.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → web-styling-cva フォルダができる
  3. 3. そのフォルダを C:\Users\あなたの名前\.claude\skills\(Win)または ~/.claude/skills/(Mac)へ移動
  4. 4. Claude Code を再起動

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。

詳しい使い方ガイドを見る →
最終更新
2026-05-18
取得日時
2026-05-18
同梱ファイル
1

📖 Skill本文(日本語訳)

※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。

CVA (Class Variance Authority) パターン

クイックガイド: CVA を使用して、宣言的な API で型安全なコンポーネントのバリアントを定義します。基本クラス、バリアントグループ (size, intent, state)、複合条件のための複合バリアント、およびデフォルト値を定義します。VariantProps で型を抽出します。あらゆる CSS アプローチ (ユーティリティクラス、CSS modules、プレーン CSS) で動作します。常に defaultVariants を設定し、常にブール値のバリアントに対して true / false の両方を定義し、常に型抽出に VariantProps を使用します。


<critical_requirements>

重要: この Skill を使用する前に

すべてのコードは、CLAUDE.md のプロジェクト規約に従う必要があります (kebab-case, named exports, import ordering, import type, named constants)

(variants オブジェクトにすべてのバリアントオプションを定義する必要があります - cva の外部で条件付きクラスロジックを絶対に使用しないでください)

(VariantProps を使用して型を抽出する必要があります - バリアントプロップの型を手動で定義しないでください)

(初期状態には defaultVariants を使用する必要があります - デフォルトとして未定義のプロップに依存しないでください)

(複数条件のスタイルには compoundVariants を使用する必要があります - 結合された状態のために三項演算子をネストしないでください)

</critical_requirements>


自動検出: cva, class-variance-authority, VariantProps, variants, compoundVariants, defaultVariants, component variants, type-safe styling, cx

使用する場合:

  • 複数の視覚的なバリアント (size, intent, state) を持つコンポーネントを構築する場合
  • 型安全なプロップを持つデザインシステムコンポーネントを作成する場合
  • 複合条件 (例: "large primary" には特別なスタイルがある) を実装する場合
  • プロジェクトまたはフレームワーク間でバリアントスタイルを共有する場合

使用しない場合:

  • バリアントのないシンプルなコンポーネント (プレーンなクラスを使用するだけ)
  • パターンの再利用がない、一度限りのスタイリング
  • ランタイム値に基づく動的なスタイル (インラインスタイルまたは CSS 変数を使用する)
  • ビューポートに基づいて変化するレスポンシブスタイル (CSS メディアクエリを使用する)

カバーされる主要なパターン:

  • cva() を使用した基本的なバリアント定義
  • トグル状態のためのブール値バリアント
  • 複合条件のための複合バリアント
  • VariantProps を使用した型抽出
  • cx() および外部ユーティリティを使用したクラスのマージ
  • マルチパートコンポーネントパターン
  • バリアント定義の構成と拡張

詳細なリソース:

  • examples/core.md - 基本的なバリアント、ブール値の状態、デフォルト値、必須バリアント
  • examples/compound-variants.md - 複数条件のスタイル、配列構文、状態の組み合わせ
  • examples/composition.md - VariantProps, クラスのマージ、マルチパートコンポーネント、バリアントの拡張
  • reference.md - 意思決定フレームワーク、アンチパターン、クイックリファレンス

<philosophy>

哲学

CVA は、コンポーネントのバリアントを UI 状態の型システム として扱います。コンポーネント全体に条件付きクラスロジックを分散させる代わりに、CVA はバリアント定義を単一の型付き構成オブジェクトに集中させます。

コア原則:

  • 命令型よりも宣言型: 各バリアントがどのように見えるかを定義し、クラスを計算する方法を定義しない
  • 型安全なバリアント: TypeScript はコンパイル時に無効なバリアント値をキャッチする
  • フレームワークに依存しない: あらゆる UI フレームワークおよびあらゆる CSS アプローチで動作する
  • 構成に優しい: バリアントは組み合わせ、拡張、および構成できる
  • 信頼できる唯一の情報源: バリアント定義は 1 か所に存在し、型は派生する

手動の条件付きクラスよりも CVA を使用する理由:

// 悪い例: ロジックが分散し、型安全性がなく、保守が困難
function getButtonClasses(size: string, variant: string, disabled: boolean) {
  let classes = "btn";
  if (size === "sm") classes += " btn-sm";
  else if (size === "lg") classes += " btn-lg";
  if (variant === "primary") classes += " btn-primary";
  if (disabled) classes += " btn-disabled";
  return classes;
}

// 良い例: 宣言的、型安全、構成可能
const buttonVariants = cva("btn", {
  variants: {
    size: { sm: "btn-sm", lg: "btn-lg" },
    variant: { primary: "btn-primary" },
    disabled: { true: "btn-disabled" },
  },
});

</philosophy>


<patterns>

コアパターン

パターン 1: 基本的なバリアント定義

基本クラスとバリアントオプションを使用してコンポーネントスタイルを定義します。可読性のために配列構文を使用し、常に defaultVariants を設定します。

import { cva } from "class-variance-authority";

const buttonVariants = cva(
  ["font-semibold", "border", "rounded"], // 基本クラスを配列として
  {
    variants: {
      intent: {
        primary: ["bg-blue-600", "text-white"],
        secondary: ["bg-white", "text-gray-800"],
      },
      size: {
        sm: ["text-sm", "py-1", "px-2"],
        md: ["text-base", "py-2", "px-4"],
      },
    },
    defaultVariants: {
      intent: "primary",
      size: "md",
    },
  },
);

buttonVariants(); // デフォルト: primary + md
buttonVariants({ intent: "secondary" }); // secondary + md

重要なルール: 常に defaultVariants を指定します (そうしないと、プロップなしで呼び出すと不完全なクラスが返されます)。可読性のために、スペースで区切られた文字列ではなく配列を使用します。

完全なボタン、バッジ、およびアイコンボタンの例については、examples/core.md を参照してください。


パターン 2: ブール値バリアント

バイナリ状態には true / false キーを使用します。常に両側を定義します。

const inputVariants = cva(["border", "rounded", "px-3", "py-2"], {
  variants: {
    disabled: {
      false: ["bg-white", "cursor-text"], // 通常の状態
      true: ["bg-gray-100", "cursor-not-allowed"], // 無効の状態
    },
    error: {
      false: ["border-gray-300"],
      true: ["border-red-500"],
    },
  },
  defaultVariants: { disabled: false, error: false },
});

重要なルール: false のケースが欠落していると、通常の状態ではスタイルが適用されません。バリアントロジックが不完全になります。

loading, disabled, および error 状態でのブール値バリアントパターンについては、examples/core.md を参照してください。


パターン 3: 複合バリアント

特定のバリアントの組み合わせに特別なスタイルが必要な場合は、compoundVariants を使用します。配列構文は複数の値と一致します。



(原文がここで切り詰められています)
📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開

CVA (Class Variance Authority) Patterns

Quick Guide: Use CVA to define type-safe component variants with a declarative API. Define base classes, variant groups (size, intent, state), compound variants for combined conditions, and default values. Extract types with VariantProps. Works with any CSS approach (utility classes, CSS modules, plain CSS). Always set defaultVariants, always define both true/false for boolean variants, always use VariantProps for type extraction.


<critical_requirements>

CRITICAL: Before Using This Skill

All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering, import type, named constants)

(You MUST define all variant options in the variants object - NEVER use conditional class logic outside cva)

(You MUST use VariantProps to extract types - NEVER manually define variant prop types)

(You MUST use defaultVariants for initial state - NEVER rely on undefined props for defaults)

(You MUST use compoundVariants for multi-condition styles - NEVER nest ternaries for combined states)

</critical_requirements>


Auto-detection: cva, class-variance-authority, VariantProps, variants, compoundVariants, defaultVariants, component variants, type-safe styling, cx

When to use:

  • Building components with multiple visual variants (size, intent, state)
  • Creating design system components with type-safe props
  • Implementing compound conditions (e.g., "large primary" has special styles)
  • Sharing variant styling across projects or frameworks

When NOT to use:

  • Simple components with no variants (just use plain classes)
  • One-off styling without pattern reuse
  • Dynamic styles based on runtime values (use inline styles or CSS variables)
  • Responsive styles that change based on viewport (use CSS media queries)

Key patterns covered:

  • Basic variant definitions with cva()
  • Boolean variants for toggle states
  • Compound variants for combined conditions
  • Type extraction with VariantProps
  • Class merging with cx() and external utilities
  • Multi-part component patterns
  • Composing and extending variant definitions

Detailed Resources:


<philosophy>

Philosophy

CVA treats component variants as a type system for UI states. Instead of scattering conditional class logic throughout components, CVA centralizes variant definitions in a single, typed configuration object.

Core Principles:

  • Declarative over imperative: Define what each variant looks like, not how to compute classes
  • Type-safe variants: TypeScript catches invalid variant values at compile time
  • Framework-agnostic: Works with any UI framework and any CSS approach
  • Composition-friendly: Variants can be combined, extended, and composed
  • Single source of truth: Variant definitions live in one place, types are derived

Why CVA over manual conditional classes:

// BAD: scattered logic, no type safety, hard to maintain
function getButtonClasses(size: string, variant: string, disabled: boolean) {
  let classes = "btn";
  if (size === "sm") classes += " btn-sm";
  else if (size === "lg") classes += " btn-lg";
  if (variant === "primary") classes += " btn-primary";
  if (disabled) classes += " btn-disabled";
  return classes;
}

// GOOD: declarative, type-safe, composable
const buttonVariants = cva("btn", {
  variants: {
    size: { sm: "btn-sm", lg: "btn-lg" },
    variant: { primary: "btn-primary" },
    disabled: { true: "btn-disabled" },
  },
});

</philosophy>


<patterns>

Core Patterns

Pattern 1: Basic Variant Definition

Define component styles with base classes and variant options. Use array syntax for readability, always set defaultVariants.

import { cva } from "class-variance-authority";

const buttonVariants = cva(
  ["font-semibold", "border", "rounded"], // base classes as array
  {
    variants: {
      intent: {
        primary: ["bg-blue-600", "text-white"],
        secondary: ["bg-white", "text-gray-800"],
      },
      size: {
        sm: ["text-sm", "py-1", "px-2"],
        md: ["text-base", "py-2", "px-4"],
      },
    },
    defaultVariants: {
      intent: "primary",
      size: "md",
    },
  },
);

buttonVariants(); // defaults: primary + md
buttonVariants({ intent: "secondary" }); // secondary + md

Key rules: always provide defaultVariants (calling without props returns incomplete classes otherwise), use arrays over space-separated strings for readability.

See examples/core.md for complete button, badge, and icon button examples.


Pattern 2: Boolean Variants

Use true/false keys for binary states. Always define both sides.

const inputVariants = cva(["border", "rounded", "px-3", "py-2"], {
  variants: {
    disabled: {
      false: ["bg-white", "cursor-text"], // normal state
      true: ["bg-gray-100", "cursor-not-allowed"], // disabled state
    },
    error: {
      false: ["border-gray-300"],
      true: ["border-red-500"],
    },
  },
  defaultVariants: { disabled: false, error: false },
});

Key rule: missing false case means no styles applied in normal state -- variant logic becomes incomplete.

See examples/core.md for boolean variant patterns with loading, disabled, and error states.


Pattern 3: Compound Variants

Use compoundVariants when specific variant combinations need special styles. Array syntax matches multiple values.

const buttonVariants = cva(["font-semibold", "rounded"], {
  variants: {
    intent: {
      primary: ["bg-blue-600", "text-white"],
      secondary: ["bg-white", "text-gray-800"],
    },
    disabled: {
      false: null,
      true: ["opacity-50", "cursor-not-allowed"],
    },
  },
  compoundVariants: [
    // Hover only when enabled
    { intent: "primary", disabled: false, class: ["hover:bg-blue-700"] },
    { intent: "secondary", disabled: false, class: ["hover:bg-gray-100"] },
    // Array syntax: matches multiple values
    {
      intent: ["primary", "secondary"],
      disabled: true,
      class: ["pointer-events-none"],
    },
  ],
  defaultVariants: { intent: "primary", disabled: false },
});

Key rules: compound variants express "when X AND Y, also apply Z", array syntax avoids duplicating rules across similar variants.

See examples/compound-variants.md for hover states, loading overrides, state matrices, and multi-part compounds.


Pattern 4: Type Extraction with VariantProps

Always use VariantProps to extract types from cva definitions -- never manually define variant types.

import { cva, type VariantProps } from "class-variance-authority";

const cardVariants = cva(["rounded-lg", "border"], {
  variants: {
    elevation: { flat: ["shadow-none"], raised: ["shadow-md"] },
    padding: { none: ["p-0"], sm: ["p-2"], md: ["p-4"] },
  },
  defaultVariants: { elevation: "flat", padding: "md" },
});

// Extract types -- always in sync with cva definition
type CardVariants = VariantProps<typeof cardVariants>;
// { elevation?: "flat" | "raised" | null; padding?: "none" | "sm" | "md" | null }

interface CardProps extends CardVariants {
  children: unknown;
  className?: string;
}

Key rule: manual types drift when you add/remove variants. VariantProps is always in sync.

To make specific variants required (no default):

type BadgeProps = Omit<BadgeVariants, "color"> &
  Required<Pick<BadgeVariants, "color">>;

See examples/composition.md for complete type extraction and required variant patterns.


Pattern 5: Class Merging with cx()

Use cx() (built-in, alias for clsx) for class concatenation. Use an external merge utility for class conflict resolution.

import { cva, cx } from "class-variance-authority";

// cx() concatenates and filters falsy values
cx(buttonVariants({ intent: "primary" }), highlighted && "ring-2", className);

// For conflict resolution (e.g., caller overriding variant padding),
// use a class-merging utility wrapper
function button(variants: ButtonVariants, className?: string): string {
  return cn(buttonVariants(variants), className); // cn() resolves conflicts
}

See examples/composition.md for class merging patterns and conflict resolution.


Pattern 6: Multi-Part Components

Define separate cva for each styled part of a component (label, input, helper text). Share variant values for visual consistency.

const formFieldVariants = {
  label: cva(["block", "font-medium"], {
    variants: { size: { sm: ["text-sm"], md: ["text-base"] } },
    defaultVariants: { size: "md" },
  }),
  input: cva(["w-full", "border", "rounded"], {
    variants: {
      size: { sm: ["text-sm", "px-2"], md: ["text-base", "px-3"] },
      error: { false: ["border-gray-300"], true: ["border-red-500"] },
    },
    defaultVariants: { size: "md", error: false },
  }),
  helper: cva(["mt-1"], {
    variants: {
      size: { sm: ["text-xs"], md: ["text-sm"] },
      error: { false: ["text-gray-500"], true: ["text-red-600"] },
    },
    defaultVariants: { size: "md", error: false },
  }),
};

See examples/composition.md for multi-part and extending/composing variant patterns.


Pattern 7: Composing and Extending Variants

Combine multiple cva definitions with cx() for shared base + specialized variants.

const interactiveVariants = cva(["transition-colors", "focus:ring-2"], {
  variants: { focusRing: { blue: ["focus:ring-blue-500"] } },
  defaultVariants: { focusRing: "blue" },
});

const buttonVariants = cva(["font-semibold", "rounded"], {
  variants: { intent: { primary: ["bg-blue-600"] } },
  defaultVariants: { intent: "primary" },
});

// Compose with cx()
type ButtonProps = VariantProps<typeof interactiveVariants> &
  VariantProps<typeof buttonVariants>;

function button(props: ButtonProps): string {
  return cx(
    interactiveVariants({ focusRing: props.focusRing }),
    buttonVariants({ intent: props.intent }),
  );
}

See examples/composition.md for composition and extension patterns.

</patterns>


<red_flags>

RED FLAGS

High Priority Issues:

  • Manual variant type definitions -- Types drift from cva definition, defeating type safety. Always use VariantProps<typeof variants>.
  • Conditional class logic outside cva -- Defeats purpose of centralized variant definitions. Add the condition as a variant.
  • Missing defaultVariants -- Calling without props returns incomplete classes. Always set defaults.
  • Nested ternaries for combined states -- Use compoundVariants instead.
  • Only defining true for boolean variants -- false case should provide base/normal styles.

Medium Priority Issues:

  • Space-separated class strings instead of arrays -- arrays are more readable and maintainable
  • Not using cx() for class merging -- manual concatenation is error-prone
  • Duplicating variant styles across components -- compose from shared base variants
  • Putting complex responsive logic in cva -- keep cva simple, handle responsiveness in CSS

Gotchas & Edge Cases:

  • VariantProps makes all variants optional (nullable) -- use TypeScript utilities (Required<Pick<>>) to make specific ones required
  • compoundVariants are applied AFTER regular variants -- order matters for class specificity
  • Empty variant values (null or empty string) are valid -- useful for "no additional styles" case
  • Base classes are always applied -- you cannot conditionally remove them via variants
  • Both class and className work in compoundVariants config -- pick one and be consistent (class in non-React contexts, className if you prefer React conventions)
  • Don't forget import type for VariantProps: import { cva, type VariantProps }

</red_flags>


<critical_reminders>

CRITICAL REMINDERS

All code must follow project conventions in CLAUDE.md

(You MUST define all variant options in the variants object - NEVER use conditional class logic outside cva)

(You MUST use VariantProps to extract types - NEVER manually define variant prop types)

(You MUST use defaultVariants for initial state - NEVER rely on undefined props for defaults)

(You MUST use compoundVariants for multi-condition styles - NEVER nest ternaries for combined states)

Failure to follow these rules will break type safety, create inconsistent styling, and defeat the purpose of using CVA.

</critical_reminders>