uicolor について
UI の色を監査・調整するスキルで、インターフェースを改善する開発者向けです。画面ごとにスクリーンショットとパレットの占有率を分析し、HSB の規則に沿って色トークンを修正して視覚的に確認します。
主な機能
- 01画面のパレットと占有率を抽出
- 02色の役割と階層を確認
- 03色温度と彩度を監査
- 04優先順位に沿って色トークンを修正
- 05スクリーンショットを確認して報告
活用シーン
- 01ブランドカラーの過剰使用を発見
- 02ニュートラルカラーの色温度を統一
- 03複数画面の色の階層を確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dawitlabs/ ui-skills --skill uicolorインストール後、Claude Code で/uicolorを入力して読み込まれたか確認します。
同梱ファイルCOLOR-THEORY.md
詳細
/uicolor — 色補正とバランス調整
色のマスターです。レンダリングされたUIを読み取り、実際の色分布を抽出し、あらゆる不均衡を診断して、HSB理論を用いて正確に補正します。画面ごとに作業し、常に視覚的に確認します。
色彩理論の完全なリファレンス:COLOR-THEORY.md
フェーズ 0 — 環境のセットアップ
0a. 開発サーバーを確認/起動する
curl -s --max-time 2 -o /dev/null -w "%{http_code}" http://localhost:3000
200 でない場合は、開発コマンドを見つけて実行し、待機します。
until curl -s --max-time 2 http://localhost:3000 > /dev/null 2>&1; do sleep 1; done && echo "ready"
0b. ソースのルートとトークンの場所を特定する
find . -type d -name "src" -not -path "*/node_modules/*" -not -path "*/.next/*" \
-not -path "*/dist/*" -not -path "*/.turbo/*" | head -1
find . \( -name "globals.css" -o -name "variables.css" \) -not -path "*/node_modules/*" | head -3
find . -name "tailwind.config.*" -not -path "*/node_modules/*" | head -2
トークンの場所にあるファイルを読み、すでに定義されている色の値をすべて記録します。これらは一から置き換えるのではなく、補正する対象の値です。
0c. DESIGN-BRIEF.md があれば読む
cat DESIGN-BRIEF.md 2>/dev/null | head -60
ブリーフの Color System セクションには、意図する方向性(色温度、彩度、プライマリのHEX値)が記載されています。これを指針として、意図に沿って補正し、そこから外れないようにします。
0d. 色分析スクリプトを作成する
プロジェクトのルートに書き込みます(node_modules が必要です)。フェーズ 4 で削除します。
_uicolor_extract.mjs — レンダリングされたカラーパレットをカバレッジの割合とともに抽出します。
import { chromium } from '@playwright/test';
import { mkdirSync, writeFileSync } from 'fs';
const get = (f) => { const i = process.argv.indexOf(f); return i !== -1 ? process.argv[i+1] : null; };
const url = get('--url') ?? 'http://localhost:3000';
const name = get('--name') ?? 'screen';
const width = parseInt(get('--width') ?? '1280');
const height = parseInt(get('--height') ?? '900');
const auth = get('--auth');
mkdirSync('/tmp/uicolor', { recursive: true });
const browser = await chromium.launch();
const context = await browser.newContext({
viewport: { width, height },
...(auth ? { storageState: auth } : {}),
});
const page = await context.newPage();
try {
await page.goto(url, { waitUntil: 'networkidle', timeout: 30000 });
} catch {
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
}
// Screenshot for visual reading
await page.screenshot({ path: `/tmp/uicolor/${name}.png`, fullPage: true });
// Extract color distribution from rendered elements
const palette = await page.evaluate(() => {
const colorArea = new Map();
const totalArea = document.documentElement.scrollHeight * window.innerWidth;
document.querySelectorAll('*').forEach(el => {
const rect = el.getBoundingClientRect();
const absTop = rect.top + window.scrollY;
const area = rect.width * (rect.height || 1);
if (area < 100) return; // skip tiny elements
const style = getComputedStyle(el);
const props = ['backgroundColor', 'color', 'borderTopColor', 'outlineColor'];
props.forEach(prop => {
const val = style[prop];
if (!val || val === 'rgba(0, 0, 0, 0)' || val === 'transparent') return;
// Normalise to rgba string as key
colorArea.set(val, (colorArea.get(val) ?? 0) + (prop === 'backgroundColor' ? area : area * 0.1));
});
});
// Convert to sorted array with coverage %
return [...colorArea.entries()]
.sort((a, b) => b[1] - a[1])
.slice(0, 20)
.map(([color, area]) => ({
color,
coverage: Math.round((area / totalArea) * 1000) / 10, // % of page area
}));
});
// Parse rgb values to hex for readability
const toHex = (rgb) => {
const m = rgb.match(/\d+/g);
if (!m || m.length < 3) return rgb;
return '#' + m.slice(0, 3).map(n => parseInt(n).toString(16).padStart(2, '0')).join('');
};
const result = {
url,
viewport: `${width}×${height}`,
palette: palette.map(p => ({ ...p, hex: toHex(p.color) })),
};
writeFileSync(`/tmp/uicolor/${name}-palette.json`, JSON.stringify(result, null, 2));
await browser.close();
console.log(`screenshot: /tmp/uicolor/${name}.png`);
console.log(`palette: /tmp/uicolor/${name}-palette.json`);
palette.slice(0, 10).forEach(p => console.log(` ${toHex(p.color).padEnd(10)} ${p.coverage}%`));
使い方:
node _uicolor_extract.mjs --url http://localhost:3000 --name home
node _uicolor_extract.mjs --url http://localhost:3000/dashboard --name dashboard --auth /tmp/uiux-auth-state.json
node _uicolor_extract.mjs --url http://localhost:3000 --name home-mobile --width 375 --height 812
実行のたびに、スクリーンショットとパレットのJSONを確認します。
0e. 認証が必要な画面に対応する
ls /tmp/uiux-auth-state.json /tmp/animate-auth-state.json /tmp/copy-auth-state.json 2>/dev/null | head -1
該当する画面が見つかった場合は、保護されたルートに対するすべてのスクリプト呼び出しに --auth <path> を渡します。
フェーズ 1 — 色の監査
1a. すべての画面で色を抽出する
node _uicolor_extract.mjs --url http://localhost:3000 --name home
node _uicolor_extract.mjs --url http://localhost:3000 --name home-mobile --width 375 --height 812
画面ごとに繰り返します。すべてのスクリーンショットとパレットのJSONを確認します。
各画面について、以下を記録します。
- カバレッジ率で上位3色 — これらはドミナントカラー、セカンダリーカラー、アクセントカラーです(60-30-10 に合致する必要があります)
- パレット全体 — カバレッジ率が <1% の色(装飾的なノイズ)や想定外の色がないか確認します
- 単一の色相がカバレッジ率 >40% で支配的になっていないか(彩度疲れ)
1b. 色の役割を確認する
パレット内の各色について、役割を判断します。
| 役割 | あるべき色 | カバレッジ率 |
|---|---|---|
| 背景/ドミナント | ニュートラルな面 | 50–65% |
| セカンダリー | わずかに浮き上がった面、サイドバー | 20–30% |
| テキスト | 暗いニュートラルカラー | 10–20%(テキスト要素に適用) |
| プライマリ/アクセント | ブランドカラー | ≤10% |
| セマンティックカラー | 成功、エラー、警告 | 合計 ≤5% |
すぐに要注意として記録します。
- プライマリ/ブランドカラーのカバレッジ率が >15% → ブランドの希釈(すべてが主張し、何も際立たない)
- 背景の色合いが3種類を超える → 面の混乱
- 影に純粋な黒(#000000)を使用 → 平坦で強すぎる印象
- 色相のかすかな気配がない純粋なグレーニュートラル → 生気のない、活力を欠いた面
- すべてのホバー状態がベースと同じ色 → フィードバックがない
1c. トークン定義を読む
パレット内の各色をトークンファイルと照合します。それぞれについて、以下を確認します。
- トークン名があるか(良好)
- ハードコードされているか(/tokens の対象として記録)
- トークン名が意味を表しているか(
--color-primaryと--blueの比較)— 意味を表していない名前を記録
1d. 目を細めて色の階層を確認する
最も複雑な画面のスクリーンショットを用意します。ぼやけるまで目を細め、次の点を確認します。
- 最初に目に入る色は何か
- それは画面上で最も重要な要素か
- 色の重みで、背景要素が前景要素と競合していないか
答えが適切でない場合 → 色の階層が崩れています。
1e. 色温度を監査する
ニュートラルな面とテキストカラーすべてについて、色温度が一貫しているか確認します。
# Extract all CSS color values from token file
grep -E "color:|background:|border:" globals.css tailwind.config.* 2>/dev/null | grep -v "var(--" | head -30
それぞれをHSBに変換して確認します。
- すべてのニュートラルカラーで、同じ色相のかすかな気配を持たせます(暖色系:H 20–50°、寒色系:H 200–240°)
- 同じ画面に暖色系グレーと寒色系グレーを混在させると、色温度がぶつかります
- ブランドカラーの色温度は、ニュートラルカラーの色温度の方向性と一致させます
次に進む前に、画面ごとにまとめた監査結果全体を提示します。
フェーズ 2 — 優先順位付け
各問題を採点します。
| 問題 | 点数 |
|---|---|
| ブランドカラー/プライマリカラーの使用率が 15% を超える(希釈) | +4 |
| 温度感の衝突(暖色系と寒色系のニュートラルカラーが混在) | +4 |
| 色の階層が不適切(前景ではなく背景にアクセントカラーを使用) | +4 |
| 影が純粋な黒(色相の色味なし) | +3 |
| 背景の色合いが 3 種類を超える | +3 |
| ホバー状態とベース状態が同じ(色による反応なし) | +3 |
| ニュートラルグレーが純粋なグレー(色相のわずかな色味なし) | +2 |
| セマンティックカラーを装飾目的で使用(成功を示さない箇所で緑を使用) | +2 |
| 同じ役割を持つ要素間で彩度に一貫性がない | +2 |
| テキストカラーに明確な段階がない | +2 |
| アクセスの多い画面/第一印象を左右する画面 | +2 |
| 色覚異常のリスク(赤と緑だけで区別) | +2 |
スコア順に並べます。最も高いものから修正します。フェーズ 3 に進む前に、ユーザーに確認を求めます。
フェーズ 3 — 色の反復修正ループ
優先順位順に問題に対処します。画面ごとの反復は最大 3 回です。
3a. 修正前のスクリーンショット
この画面に対して _uicolor_extract.mjs を実行します。スクリーンショットとパレットの JSON を確認します。
現時点での色の印象を 1 文で述べます。(冷たく平板ですか?派手ですか?濁っていますか?生気がありませんか?)
3b. 根本原因を診断する
色の問題には、次の 5 つの根本原因のいずれかがあります。
- 値が不適切 — 16 進数カラーコード自体が不適切(彩度が高すぎる、色温度が違う)
- 役割が不適切 — 色が本来と異なる目的に使われている(プライマリカラーを装飾的に使用)
- バリエーション不足 — 色のシェードが 1 種類しかなく、状態を区別できない
- 色相のわずかな色味がない — ニュートラルカラーが純粋なグレーで、生気のないパレットになっている
- 影の色味がない — 影が色相を帯びた色ではなく、純粋な黒になっている
修正を書く前に、根本原因を特定します。
3c. 修正 — HSB を正確に調整する
COLOR-THEORY.md のルールを自動的に適用します。
Brand dilution (primary used everywhere):
→ Keep primary on: CTAs, links, focus rings, active nav items, badges
→ Replace on: decorative icons, background fills, section dividers
→ Replace with: neutral-600 for icons, surface colors for backgrounds
Temperature correction:
→ Convert current neutral hex to HSB
→ Adjust hue: toward 30° for warm, toward 220° for cool
→ Keep S ≤ 8% (barely perceptible — a whisper, not a tint)
→ Recheck against brand primary temperature — they should agree
Shadow construction:
→ Bad: box-shadow: 0 4px 6px rgba(0,0,0,0.1)
→ Good: take surface color HSB → increase S by 30% → decrease B by 60% → use at 0.12–0.20 opacity
→ Example: white surface (#ffffff) → shadow: 0 4px 6px rgba(0, 0, 40, 0.12)
→ Example: blue-50 surface (#eff6ff) → shadow: 0 4px 6px rgba(30, 60, 120, 0.12)
Hover state construction:
→ Hover (darker): S+10, B-15, hue toward 0°/120°/240°
→ Active/pressed: S+15, B-25 (more dramatic than hover)
→ Disabled: S-30, B+10, opacity 0.5
→ Focus ring: brand primary at full S+B, 2px offset
Neutral hue whisper:
→ Pure gray: hsl(0, 0%, 97%) → add whisper: hsl(220, 8%, 97%)
→ The hue should match the brand primary's temperature direction
→ S between 3–8% — visible in isolation, invisible when focused on content
Text color scale:
→ Primary text: brand-neutral-900 (or equivalent)
→ Secondary text: brand-neutral-500 (not pure gray — use hue-tinted neutral)
→ Disabled text: brand-neutral-400
→ Never more than 4 levels of text gray
60-30-10 correction:
→ Measure coverage from palette JSON
→ If primary > 10%: audit every use, keep only interactive elements
→ If no element < 5% coverage: the accent has no room to breathe — reduce secondary color
まずトークンファイルを修正し、その後、コンポーネントでの使用状況を確認します。 トークンを 1 つ変更するだけで、50 個のコンポーネントを修正できます。
3d. 修正後のスクリーンショット
sleep 3 # hot reload
node _uicolor_extract.mjs --url http://localhost:3000<ROUTE> --name <name>-after
スクリーンショットと新しいパレットの JSON を確認します。
修正前後のパレット JSON を比較します。
- プライマリカラーの使用率は 10% 以下に下がりましたか?
- ニュートラルカラーの色相は適切な方向に変化しましたか?
- すべてのニュートラルカラーで色温度に一貫性がありますか?
もう一度、目を細めて確認します。次の点を確認します。
- 画面は修正前より暖かく/冷たく/バランスよく感じられますか?
- プライマリカラーは背景に対して際立っていますか?
- 影に奥行きがありますか?それとも、まだ表面に塗られたように見えますか?
いずれかの答えが適切でない場合は、3c に戻ります(反復回数を増やし、最大 3 回まで)。
3e. 色のチェックリスト
次の画面に進む前に確認します。
- どの画面でも、プライマリカラー/ブランドカラーの使用率が 10% 以下(パレット JSON に基づく)
- すべてのニュートラルカラーに一貫した色相のわずかな色味があり、色温度の方向が同じ
- どのサーフェスにも純粋なグレーのニュートラルカラー(HSB S = 0%)がない
- すべての影が色相を帯びており、純粋な黒ではない
- ホバー状態がベース状態と視覚的に区別できる(最小値 S+10 B-15)
- アクティブ/押下状態がホバー状態と視覚的に区別できる
- テキストは 4 段階以下のグレースケールに従い、色相に一貫性がある
- セマンティックカラー(成功を示す緑、エラーを示す赤)を装飾目的で使用していない
- 2 つの状態の違いを示す唯一の手段として、赤と緑の組み合わせを使用していない
- 目を細めて確認:視線が最優先の要素に最初に向かう
- 色温度に一貫性があり、1 つの画面内で暖色系と寒色系が衝突していない
- 修正前後のパレット JSON で、使用率が適切な方向に変化している
不合格があれば 3c に戻ります。3 回目の反復後は、問題にフラグを付けて次に進みます。
フェーズ 4 — クリーンアップとレポート
一時スクリプトを削除します。
rm -f _uicolor_extract.mjs
各画面の処理後:
✓ Screen name (rank N, iteration K) — primary coverage: 23% → 8%, shadows tinted, neutrals warmed
最終サマリー:
| 画面 | 修正前のプライマリカラー使用率 | 修正後 | 色温度 | 影 | 反復回数 | 状態 |
|---|---|---|---|---|---|---|
| ホーム | 28% | 7% | ✓ 暖色 | ✓ 色味あり | 2 | ✓ |
修正前/修正後のパレット — 変更したすべてのトークンについて、変更前と変更後の値を一覧にします。
フラグを付けたすべての問題を、最後に一覧表示します。
ルール
- 最初にスクリーンショットを確認します。必ず確認してください。 色の問題は、数値で測る前に感じ取れるものです。画像を見てから、数値を確認します。
- コンポーネントではなく、トークンを修正します。 40か所で間違っている色は、トークン定義が1つ間違っています。
- HSBを使います。 16進数の値を推測して色を調整してはいけません。HSBに変換し、ルールを適用してから、元に戻します。
- 60-30-10は視覚上の法則であり、単なる提案ではありません。 プライマリカラーの占有率が25%だと、ほかのすべての要素と競合します。減らしてください。
- ニュートラルカラーが本当の意味でニュートラルになることはありません。 純粋なグレーは、暖色系のブランドでは冷たく、寒色系のブランドでは暖かく見えます。必ず色相をわずかに加えてください。
- 影にも色があります。 純粋な黒の影はUIを平坦に見せます。色味のある影は奥行きを加えます。
- 色温度はシステム全体で決めます。 暖色系と寒色系のニュートラルカラーを混在させてはいけません。方向性を決めて統一します。
- セマンティックカラーは厳守します。 緑は成功、赤はエラーを表します。ブランドカラーやエラー状態に緑を使うと、シグナルが損なわれます。
- 1画面あたり最大3回まで反復します。 問題点に印を付けて、次に進みます。
- ロジック、レイアウト、コンポーネント構造は変更しません。 色の値だけを変更します。
HSBのリファレンス、色相の温度チャート、影の作成式、60-30-10の完全ガイドについては、COLOR-THEORY.mdを参照してください。