Color Systems Reference captures reusable agent playbook guidance for accessibility & mobile ux. Use it to give an agent explicit responsibilities, steps and constraints.
Color Systems Reference
Snapshot 2026-08-04 16:17:00 UTC · version 1
Research document
Color Systems Reference
Color Systems Reference captures reusable agent playbook guidance for accessibility & mobile ux. Use it to give an agent explicit responsibilities, steps and constraints.
Editorial note: curated source snapshot published by Collider.club under the MIT License. Source attribution is preserved in the front matter.
Source snapshot
Color Systems Reference
Color Palette Generation
Perceptually Uniform Scales
Using OKLCH for perceptually uniform color scales:
/* OKLCH: Lightness, Chroma, Hue */
:root {
/* Generate a blue scale with consistent perceived lightness steps */
--blue-50: oklch(97% 0.02 250);
--blue-100: oklch(93% 0.04 250);
--blue-200: oklch(86% 0.08 250);
--blue-300: oklch(75% 0.12 250);
--blue-400: oklch(65% 0.16 250);
--blue-500: oklch(55% 0.2 250); /* Primary */
--blue-600: oklch(48% 0.18 250);
--blue-700: oklch(40% 0.16 250);
--blue-800: oklch(32% 0.12 250);
--blue-900: oklch(25% 0.08 250);
--blue-950: oklch(18% 0.05 250);
}
Programmatic Scale Generation
function generateColorScale(
hue: number,
saturation: number = 100,
): Record<string, string> {
const lightnessStops = [
{ name: "50", l: 97 },
{ name: "100", l: 93 },
{ name: "200", l: 85 },
{ name: "300", l: 75 },
{ name: "400", l: 65 },
{ name: "500", l: 55 },
{ name: "600", l: 45 },
{ name: "700", l: 35 },
{ name: "800", l: 25 },
{ name: "900", l: 18 },
{ name: "950", l: 12 },
];
return Object.fromEntries(
lightnessStops.map(({ name, l }) => [
name,
`hsl(${hue}, ${saturation}%, ${l}%)`,
]),
);
}
// Generate semantic colors
const brand = generateColorScale(220); // Blue
const success = generateColorScale(142); // Green
const warning = generateColorScale(38); // Amber
const error = generateColorScale(0); // Red
Semantic Color Tokens
Two-Tier Token System
/* Tier 1: Primitive colors (raw values) */
:root {
--primitive-blue-500: #3b82f6;
--primitive-blue-600: #2563eb;
--primitive-green-500: #22c55e;
--primitive-red-500: #ef4444;
--primitive-gray-50: #f9fafb;
--primitive-gray-900: #111827;
}
/* Tier 2: Semantic tokens (purpose-based) */
:root {
/* Background */
--color-bg-primary: var(--primitive-gray-50);
--color-bg-secondary: white;
--color-bg-tertiary: var(--primitive-gray-100);
--color-bg-inverse: var(--primitive-gray-900);
/* Text */
--color-text-primary: var(--primitive-gray-900);
--color-text-secondary: var(--primitive-gray-600);
--color-text-tertiary: var(--primitive-gray-400);
--color-text-inverse: white;
--color-text-link: var(--primitive-blue-600);
/* Border */
--color-border-default: var(--primitive-gray-200);
--color-border-strong: var(--primitive-gray-300);
--color-border-focus: var(--primitive-blue-500);
/* Interactive */
--color-interactive-primary: var(--primitive-blue-600);
--color-interactive-primary-hover: var(--primitive-blue-700);
--color-interactive-primary-active: var(--primitive-blue-800);
/* Status */
--color-status-success: var(--primitive-green-500);
--color-status-warning: var(--primitive-amber-500);
--color-status-error: var(--primitive-red-500);
--color-status-info: var(--primitive-blue-500);
}
Component Tokens
/* Tier 3: Component-specific tokens */
:root {
/* Button */
--button-bg: var(--color-interactive-primary);
--button-bg-hover: var(--color-interactive-primary-hover);
--button-text: white;
--button-border-radius: 0.375rem;
/* Input */
--input-bg: var(--color-bg-secondary);
--input-border: var(--color-border-default);
--input-border-focus: var(--color-border-focus);
--input-text: var(--color-text-primary);
--input-placeholder: var(--color-text-tertiary);
/* Card */
--card-bg: var(--color-bg-secondary);
--card-border: var(--color-border-default);
--card-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
Dark Mode Implementation
CSS Custom Properties Approach
/* Light theme (default) */
:root {
--color-bg-primary: #ffffff;
--color-bg-secondary: #f9fafb;
--color-bg-tertiary: #f3f4f6;
--color-text-primary: #111827;
--color-text-secondary: #4b5563;
--color-border-default: #e5e7eb;
}
/* Dark theme */
[data-theme="dark"] {
--color-bg-primary: #111827;
--color-bg-secondary: #1f2937;
--color-bg-tertiary: #374151;
--color-text-primary: #f9fafb;
--color-text-secondary: #9ca3af;
--color-border-default: #374151;
}
/* System preference */
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--color-bg-primary: #111827;
/* ... dark theme values */
}
}
React Theme Context
import { createContext, useContext, useEffect, useState } from "react";
type Theme = "light" | "dark" | "system";
interface ThemeContextValue {
theme: Theme;
setTheme: (theme: Theme) => void;
resolvedTheme: "light" | "dark";
}
const ThemeContext = createContext<ThemeContextValue | null>(null);
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState<Theme>("system");
const [resolvedTheme, setResolvedTheme] = useState<"light" | "dark">("light");
useEffect(() => {
const root = document.documentElement;
if (theme === "system") {
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
.matches
? "dark"
: "light";
setResolvedTheme(systemTheme);
root.setAttribute("data-theme", systemTheme);
} else {
setResolvedTheme(theme);
root.setAttribute("data-theme", theme);
}
}, [theme]);
useEffect(() => {
if (theme !== "system") return;
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
const handler = (e: MediaQueryListEvent) => {
const newTheme = e.matches ? "dark" : "light";
setResolvedTheme(newTheme);
document.documentElement.setAttribute("data-theme", newTheme);
};
mediaQuery.addEventListener("change", handler);
return () => mediaQuery.removeEventListener("change", handler);
}, [theme]);
return (
<ThemeContext.Provider value={{ theme, setTheme, resolvedTheme }}>
{children}
</ThemeContext.Provider>
);
}
export function useTheme() {
const context = useContext(ThemeContext);
if (!context) throw new Error("useTheme must be within ThemeProvider");
return context;
}
Contrast and Accessibility
WCAG Contrast Checker
function hexToRgb(hex: string): [number, number, number] {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
if (!result) throw new Error("Invalid hex color");
return [
parseInt(result[1], 16),
parseInt(result[2], 16),
parseInt(result[3], 16),
];
}
function getLuminance(r: number, g: number, b: number): number {
const [rs, gs, bs] = [r, g, b].map((c) => {
c = c / 255;
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
});
return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
}
function getContrastRatio(hex1: string, hex2: string): number {
const [r1, g1, b1] = hexToRgb(hex1);
const [r2, g2, b2] = hexToRgb(hex2);
const l1 = getLuminance(r1, g1, b1);
const l2 = getLuminance(r2, g2, b2);
const lighter = Math.max(l1, l2);
const darker = Math.min(l1, l2);
return (lighter + 0.05) / (darker + 0.05);
}
function meetsWCAG(
foreground: string,
background: string,
size: "normal" | "large" = "normal",
level: "AA" | "AAA" = "AA",
): boolean {
const ratio = getContrastRatio(foreground, background);
const requirements = {
normal: { AA: 4.5, AAA: 7 },
large: { AA: 3, AAA: 4.5 },
};
return ratio >= requirements[size][level];
}
// Usage
meetsWCAG("#ffffff", "#3b82f6"); // true (4.5:1 for AA normal)
meetsWCAG("#ffffff", "#60a5fa"); // false (below 4.5:1)
Accessible Color Pairs
// Generate accessible text color for any background
function getAccessibleTextColor(backgroundColor: string): string {
const [r, g, b] = hexToRgb(backgroundColor);
const luminance = getLuminance(r, g, b);
// Use white text on dark backgrounds, black on light
return luminance > 0.179 ? "#111827" : "#ffffff";
}
// Find the nearest accessible shade
function findAccessibleShade(
textColor: string,
backgroundScale: string[],
minContrast: number = 4.5,
): string | null {
for (const shade of backgroundScale) {
if (getContrastRatio(textColor, shade) >= minContrast) {
return shade;
}
}
return null;
}
Color Harmony
Harmony Functions
type HarmonyType =
| "complementary"
| "triadic"
| "analogous"
| "split-complementary";
function generateHarmony(baseHue: number, type: HarmonyType): number[] {
switch (type) {
case "complementary":
return [baseHue, (baseHue + 180) % 360];
case "triadic":
return [baseHue, (baseHue + 120) % 360, (baseHue + 240) % 360];
case "analogous":
return [(baseHue - 30 + 360) % 360, baseHue, (baseHue + 30) % 360];
case "split-complementary":
return [baseHue, (baseHue + 150) % 360, (baseHue + 210) % 360];
default:
return [baseHue];
}
}
// Generate palette from harmony
function generateHarmoniousPalette(
baseHue: number,
type: HarmonyType,
): Record<string, string> {
const hues = generateHarmony(baseHue, type);
const names = ["primary", "secondary", "tertiary"];
return Object.fromEntries(
hues.map((hue, i) => [names[i] || `color-${i}`, `hsl(${hue}, 70%, 50%)`]),
);
}
Color Blindness Considerations
// Simulate color blindness
type ColorBlindnessType = "protanopia" | "deuteranopia" | "tritanopia";
// Matrix transforms for common types
const colorBlindnessMatrices: Record<ColorBlindnessType, number[][]> = {
protanopia: [
[0.567, 0.433, 0],
[0.558, 0.442, 0],
[0, 0.242, 0.758],
],
deuteranopia: [
[0.625, 0.375, 0],
[0.7, 0.3, 0],
[0, 0.3, 0.7],
],
tritanopia: [
[0.95, 0.05, 0],
[0, 0.433, 0.567],
[0, 0.475, 0.525],
],
};
// Best practices for color-blind accessibility:
// 1. Do not rely solely on color to convey information
// 2. Use patterns or icons alongside color
// 3. Ensure sufficient contrast between colors
// 4. Test with color blindness simulators
// 5. Use blue-orange instead of red-green for contrast
CSS Color Functions
/* Modern CSS color functions */
.modern-colors {
/* Relative color syntax */
--lighter: hsl(from var(--base-color) h s calc(l + 20%));
--darker: hsl(from var(--base-color) h s calc(l - 20%));
/* Color mixing */
--mixed: color-mix(in srgb, var(--color-1), var(--color-2) 30%);
/* Transparency */
--semi-transparent: rgb(from var(--base-color) r g b / 50%);
/* OKLCH for perceptual uniformity */
--vibrant-blue: oklch(60% 0.2 250);
}
/* Alpha variations */
.alpha-scale {
--color-10: rgb(59 130 246 / 0.1);
--color-20: rgb(59 130 246 / 0.2);
--color-30: rgb(59 130 246 / 0.3);
--color-40: rgb(59 130 246 / 0.4);
--color-50: rgb(59 130 246 / 0.5);
}
About Collider.club
This card belongs to the curated knowledge base of Collider.club — a closed business club for entrepreneurs, engineers, investors and domain experts building projects for international markets. Members work across DeFi, AI/ML, FinTech, Web3, banking, hardware and venture capital, and the club runs closed sessions on high-margin niches with anonymous speakers.
- Club: https://collider.club
- Collection: Collider.club curated card library (
mdrss-card/v2) - Maintainer: Collider.club editorial team
License
MIT License — Copyright (c) 2026 Collider.club. Full text: LICENSE · https://opensource.org/licenses/MIT
Why MDRSS assigned this score
- evidence comes from multiple domains
- some evidence URLs look like primary-source hosts
Evidence (4)
concept:accessibility-mobile-uxorg:collider-club Discussion 0
Sign in to join the discussion.