ツールガイド

shadcn/ui カラーカスタマイズ:CSSカスタムプロパティでテーマ設定

3分で読める

shadcn/uiは、React開発者の世代にとってデフォルトのコンポーネントライブラリになっています。プレコンパイルされたバンドルを配信する従来のライブラリとは異なり、shadcn/uiコンポーネントはプロジェクトに直接コピーされます—つまり、色のすべての決定を含むコードを所有しています。ライブラリは、テーマを簡単にする思慮深いHSLベースのCSSカスタムプロパティシステムを搭載されていますが、その完全な力は、アーキテクチャの背後にある理由を理解した場合に排他的にロック解除されます。このガイドは、カスタムテーマ作成、ダークモード統合、およびTailwind CSS設定との同期を通じて、基礎からカラーシステムを歩きます。

shadcn/ui カラーアーキテクチャの概要

shadcn/uiプロジェクトを初期化する場合(npx shadcn@latest init)、CLIはグローバルスタイルシートに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%;
    --popover: 0 0% 100%;
    --popover-foreground: 222.2 84% 4.9%;
    --primary: 222.2 47.4% 11.2%;
    --primary-foreground: 210 40% 98%;
    --secondary: 210 40% 96.1%;
    --secondary-foreground: 222.2 47.4% 11.2%;
    --muted: 210 40% 96.1%;
    --muted-foreground: 215.4 16.3% 46.9%;
    --accent: 210 40% 96.1%;
    --accent-foreground: 222.2 47.4% 11.2%;
    --destructive: 0 84.2% 60.2%;
    --destructive-foreground: 210 40% 98%;
    --border: 214.3 31.8% 91.4%;
    --input: 214.3 31.8% 91.4%;
    --ring: 222.2 84% 4.9%;
    --radius: 0.5rem;
  }

  .dark {
    --background: 222.2 84% 4.9%;
    --foreground: 210 40% 98%;
    /* ... ダークモード オーバーライド ... */
  }
}

2つの構造的な決定はそれ以外すべてを形作ります:

  1. セマンティック命名:変数は役割(--primary--destructive--muted)にちなんで命名されており、視覚的値(--blue-600--red-500)ではなく。--primaryを使用するコンポーネントは、プライマリが青、紫、またはオレンジかどうかを知っていたり気にしたりしません。

  2. チャネルのみのHSL値:変数はhsl()関数呼び出し(hsl(222.2, 84%, 11.2%))ではなく、完全な値ではなく、HSLチャネルのみを格納します。これは意図的です:コンポーネントはフォーム変数として変数を使用しますhsl(var(--primary))、これにより透明度修飾子が自然に構成されます:hsl(var(--primary) / 0.5)

HSL CSSカスタムプロパティシステム

HSLチャネルを使用する理由

カラーを222.2 84% 11.2%(チャネルのみ)として保存することはhsl(222.2, 84%, 11.2%)(完全な値)ではなく、標準CSSカスタムプロパティは優雅にサポートしないパターンを有効にします:

/* 標準CSS—アルファ構成には別の変数が必要 */
:root {
  --primary: hsl(222.2, 84%, 11.2%);
  --primary-alpha-50: hsl(222.2, 84%, 11.2%, 0.5); /* 複製する必要があります */
}

/* shadcn/ui パターン—アルファが複製なしで構成 */
:root {
  --primary: 222.2 47.4% 11.2%;
}

.element {
  background: hsl(var(--primary));          /* 不透明 */
  border-color: hsl(var(--primary) / 0.2);  /* 20%の不透明度 */
}

チャネルのみの値はhsl()内でのみ意味があるフラグメントです。これは変数をどこで使用できるかを制限しますが、その制約は機能です:これは、コードベース全体で一貫した不透明度構成を強制します。

完全な変数マップ

shadcn/uiは16色の役割を定義します(--radiusを除く):

