mcpField
U
uicolor

uicolor

github.com/dawitlabsデザイン・UI740 回インストール24 週間前に更新
リポジトリ

uicolor について

UI の色を監査・調整するスキルで、インターフェースを改善する開発者向けです。画面ごとにスクリーンショットとパレットの占有率を分析し、HSB の規則に沿って色トークンを修正して視覚的に確認します。

主な機能

  1. 01画面のパレットと占有率を抽出
  2. 02色の役割と階層を確認
  3. 03色温度と彩度を監査
  4. 04優先順位に沿って色トークンを修正
  5. 05スクリーンショットを確認して報告

活用シーン

  1. 01ブランドカラーの過剰使用を発見
  2. 02ニュートラルカラーの色温度を統一
  3. 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. 目を細めて色の階層を確認する

最も複雑な画面のスクリーンショットを用意します。ぼやけるまで目を細め、次の点を確認します。

  1. 最初に目に入る色は何か
  2. それは画面上で最も重要な要素か
  3. 色の重みで、背景要素が前景要素と競合していないか

答えが適切でない場合 → 色の階層が崩れています。

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 つの根本原因のいずれかがあります。

  1. 値が不適切 — 16 進数カラーコード自体が不適切(彩度が高すぎる、色温度が違う)
  2. 役割が不適切 — 色が本来と異なる目的に使われている(プライマリカラーを装飾的に使用)
  3. バリエーション不足 — 色のシェードが 1 種類しかなく、状態を区別できない
  4. 色相のわずかな色味がない — ニュートラルカラーが純粋なグレーで、生気のないパレットになっている
  5. 影の色味がない — 影が色相を帯びた色ではなく、純粋な黒になっている

修正を書く前に、根本原因を特定します。

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% 以下に下がりましたか?
  • ニュートラルカラーの色相は適切な方向に変化しましたか?
  • すべてのニュートラルカラーで色温度に一貫性がありますか?

もう一度、目を細めて確認します。次の点を確認します。

  1. 画面は修正前より暖かく/冷たく/バランスよく感じられますか?
  2. プライマリカラーは背景に対して際立っていますか?
  3. 影に奥行きがありますか?それとも、まだ表面に塗られたように見えますか?

いずれかの答えが適切でない場合は、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✓

修正前/修正後のパレット — 変更したすべてのトークンについて、変更前と変更後の値を一覧にします。

フラグを付けたすべての問題を、最後に一覧表示します。


ルール

  1. 最初にスクリーンショットを確認します。必ず確認してください。 色の問題は、数値で測る前に感じ取れるものです。画像を見てから、数値を確認します。
  2. コンポーネントではなく、トークンを修正します。 40か所で間違っている色は、トークン定義が1つ間違っています。
  3. HSBを使います。 16進数の値を推測して色を調整してはいけません。HSBに変換し、ルールを適用してから、元に戻します。
  4. 60-30-10は視覚上の法則であり、単なる提案ではありません。 プライマリカラーの占有率が25%だと、ほかのすべての要素と競合します。減らしてください。
  5. ニュートラルカラーが本当の意味でニュートラルになることはありません。 純粋なグレーは、暖色系のブランドでは冷たく、寒色系のブランドでは暖かく見えます。必ず色相をわずかに加えてください。
  6. 影にも色があります。 純粋な黒の影はUIを平坦に見せます。色味のある影は奥行きを加えます。
  7. 色温度はシステム全体で決めます。 暖色系と寒色系のニュートラルカラーを混在させてはいけません。方向性を決めて統一します。
  8. セマンティックカラーは厳守します。 緑は成功、赤はエラーを表します。ブランドカラーやエラー状態に緑を使うと、シグナルが損なわれます。
  9. 1画面あたり最大3回まで反復します。 問題点に印を付けて、次に進みます。
  10. ロジック、レイアウト、コンポーネント構造は変更しません。 色の値だけを変更します。

HSBのリファレンス、色相の温度チャート、影の作成式、60-30-10の完全ガイドについては、COLOR-THEORY.mdを参照してください。

コメント