color-palette について
1 色のブランドカラーから、セマンティックトークンとライト・ダークモードの設定を含む Tailwind v4 の配色システムを作るスキルです。コントラスト確認とアクセシビリティ向けの調整も扱います。
主な機能
- 0111 段階のカラースケール生成
- 02シェードをセマンティックトークンに対応付け
- 03ライト・ダークモードの色ペア設定
- 04WCAG コントラスト比の確認
- 05Tailwind v4 CSS の出力
活用シーン
- 01ブランドカラーからテーマを作成
- 02背景と前景のトークンをペアで設定
- 03両モードでテキストと UI のコントラストを確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ jezweb/ claude-skills --skill color-paletteインストール後、Claude Code で/color-paletteを入力して読み込まれたか確認します。
同梱ファイルassets/tailwind-colors.css
詳細
カラーパレットジェネレーター
1つのブランドカラーの16進数コードから、アクセシビリティに配慮した完全なカラーシステムを生成します。プロジェクトにそのまま貼り付けられる Tailwind v4 CSS を出力します。
ワークフロー
ステップ 1:ブランドカラーの16進数コードを取得する
メインのブランドカラーを尋ねます。#0D9488 のような16進数コード1つで十分です。
ステップ 2:11段階のスケールを生成する
16進数コードを HSL に変換し、色相を固定したまま明度を変えて各シェードを生成します。
16進数コードから HSL への変換
function hexToHSL(hex) {
hex = hex.replace(/^#/, '');
const r = parseInt(hex.substring(0, 2), 16) / 255;
const g = parseInt(hex.substring(2, 4), 16) / 255;
const b = parseInt(hex.substring(4, 6), 16) / 255;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
const diff = max - min;
let l = (max + min) / 2;
let s = 0;
if (diff !== 0) {
s = l > 0.5 ? diff / (2 - max - min) : diff / (max + min);
}
let h = 0;
if (diff !== 0) {
if (max === r) h = ((g - b) / diff + (g < b ? 6 : 0)) / 6;
else if (max === g) h = ((b - r) / diff + 2) / 6;
else h = ((r - g) / diff + 4) / 6;
}
return { h: Math.round(h * 360), s: Math.round(s * 100), l: Math.round(l * 100) };
}
明度と彩度の値
| シェード | 明度 | 彩度の倍率 | 用途 |
|---|---|---|---|
| 50 | 97% | 0.80 | 控えめな背景 |
| 100 | 94% | 0.80 | ホバー状態 |
| 200 | 87% | 0.85 | 境界線、区切り線 |
| 300 | 75% | 0.90 | 無効状態 |
| 400 | 62% | 0.95 | プレースホルダーテキスト |
| 500 | 48% | 1.00 | ブランドカラーの基準 |
| 600 | 40% | 1.00 | 主要なアクション(多くの場合、ブランドカラー) |
| 700 | 33% | 1.00 | 主要な要素のホバー状態 |
| 800 | 27% | 1.00 | アクティブ状態 |
| 900 | 20% | 1.00 | 明るい背景上のテキスト |
| 950 | 10% | 1.00 | 最も暗いアクセント |
明るいシェード(50~200)は彩度を15~20%、300~400は5~10%下げて、パステルカラーが鮮やかになりすぎないようにします。500~950は彩度を最大に保ちます。
スケール全体の生成
function generateShadeScale(brandHex) {
const { h, s } = hexToHSL(brandHex);
const shades = {
50: { l: 97, sMul: 0.8 }, 100: { l: 94, sMul: 0.8 },
200: { l: 87, sMul: 0.85 }, 300: { l: 75, sMul: 0.9 },
400: { l: 62, sMul: 0.95 }, 500: { l: 48, sMul: 1.0 },
600: { l: 40, sMul: 1.0 }, 700: { l: 33, sMul: 1.0 },
800: { l: 27, sMul: 1.0 }, 900: { l: 20, sMul: 1.0 },
950: { l: 10, sMul: 1.0 }
};
const result = {};
for (const [shade, { l, sMul }] of Object.entries(shades)) {
result[shade] = `hsl(${h}, ${Math.round(s * sMul)}%, ${l}%)`;
}
return result;
}
HSL から16進数コードへの変換
function hslToHex(h, s, l) {
s = s / 100; l = l / 100;
const c = (1 - Math.abs(2 * l - 1)) * s;
const x = c * (1 - Math.abs((h / 60) % 2 - 1));
const m = l - c / 2;
let r = 0, g = 0, b = 0;
if (h < 60) { r = c; g = x; }
else if (h < 120) { r = x; g = c; }
else if (h < 180) { g = c; b = x; }
else if (h < 240) { g = x; b = c; }
else if (h < 300) { r = x; b = c; }
else { r = c; b = x; }
r = Math.round((r + m) * 255);
g = Math.round((g + m) * 255);
b = Math.round((b + m) * 255);
return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`.toUpperCase();
}
検証
生成されたシェードが同じ色の系統に見え、滑らかに変化していることを確認します。明るいシェード(50~300)は背景に、暗いシェード(700~950)はテキストに使用できます。ブランドカラーは500~700で認識できる状態にします。
ステップ 3:セマンティックトークンを割り当てる
背景トークンには必ず対応する前景トークンを設定します。対応するトークンなしで背景を使用しないでください。ダークモードが正しく機能しなくなります。
ライトモードのトークン
| トークン | シェード | 用途 |
|---|---|---|
background | 白 | ページの背景 |
foreground | 950 | 本文テキスト |
card | 白 | カードの背景 |
card-foreground | 900 | カードのテキスト |
popover | 白 | ドロップダウン/ツールチップの背景 |
popover-foreground | 950 | ドロップダウンのテキスト |
primary | 600 | 主要なボタン、リンク |
primary-foreground | 白 | 主要なボタン上のテキスト |
secondary | 100 | セカンダリボタン |
secondary-foreground | 900 | セカンダリボタン上のテキスト |
muted | 50 | 無効状態の背景、控えめなセクション |
muted-foreground | 600 | 控えめなテキスト、キャプション |
accent | 100 | ホバー状態、控えめな強調表示 |
accent-foreground | 900 | アクセント背景上のテキスト |
destructive | red-600 | 削除ボタン、エラー |
destructive-foreground | 白 | 削除ボタン上のテキスト |
border | 200 | 入力欄の境界線、区切り線 |
input | 200 | 入力フィールドの境界線 |
ring | 600 | フォーカスリング |
ダークモードのトークン
| トークン | シェード | 用途 |
|---|---|---|
background | 950 | ページの背景 |
foreground | 50 | 本文テキスト |
card | 900 | カードの背景 |
card-foreground | 50 | カードのテキスト |
popover | 900 | ドロップダウンの背景 |
popover-foreground | 50 | ドロップダウンのテキスト |
primary | 500 | 主要なボタン(ダークモードでは明るめ) |
primary-foreground | 白 | 主要なボタン上のテキスト |
secondary | 800 | セカンダリボタン |
secondary-foreground | 50 | セカンダリボタン上のテキスト |
muted | 800 | 無効状態の背景 |
muted-foreground | 400 | 控えめなテキスト |
accent | 800 | ホバー状態 |
accent-foreground | 50 | アクセント背景上のテキスト |
destructive | red-500 | 削除ボタン(明るめ) |
destructive-foreground | 白 | 削除ボタン上のテキスト |
border | 800 | 境界線 |
input | 800 | 入力欄の境界線 |
ring | 500 | フォーカスリング |
ダークモードの反転パターン
ダークモードでは、色相と彩度を保ちながら明度を反転させます。両端の値を入れ替え(50 は 950 に、950 は 50 に)、中間の値は維持します(500 は 500 付近のままです)。
| ライトのシェード | ダークの相当色 | 役割 |
|---|---|---|
| 50 | 950 | 背景 |
| 100 | 900 | 控えめな背景 |
| 200 | 800 | ボーダー |
| 500 | 500(やや明るく) | ブランドの基準色 |
| 600 | 400 | 主要なアクション |
| 950 | 50 | テキストの色 |
ダークモードの主な原則:
- プライマリーにはシェード 600 ではなく 500 を使います。暗い背景でも見やすいよう、より明るい色にします
- テキストには純白の
#FFFFFFではなく、シェード 50(オフホワイト)を使います。目に優しい色です - ボーダーは背景より約 10~15%明るくします(例:背景が 950 の場合、ボーダーは 800)
- 高いレイヤーほど明るい色にします(ライトモードのシャドウとは逆です)
- 背景を変更するときは、必ず前景色も更新します
ステップ 4:コントラストを確認する
WCAG の最小コントラスト比
| コンテンツの種類 | AA | AAA |
|---|---|---|
| 通常のテキスト(<18px または <14px の太字) | 4.5:1 | 7:1 |
| 大きなテキスト(>=18px または >=14px の太字) | 3:1 | 4.5:1 |
| UI コンポーネント(ボタン、ボーダー) | 3:1 | 定義なし |
| グラフィック要素(アイコン、チャート) | 3:1 | 定義なし |
ほとんどのプロジェクトでは AA を目標とし、高いアクセシビリティが必要な場合(政府、医療など)は AAA を目標にします。
輝度とコントラストの計算式
function getLuminance(hex) {
hex = hex.replace(/^#/, '');
const r = parseInt(hex.substring(0, 2), 16) / 255;
const g = parseInt(hex.substring(2, 4), 16) / 255;
const b = parseInt(hex.substring(4, 6), 16) / 255;
const rsRGB = r <= 0.03928 ? r / 12.92 : Math.pow((r + 0.055) / 1.055, 2.4);
const gsRGB = g <= 0.03928 ? g / 12.92 : Math.pow((g + 0.055) / 1.055, 2.4);
const bsRGB = b <= 0.03928 ? b / 12.92 : Math.pow((b + 0.055) / 1.055, 2.4);
return 0.2126 * rsRGB + 0.7152 * gsRGB + 0.0722 * bsRGB;
}
function getContrastRatio(hex1, hex2) {
const lum1 = getLuminance(hex1);
const lum2 = getLuminance(hex2);
const lighter = Math.max(lum1, lum2);
const darker = Math.min(lum1, lum2);
return (lighter + 0.05) / (darker + 0.05);
}
確認用クイック表 -- ライトモード
| 前景色 | 背景色 | 比率 | 合格? | 用途 |
|---|---|---|---|---|
| 950 | white | 18.5:1 | AAA | 本文テキスト |
| 900 | white | 14.2:1 | AAA | カードのテキスト |
| 700 | white | 8.1:1 | AAA | テキスト |
| 600 | white | 5.7:1 | AA | テキスト、ボタン |
| 500 | white | 3.9:1 | 不合格 | テキストには明るすぎます |
| white | 600 | 5.7:1 | AA | ボタンのテキスト |
| white | 700 | 8.1:1 | AAA | ボタンのテキスト |
| 600 | 50 | 5.4:1 | AA | 控えめなセクションのテキスト |
確認用クイック表 -- ダークモード
| 前景色 | 背景色 | 比率 | 合格? | 用途 |
|---|---|---|---|---|
| 50 | 950 | 18.5:1 | AAA | 本文テキスト |
| 50 | 900 | 14.2:1 | AAA | カードのテキスト |
| 400 | 950 | 8.2:1 | AAA | 控えめなテキスト |
| 400 | 900 | 6.3:1 | AA | 控えめなテキスト |
| white | 600 | 5.7:1 | AA | ボタンのテキスト |
経験則:テキストでは、前景色と背景色の明度差を 50%以上にすることを目指します。
確認すべき必須の組み合わせ
- 本文テキスト:背景上の前景色(ライト:白地に 950 = 18.5:1、ダーク:950 地に 50 = 18.5:1)
- プライマリーボタン:プライマリー上のプライマリー前景色(ライト:600 地に white = 5.7:1、ダーク:500 地に white = 3.9:1 -- 境界線上)
- 控えめなテキスト:控えめな色の上の控えめな前景色(ライト:50 地に 600 = 5.4:1、ダーク:800 地に 400 = 4.1:1 -- 不合格の可能性あり)
- カードのテキスト:カード上のカード前景色(ライト:白地に 900 = 14.2:1、ダーク:900 地に 50 = 14.2:1)
よくあるコントラスト不足の修正
primary-500 上の白は不合格(3.9:1):代わりに primary-600(5.7:1)を使うか、ボタン上に暗いテキストを使います。
ダークモードの控えめなテキストが不合格(800 上の 400 = 4.1:1):900 上の 300(6.8:1)を使います。
リンクが見づらい(白地に 500 = 3.9:1):primary-700(8.1:1)を使うか、下線を追加します。
ステップ 5:Tailwind v4 CSS を出力する
@import "tailwindcss";
@theme {
/* Shade scale */
--color-primary-50: #F0FDFA;
--color-primary-100: #CCFBF1;
--color-primary-200: #99F6E4;
--color-primary-300: #5EEAD4;
--color-primary-400: #2DD4BF;
--color-primary-500: #14B8A6;
--color-primary-600: #0D9488;
--color-primary-700: #0F766E;
--color-primary-800: #115E59;
--color-primary-900: #134E4A;
--color-primary-950: #042F2E;
/* Light mode semantic tokens */
--color-background: #FFFFFF;
--color-foreground: var(--color-primary-950);
--color-card: #FFFFFF;
--color-card-foreground: var(--color-primary-900);
--color-popover: #FFFFFF;
--color-popover-foreground: var(--color-primary-950);
--color-primary: var(--color-primary-600);
--color-primary-foreground: #FFFFFF;
--color-secondary: var(--color-primary-100);
--color-secondary-foreground: var(--color-primary-900);
--color-muted: var(--color-primary-50);
--color-muted-foreground: var(--color-primary-600);
--color-accent: var(--color-primary-100);
--color-accent-foreground: var(--color-primary-900);
--color-destructive: #DC2626;
--color-destructive-foreground: #FFFFFF;
--color-border: var(--color-primary-200);
--color-input: var(--color-primary-200);
--color-ring: var(--color-primary-600);
--radius: 0.5rem;
}
/* Dark mode overrides */
.dark {
--color-background: var(--color-primary-950);
--color-foreground: var(--color-primary-50);
--color-card: var(--color-primary-900);
--color-card-foreground: var(--color-primary-50);
--color-popover: var(--color-primary-900);
--color-popover-foreground: var(--color-primary-50);
--color-primary: var(--color-primary-500);
--color-primary-foreground: #FFFFFF;
--color-secondary: var(--color-primary-800);
--color-secondary-foreground: var(--color-primary-50);
--color-muted: var(--color-primary-800);
--color-muted-foreground: var(--color-primary-400);
--color-accent: var(--color-primary-800);
--color-accent-foreground: var(--color-primary-50);
--color-destructive: #EF4444;
--color-destructive-foreground: #FFFFFF;
--color-border: var(--color-primary-800);
--color-input: var(--color-primary-800);
--color-ring: var(--color-primary-500);
}
assets/tailwind-colors.css を開始用テンプレートとしてコピーします。
コンポーネントの使用例
// Primary button
<button className="bg-primary text-primary-foreground hover:bg-primary/90">Click me</button>
// Secondary button
<button className="bg-secondary text-secondary-foreground hover:bg-secondary/80">Cancel</button>
// Card
<div className="bg-card text-card-foreground border-border rounded-lg">
<h2>Title</h2>
<p className="text-muted-foreground">Description</p>
</div>
// Input
<input className="bg-background text-foreground border-input focus:ring-ring" />
よくある調整
- 明るいシェードが鮮やかすぎる:彩度を 10~20%下げます
- プライマリーのコントラストが低い:テキストにはシェード 700 以上を使います
- ダークモードが暗すぎる:背景に 950 ではなく 900 を使います
- ブランドカラーが明るすぎる/暗すぎる:シェード 500~600 の範囲に調整します
- ダークモードがくすんで見える:プライマリーにシェード 500 を使います(ライトモードの 600 より明るい色です)
- ダークモードで純白のテキストが強すぎる:代わりにシェード 50(オフホワイト)を使います
- ダークモードの控えめなテキストがコントラスト不足になる:より差の大きいシェードを使います(400 を 800 に載せる代わりに、300 を 900 に載せます)
ブランドアイデンティティの調整
- 保守的なブランド(金融、法律):ボタンには primary-700 を使用し、明るい色合いでは彩度を下げます
- 鮮やかなブランド(クリエイティブ、テクノロジー):primary-500-600 を使用し、彩度を保ちます
- ミニマルなブランド(デザイン、建築):primary の使用を控えめにし、落ち着いた色調と控えめな境界線(primary-100)を重視します
検証チェックリスト
- 本文テキスト:4.5:1 以上(通常サイズ)または 3:1 以上(大きいサイズ)
- プライマリボタンのテキスト:4.5:1 以上
- セカンダリボタンのテキスト:4.5:1 以上
- 控えめなテキスト:4.5:1 以上
- リンク:4.5:1 以上(または下線付き)
- UI 要素(境界線):3:1 以上
- フォーカスインジケーター:3:1 以上
- エラーテキスト:4.5:1 以上
- ダークモード:上記のチェック項目をすべて満たす
- すべての背景に対応する前景色がある
- どちらのモードでもブランドカラーを認識できる
- 境界線が見やすく、強すぎない
- カードやセクションの境界が明確
リリース前に両方のモードをテストしてください。
参考資料(任意)
- オンラインのコントラストチェッカー:WebAIM (webaim.org/resources/contrastchecker)、Coolors (coolors.co/contrast-checker)、Accessible Colors (accessible-colors.com)
- CI/CD のコントラストテスト:テストスイートで
getContrastRatio()を使用し、すべてのトークンの組み合わせが最小比率を満たすことを検証します - 透明色/グラデーションの特殊なケース:不透明度のある色は、最終的に描画された色に対して計算します。グラデーションは両端を確認します。
- OLED のダークモード:AMOLED 画面でバッテリーを節約するため、背景に
#000000を使用する@media (prefers-contrast: high)を使います - 複数色のパレット:ブランドカラーごとに個別の濃淡スケールを生成し、異なるセマンティックロール(primary、accent)に割り当てます
- パレットの可視化ツール:coolors.co、paletton.com、Figma のスウォッチ
assets/tailwind-colors.css— CSS 出力テンプレート一式