変数 目的 一般的なコンシューマー
--background ページ背景 <body>, ページラッパー
--foreground 本文テキスト <p>, <span>
--card カード表面 <Card>
--card-foreground カード上のテキスト カードコンテンツ
--popover フローティングサーフェス <Popover>, <DropdownMenu>
--popover-foreground ポップオーバー内のテキスト ポップオーバーコンテンツ
--primary プライマリインタラクティブ <Button variant="default">
--primary-foreground プライマリ上のテキスト ボタンラベル
--secondary セカンダリサーフェス <Button variant="secondary">
--secondary-foreground セカンダリ上のテキスト セカンダリボタンラベル
--muted 抑制された背景 <Badge>, 無効な状態
--muted-foreground 抑制されたテキスト メタデータ、プレースホルダー
--accent ホバー/選択された状態 メニューのアイテムホバー
--accent-foreground アクセント上のテキスト ホバーされたアイテムラベル
--destructive 危険/エラー <Button variant="destructive">
--destructive-foreground 破壊的なテキスト 破壊的なボタンラベル
--border デフォルトボーダー 仕切り、入力アウトライン
--input 入力フィールドボーダー <Input>, <Select>
--ring フォーカスリング キーボードフォーカスインジケーター

カスタムテーマの作成

アプローチ1:公式テーマビルダーを使用

shadcn/uiウェブサイトはui.shadcn.com/themesでテーマビルダーを配信します。ベースカラーの色合いとグレースケールを選択すると、ライトモードとダークモードの両方のためにCSS全体を生成します。これは単純なブランドカラー変更のための最速のアプローチです。

アプローチ2:ブランドカラーから構築

完全な制御については、ブランドのプライマリカラーから始まり、変数セット全体を導き出します。

ブランドプライマリが#6D28D9(深い紫)であると仮定します。Color Converterを使用してHSLに変換します:約hsl(263, 70%, 50%)。必要なHSLチャネルは263 70% 50%です。

そこから、完全なパレットを導き出します:

@layer base {
  :root {
    /* ブランド由来の変数 */
    --background: 0 0% 100%;
    --foreground: 263 20% 8%;           /* 紫色のヒント付きほぼ黒 */

    --card: 0 0% 100%;
    --card-foreground: 263 20% 8%;

    --popover: 0 0% 100%;
    --popover-foreground: 263 20% 8%;

    /* プライマリ:あなたのブランドの紫 */
    --primary: 263 70% 50%;
    --primary-foreground: 263 10% 98%; /* ほぼ白 */

    /* セカンダリ:軽い紫のティント */
    --secondary: 263 30% 94%;
    --secondary-foreground: 263 70% 30%;

    /* ミューテッド:飽和度低減化セカンダリ */
    --muted: 260 20% 95%;
    --muted-foreground: 260 15% 50%;

    /* アクセント:ホバー状態(セカンダリより少し飽和) */
    --accent: 263 40% 90%;
    --accent-foreground: 263 70% 30%;

    /* 破壊的:標準的な赤 */
    --destructive: 0 84% 60%;
    --destructive-foreground: 0 0% 98%;

    /* ボーダーとコントロール */
    --border: 263 20% 88%;
    --input:  263 20% 88%;
    --ring:   263 70% 50%;   /* プライマリと一致—フォーカスリングはブランドをエコーします */

    --radius: 0.5rem;
  }
}

各役割を導き出すためのヒント

--primary-foreground:プライマリボタン上の前景は、WCAG AA(4.5:1)に対して--primaryを満たす必要があります。深く飽和した色の場合、ほぼ白(98%の明度)は通常機能します。Contrast Checkerで確認してください。

--secondary--muted:これらは通常--primaryと同じ色合いですが、飽和度20~35%に低下し、明度は90%以上にプッシュされます。プライマリと競合することなく視覚的凝集を作成します。

--accent:これはインタラクティブなリストアイテム(コンボボックスオプション、ドロップダウンメニューアイテム)のホバー背景です。目に見えますが、不安定ではないはずです—ほとんどの色合いについては、88~92%の明度と中程度の飽和度が機能します。

--ring:キーボードフォーカスリングはプライマリカラーと正確に一致する必要があります。したがって、キーボードユーザーはマウスユーザーがボタンの背景に見えるのと同じブランドカラーを見ます。アクセシビリティはリングが隣接する背景に対して可視である必要があります:3:1コントラスト。

--destructive:ブランドが赤でない限り、この破壊的なアクションが赤であるという深い条件付きの期待をユーザーが持っているため、これを変更しないでください。0~10°色合い範囲に保持します。

