🎨
OKLCHで配色設計してみた:React/Tailwindなら明るさを崩さずに
はじめに
最近、業務でマークアップ(React/TypeScript)を触ることが増えて、配色の崩れやすさに何度か泣きました。そこで OKLCH を軸に「明るさ(L)と彩度(C)を意識したトークン設計」を試したらかなり楽になったのでメモします。
結論、OKLCH + CSS変数 + Tailwindの組み合わせだと、ライト/ダークどちらでも破綻しにくい配色をトークン化できて、Hover/Active みたいな状態色も CSSだけでほぼ自動生成できます。


なんでOKLCH?
-
人間の見え方に近い軸(L/C/H)で色を決められる
→ 背景と前景のコントラスト調整がやりやすい。 -
ライト/ダークの切り替えが安定
→ H(色相)はなるべく固定、L/Cだけ動かすのがコツ。 -
CSSだけで派生色が作れる
→color-mix()で Hover/Press を算出。JSに寄せなくてOK。
ざっくり方針
-
CSS変数でトークン宣言(
--brand/--bg/--fg/--surfaceなど) -
Tailwindの色にブリッジ(
oklch(var(--brand)/<alpha-value>)みたいに) -
派生色はCSSで生成(
--brand-hover等をcolor-mix()で) - ダークはL/Cだけ再調整(Hは固定してブランド感を保つ)
実装スニペット
globals.css
:root {
--brand: oklch(0.62 0.19 265);
--bg: oklch(0.98 0.01 255);
--fg: oklch(0.18 0.03 255);
--brand-hover: color-mix(in oklch, var(--brand), white 12%);
--brand-press: color-mix(in oklch, var(--brand), black 10%);
--surface: oklch(0.98 0.01 255);
--surface-2: color-mix(in oklch, var(--surface), black 4%);
}
@media (prefers-color-scheme: dark) {
:root {
--bg: oklch(0.15 0.02 255);
--fg: oklch(0.93 0.02 255);
--brand: oklch(0.70 0.17 265);
--surface: oklch(0.20 0.02 255);
--surface-2: color-mix(in oklch, var(--surface), white 6%);
--brand-hover: color-mix(in oklch, var(--brand), white 10%);
--brand-press: color-mix(in oklch, var(--brand), black 12%);
}
}
tailwind.config.ts
import type { Config } from "tailwindcss";
export default {
content: ["./src/**/*.{ts,tsx,mdx}"],
theme: {
extend: {
colors: {
bg: "oklch(var(--bg) / <alpha-value>)",
fg: "oklch(var(--fg) / <alpha-value>)",
brand: "oklch(var(--brand) / <alpha-value>)",
surface: "oklch(var(--surface) / <alpha-value>)",
"surface-2": "oklch(var(--surface-2) / <alpha-value>)",
},
ringColor: { DEFAULT: "oklch(var(--brand) / 1)" },
},
},
} satisfies Config;
Button
type ButtonProps = React.ComponentProps<"button"> & { variant?: "primary" | "ghost" };
export function Button({ variant = "primary", className = "", ...props }: ButtonProps) {
const base =
variant === "primary"
? "bg-brand text-white hover:bg-[--brand-hover] active:bg-[--brand-press] focus-visible:ring"
: "bg-surface-2 text-fg hover:bg-[color-mix(in_oklch,var(--surface-2),white_6%)]";
return (
<button
className={`inline-flex items-center rounded-xl px-4 py-2 transition ${base} ${className}`}
{...props}
/>
);
}
まとめ
OKLCHをベースのトークン→ユーティリティの流れを作ると、配色レビューの迷いが激減しました。React/TSでも型でバリアントを整理しやすくて、UIの追加にも強いです。
同じように配色で悩んでる人は、ぜひ手元のプロジェクトで試してみてください!
Discussion