ダークモード統合

shadcn/uiは、ダークモードセレクターとして([data-theme="dark"]ではなく)<html>.darkクラスを使用します。これはTailwind CSSのdarkMode: 'class'設定と一致します。

ダークモード値を定義

ダークモードブロックは同じ構造に従いますが、明度関係を反転します:

.dark {
  --background: 263 25% 7%;            /* 非常に暗い紫灰色 */
  --foreground: 263 10% 92%;

  --card: 263 25% 10%;
  --card-foreground: 263 10% 92%;

  --popover: 263 25% 10%;
  --popover-foreground: 263 10% 92%;

  /* プライマリ:同じ色合い、ダーク背景のより軽い色合い */
  --primary: 263 70% 70%;             /* ライトで50%、ダークで70%の明度 */
  --primary-foreground: 263 30% 8%;   /* ライトボタン上のダークテキスト */

  --secondary: 263 25% 18%;
  --secondary-foreground: 263 70% 85%;

  --muted: 263 20% 15%;
  --muted-foreground: 263 15% 55%;

  --accent: 263 30% 22%;
  --accent-foreground: 263 70% 85%;

  --destructive: 0 62% 50%;
  --destructive-foreground: 0 0% 98%;

  --border: 263 20% 20%;
  --input:  263 20% 20%;
  --ring:   263 70% 70%;              /* プライマリと一致 */
}

主な調整は--primaryです:ライトモードでは50%の明度(飽和、白背景と対比)でした。ダークモードではほぼ黒の--backgroundに対して、70%の明度—より軽く、まだ飽和しています。ライトモードで対比を通すプライマリカラーは、この調整なしでダークモードでほぼ確実に失敗します。

Shade Generatorを使用して、すべての明度レベル全体で色合いをすばやく探索します。#6D28D9を入力し、ダークモードプライマリシェードの適切な200~400範囲を検査します。

JavaScript トグル統合

shadcn/uiのダークモードは、<html>.darkクラスを追加/削除する任意のメカニズムで機能します。Next.jsプロジェクトでの最も一般的な統合はnext-themesを使用します:

npm install next-themes
// app/providers.tsx
import { ThemeProvider } from 'next-themes'

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <ThemeProvider
      attribute="class"          // <html>にダークな'クラスを追加
      defaultTheme="system"      // OSの設定を尊重
      enableSystem               // システム設定の検出を許可
    >
      {children}
    </ThemeProvider>
  )
}
// components/theme-toggle.tsx
import { useTheme } from 'next-themes'

export function ThemeToggle() {
  const { theme, setTheme } = useTheme()
  return (
    <button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
      テーマを切り替え
    </button>
  )
}

next-themesなしVite/プレーンReactプロジェクトの場合、localStorageでクラスを直接管理します:

// utils/theme.ts
export function getTheme(): 'light' | 'dark' {
  const stored = localStorage.getItem('theme') as 'light' | 'dark' | null
  if (stored) return stored
  return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
}

export function applyTheme(theme: 'light' | 'dark') {
  document.documentElement.classList.toggle('dark', theme === 'dark')
  localStorage.setItem('theme', theme)
}

最初のレンダリング前に<head>applyTheme(getTheme())を呼び出してフラッシュを防ぐ。

Shadcn テーマをTailwind Configと同期

shadcn/uiコンポーネントはTailwindユーティリティクラスを使用します—bg-backgroundtext-foregroundborder-borderなど—これらのユーティリティは、shadcn/uiがtailwind.config.jsまたはtailwind.config.tsで設定されているため、排他的にのみ存在します:

// tailwind.config.ts — shadcn initで生成
import type { Config } from 'tailwindcss'

const config: Config = {
  darkMode: ['class'],
  content: ['./app/**/*.{ts,tsx}', './components/**/*.{ts,tsx}'],
  theme: {
    extend: {
      colors: {
        background:    'hsl(var(--background))',
        foreground:    'hsl(var(--foreground))',
        card: {
          DEFAULT:     'hsl(var(--card))',
          foreground:  'hsl(var(--card-foreground))',
        },
        primary: {
          DEFAULT:     'hsl(var(--primary))',
          foreground:  'hsl(var(--primary-foreground))',
        },
        // ... など
        destructive: {
          DEFAULT:     'hsl(var(--destructive))',
          foreground:  'hsl(var(--destructive-foreground))',
        },
        border:   'hsl(var(--border))',
        input:    'hsl(var(--input))',
        ring:     'hsl(var(--ring))',
      },
    },
  },
}

この設定はTailwindのユーティリティクラスシステムをCSSカスタムプロパティルックアップにリンクします。bg-primarybackground-color: hsl(var(--primary))を生成し、これは次にCSSで--primaryに設定されているHSLチャネルを読みます。完全な色はTailwindの生成されたCSSにハードコードされていません—実行時に変数から解決されます。

ブランドスケール色をセマンティックトークンと共に追加

一般的な要件は、セマンティックbg-primaryユーティリティを持ちながら、コンポーネントでブランドの完全なシェードスケール(例えばtext-brand-700)を使用することです。これらは紛争なく共存します:

// tailwind.config.ts
const config: Config = {
  theme: {
    extend: {
      colors: {
        // セマンティックトークン(shadcn/ui)
        background: 'hsl(var(--background))',
        foreground: 'hsl(var(--foreground))',
        primary: {
          DEFAULT: 'hsl(var(--primary))',
          foreground: 'hsl(var(--primary-foreground))',
        },
        // スケールトークン(ブランド)
        brand: {
          50:  '#F5F3FF',
          100: '#EDE9FE',
          200: '#DDD6FE',
          300: '#C4B5FD',
          400: '#A78BFA',
          500: '#8B5CF6',
          600: '#7C3AED',
          700: '#6D28D9',
          800: '#5B21B6',
          900: '#4C1D95',
          950: '#2E1065',
        },
      },
    },
  },
}

この設定では、bg-primary(動的、テーマ対応)とbg-brand-700#6D28D9、静的)の両方を取得します。コンポーネントのデフォルトにセマンティックトークンを使用し、現在のテーマに関係なく特定のシェードが必要な場所でスケールトークンを使用します。

Tailwind v4互換性

Tailwind v4でshadcn/uiを採用している場合、設定はCSSの@themeに移動します:

@import "tailwindcss";

@theme {
  --color-background:        hsl(var(--background));
  --color-foreground:        hsl(var(--foreground));
  --color-primary:           hsl(var(--primary));
  --color-primary-foreground: hsl(var(--primary-foreground));
  /* ... など */
}

ダブル変数間接参照に注意してください:--color-primary(TailwindのCSSカスタムプロパティ命名規則)はhsl(var(--primary))(shadcn/uiの変数)を参照します。これは少し厄介ですが、完全に機能し、v4で両方のシステムをブリッジするために必要です。

重要なテイクアウト

  • shadcn/uiのカラーシステムは、完全なhsl()値ではなく、HSLチャネルのみのCSSカスタムプロパティ(例えば222.2 47.4% 11.2%)を使用します。これにより、コンポーザブルな不透明度が有効になります:hsl(var(--primary) / 0.5)
  • 16セマンティック色の役割(--background--primary--destructiveなど)はマークアップ固有の値からコンポーネントを切り離します—システムをテーマ設定することで値を変更する、コンポーネントコードを変更しないます。
  • カスタムテーマの場合、ブランドのプライマリヘックスから始め、Color ConverterでHSLに変換し、飽和度を減らして明度を調整することで、セカンダリとミューテッド役割を導き出します。
  • ダークモードは--primaryへの特別な注意が必要です:白背景で機能する50%の明度の色合いは、暗い背景に対して対比を維持するために、65~75%の明度に移動する必要があります。
  • Tailwind設定はユーティリティクラス(bg-primarytext-foreground)をCSSカスタムプロパティルックアップ(hsl(var(--primary)))にマップし、2つのシステムをリンクします。スケールベースの色は、同じ設定内のセマンティックトークンと共存できます。
  • Shade Generatorを使用して、正確なシェード使用ケースの完全なブランドスケールを作成し、Palette Generatorを使用して、相補的なセカンダリカラーの選択肢を探索します。

関連カラー

関連ブランド

関連ツール