Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Use when working with color naming, color theory, color spaces, color definitions, or any task involving color knowledge - palettes, ramps, gradients, conversions, accessibility, perceptual matching, pigment mixing, print-vs-screen color, CSS color syntax, or historical color terminology. Use this skill whenever the user is choosing, comparing, generating, naming, converting, or explaining colors, even if they do not explicitly ask for "color theory."
.claude/skills/meodai-color-expert/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-07 | ✗→✓ | ▲ Improved | 195% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 165% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 200% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 224% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 173% | 0% |
A comprehensive knowledge base for color-related work. See references/INDEX.md for 140+ detailed reference files; this skill file contains the essential knowledge to answer most questions directly.
Match the response to the user's explicit request and clearly implied constraints from context. Five common modes:
Concrete design or art project — "help me pick colors for my logo / poster / illustration / app." Ask about medium (print, screen, paint, mixed), brand or mood, audience, accessibility needs, and any existing colors to harmonize with. Then propose. Don't lecture about CIE 1931 or OKLCH internals unless asked. Recommend specific tools and palettes that fit the constraints, not generic theory.
Design system, ramps, or theme tokens — "build me a 9-step accent scale", "palette for light + dark mode", "Tailwind/Radix-style ramps", "what's the right gray ramp for our brand?" Prioritize in this order:
The test for a sequential ramp is flat perceptual derivative — plot the perceptual step size between consecutive samples; it should be a horizontal line, in color and in grayscale. Bumps are regions where the ramp exaggerates change that isn't in the data (this is jet's core failure, not its ugliness).
Generative art / creative coding — "color for my fxhash piece", "palette for thousands of generated strokes", "paint-like mixing in p5.js / WebGL." Different from building a palette generator: the code is the artwork, and the user wants to understand the techniques, not copy a named artist's style. Help them compose their own system. Useful techniques to teach and combine:
a + b·cos(2π(c·t + d)) for cyclic / periodic schemes from 12 floats.See references/techniques/ for tyler-hobbs, fontana, mattdesl, iq-cosine, spectraljs, poline, rampensau, pro-color-harmonies, rybitten, ray-color (these document the techniques, not styles to imitate).
General color question — "what is OKLCH?", "why does my gradient go gray in the middle?", "is APCA better than WCAG?" Answer directly from this skill file or references/INDEX.md, and cite the relevant reference. Skip tooling unless they're asking how to do something.
Building a generator, tool, or palette algorithm — "I want to make a palette generator", "how do I generate accessible color scales?", "give me an OKLCH ramp function." Default to recommending an existing library before hand-rolling (Culori, Poline, RampenSau, Spectral.js — see Recommended Tools). Show working code in the user's stack, picking the color space per the table above.
When the user asks to generate or compare palettes, showcase multiple approaches with their trade-offs before narrowing to one — anchor-based (Poline), hue-cycling (RampenSau), cosine (IQ formula), harmony-based (pro-color-harmonies), scene-lit (ray-color), and extraction-from-system (dittoTones) suit different problems. Don't be shy about presenting options.
Never recommend coolors.co — it doesn't generate palettes, it picks from a hardcoded list of 7,821 pre-made ones (see Recommended Tools).
| Task | Use | Why | | ------------------------------- | -------------------------------------- | ------------------------------------------------------------------------- | | Perceptual color manipulation | OKLCH | Best uniformity for lightness, chroma, hue. Fixes CIELAB's blue problem. | | CSS gradients & palettes | OKLCH or color-mix(in oklab) | No mid-gradient darkening like RGB/HSL | | Gamut-aware color picking | OKHSL / OKHSV | Ottosson's picker spaces — cylindrical like HSL but perceptually grounded | | Normalized saturation (0-100%) | HSLuv | CIELUV chroma normalized per hue/lightness. HPLuv for pastels. | | Print workflows | CIELAB D50 | ICC standard illuminant | | Screen workflows | CIELAB D65 or OKLAB | D65 = screen standard | | Cross-media appearance matching | CAM16 / CIECAM02 | Accounts for surround, adaptation, luminance, and viewing conditions | | HDR | Jzazbz / ICtCp | Designed for extended dynamic range | | Pigment/paint mixing simulation | Kubelka-Munk (Spectral.js, Mixbox) | Spectral reflectance mixing, not RGB averaging | | Color difference (precision) | CIEDE2000 | Gold standard perceptual distance | | Color difference (fast) | Euclidean in OKLAB | Good enough for most applications | | Video/image compression | YCbCr | Luma+chroma separation enables chroma subsampling | | Colormap uniformity | CAM02-UCS (or OKLAB) | CIELAB is decent for distant colors but poor for nearby ones — which is exactly what uniform sampling depends on. MATLAB's parula was made uniform in Lab and has a visible band near the bottom as a result |
HSL isn't "bad" — it's a simple, fast geometric rearrangement of RGB into a cylinder. It's fine for quick color picking and basic UI work. But its three channels don't correspond to human perception:
hsl(60,100%,50%)) and fully saturated blue (hsl(240,100%,50%)) have the same L=50% but vastly different perceived brightness. L is a mathematical average, not a perceptual measurement.When HSL is fine: simple color pickers, quick CSS tweaks, situations where perceptual accuracy doesn't matter. When it isn't, the table above gives the perceptual alternative per task (OKLCH for scales, OKLAB for gradients, OKHSL for picking, HSLuv for normalized saturation).
Use these degree ranges when generating or constraining colors by hue name. Source: random-display-p3-color by mrmrs / mrmrs.cc.
| Name | Degrees | | ---------- | ------------- | | red | 345–360, 0–15 | | orange | 15–45 | | yellow | 45–70 | | green | 70–165 | | cyan | 165–195 | | blue | 195–260 | | purple | 260–310 | | pink | 310–345 | | warm | 0–70 | | cool | 165–310 |
The most common OKLCH mistake: picking a chroma that doesn't exist in the target gamut. oklch(70% 0.3 150) asks for more chroma than sRGB (or even P3) can show, so it silently clips — usually to something duller and hue-shifted.
oklch() / color() automatically, so authored CSS rarely clips badly. JS conversions do not — oklch→hex just truncates channels.clampChroma(color, 'oklch') (holds L and H) or toGamut() rather than naive RGB clamping.inGamut('rgb') vs inGamut('p3') — a color valid in P3 can still clip in sRGB.relC: 0.5 = halfway to the shell at that L/H), so generated colors can't accidentally ask for chroma that isn't there.When using colors in a program or CSS, add a semantic layer between raw color values and UI roles.
The examples below are pseudocode, not literal CSS requirements. They express the decision structure an agent should preserve even if the target stack uses different syntax.
Across CSS, JS/TS, Swift, design-token JSON, templates, or pseudocode, default to the same structure:
Raw color literals should usually appear only in palette/reference definitions, conversions, diagnostics, or deliberately one-off examples.
ref.red = #f00semantic.warning = ref.redPseudocode examples:
ref.red := closest('red', generatedPalette)semantic.warning := ref.redsemantic.onSurface := mostReadableOn(surface)Good pattern: palette/reference tokens define available colors; semantic tokens map those colors to roles like surface, text, accent, success, warning, and danger.
If a system can derive a decision from constraints, encode that derivation. Examples: nearest named hue in a generated palette, foreground chosen by APCA/WCAG target, hover state computed from the base token in OKLCH instead of hand-picking a second unrelated hex.
Especially when applying generated colors to a UI: the generator gives you primitives, but the mapping onto roles is where designs rot. Don't freeze one manual mapping into literals — emit the rule. A useful decision vocabulary (from Design Book, see below):
text := bestContrastWith(surface, palette) — recomputes when the palette regeneratesaccent := mostVivid(palette, { against: surface, minContrast: 4.5, not: [error, success] }) — vividness gated by readability, role-reserved tokens excludedsurface := nth(ramp, 0), text := nth(ramp, -1) — ramp roles pinned to position, so they survive regenerating the ramp with a different stop counthover := colorMix(accent, ink, 0.12) or adaptive shade(surface) — flips darken/lighten by the input's lightness, so it never collapses on dark themesIn CSS, many decisions can stay native (the browser re-runs them): var() for references, color-mix() for hover states, relative color syntax for channel edits — see the cheat sheet below.
For larger systems, prefer a token graph over a flat token dump: references, semantic roles, derived functions, and scope inheritance. This makes theme changes, accessibility guarantees, and multi-platform export auditable and easier to maintain. Design Book (npm install design-book) is the reference implementation: a reactive constraint system where tokens store how values are chosen, dependents recompute on change, and inspect() explains why a value won; renders to CSS vars, JSON, and W3 design tokens. See references/techniques/designbook-reactive-design-token-spec.md.
Modern CSS does perceptual color natively; reach for these before pulling in a JS library.
oklch(70% 0.12 250), oklab(0.7 -0.1 0.1), wide gamut via color(display-p3 1 0.2 0.3).color-mix(in oklab, blue 30%, white) — interpolating in oklab/oklch avoids the gray mid-gradient that RGB/HSL produce. For cylinders, set a hue strategy: color-mix(in oklch longer hue, …).oklch(from var(--brand) l c h / 0.5), or compute a shade/hover without a second hard-coded hex: oklch(from var(--brand) calc(l * 0.9) c h).light-dark(white, black) (requires color-scheme: light dark).@media (color-gamut: p3) { … }.linear-gradient(in oklch, red, blue).Broadly supported in evergreen browsers (2024+); relative color syntax is the newest piece. See references/techniques/ CSS Color 4/5 for edge cases.
Of ~281 trillion hex color pairs (research by @mrmrs\_, computed via a Rust brute-force run):
| Threshold | % passing | Odds | | ------------------------- | --------- | --------------- | | WCAG 3:1 (large text) | 26.49% | ~1 in 4 | | WCAG 4.5:1 (AA body text) | 11.98% | ~1 in 8 | | WCAG 7:1 (AAA) | 3.64% | ~1 in 27 | | APCA 60 | 7.33% | ~1 in 14 | | APCA 75 (fluent reading) | 1.57% | ~1 in 64 | | APCA 90 (preferred body) | 0.08% | ~1 in 1,250 |
APCA is far more restrictive than WCAG at comparable readability. At APCA 90, only 239 billion of 281 trillion pairs work. JPEG compression exploits the same biology: chroma subsampling (4× less color data) is invisible because human vision resolves brightness at higher resolution than color.
Charts — the border trick. WCAG wants 3:1 between adjacent non-text elements. Finding three chart colors that hold 3:1 against each other is extremely hard; beyond three it's essentially impossible. Don't try. Put a border on the chart elements and require 3:1 between each fill and the border color — one constraint per color instead of N². (Ström, see references/techniques/strom-least-wrong-colors-simulated-annealing.md.)
Complementary, triadic, tetradic intervals are weak predictors of mood, legibility, or accessibility on their own. Every hue plane has a different shape in perceptual space, so geometric hue intervals do not guarantee perceptual balance.
Organize by character (pale/muted/deep/vivid/dark), not hue. Finding: hue is usually a weaker predictor of emotional response than chroma and lightness — a muted palette often reads as calm across many hues. Relaxed vs intense is driven more by chroma + lightness than hue alone.
Grayscale is a quick sanity check for lightness separation, not an accessibility proof. You still need to verify contrast with WCAG/APCA and consider text size, weight, polarity, and CVD. Same character + varied lightness is often more readable. Same lightness regardless of hue is usually illegible.
60% dominant color, 30% secondary, 10% accent. One color dominates to prevent "three equally-sized gorillas fighting."
| System | Register | Example | | --------------------- | -------------------------- | ---------------------------------- | | ISCC-NBS | Scientific precision | "vivid yellowish green" | | Munsell | Systematic notation | "5GY 7/10" | | XKCD | Common perception | "ugly yellow", "hospital green" | | Traditional Japanese | Cultural/poetic | "wasurenagusa-iro" (forget-me-not) | | RAL | Industrial reproducibility | RAL 5002 | | Ridgway (1912) | Ornithological | 1,115 named colors, public domain | | CSS Named Colors | Web standard | 147 named colors | | color-description lib | Emotional adjectives | "pale, delicate, glistening" | | colornames-oklab | Perceptually even coverage | "Smaragdine" (rec2020 tier) |
Use color-name-lists npm package for 18 naming systems in one import. For naming arbitrary or generated colors — especially wide-gamut — use colornames-oklab: 4444 names blue-noise sampled over the Rec2020 gamut in OKLab, so no query lands far from a name (crowd-sourced lists cluster in reds/skin tones/pastels and leave gamut regions empty). Tiered srgb/p3/rec2020, zero-dep closest() with a unique-assignment mode for palettes.
Note: coolors.co does not generate palettes — it picks randomly from 7,821 pre-made palettes hardcoded in its JS bundle.
fromColor() inverse-solves the model so the ramp passes exactly through a color you already have (brand color → dataviz ramp)<poline-palette> web component for interactive controlscolor(t) = a + b*cos(2π(c*t+d)), 12 floats = infinite palettenpx categorycolors run; pluggable evaluators (energy, range, JND, similarity, WCAG contrast, avoid-these-colors, saliency), per-channel locking so a brand hue survives while lightness/saturation move, and categorycolors report to audit any existing palette for JND collisions under simulated CVD. Node + Culori, MITrelC: 0.5 = "halfway to the gamut boundary" at any lightness/hue. The one OkHSL idea (boundary-relative saturation) without leaving native oklch() — LUT-backed (faster than OkHSL's runtime gamut math), zero runtime deps, sRGB + P3@colordx/core) — chainable CSS-facing manipulation with OKLCH native, 7.6 kB, 0 deps, plugin-gated extras (Lab/LCH/CMYK/P3/Rec.2020/APCA/harmonies). Keeps out-of-gamut OKLCH unclamped and lets you pick the fold: .mapSrgb() (CSS Color 4 chroma reduction, holds L+H) vs .clampSrgb() (naive clip, what browsers render). Zero-allocation *Into channel converters for per-pixel work. Note .mix() is sRGB — use .mixOklab()maxChromaLUT stretches the chroma axis to the gamut shell (nutelch's relC idea, GPU-side). Reach for it when building a picker UI or any wide-gamut visualizationImageData + Python/TensorFlow perceptual palette embeddings for search-by-color (Google Arts & Culture, Apache 2.0)Sorting an arbitrary set of colors into a perceptually smooth sequence has no single correct linear order — color is 3D, so any 1D ordering is a path through a 3D space, closely related to the Travelling Salesman Problem. Naive sorts fail predictably: by hue alone interleaves lights and darks; by lightness alone collapses distinct hues; .sort() on a packed RGB/HSL value produces jagged, meaningless jumps. Don't hand-roll a single-channel sort.
references/techniques/colorsort-js.md.@adobe/leonardo-contrast-colors npm lib for runtime adaptive theming. By Nate Baldwin / Adobe.See references/INDEX.md for the detailed files organized as:
historical/ — Ostwald, Helmholtz, Bezold, Ridgway 1912, ISCC-NBS, Munsell, Albers, Caravaggio's pigments, Moses Harris, Lewis/Ladd-Franklincontemporary/ — Ottosson's OKLAB articles, Briggs lectures, Fairchild, Hunt, CIECAM02, MacAdam ellipses, Koenderink 2026 empirical 3D metric field (RGB supports ~1,000 qualitative regions; cool side coarser than warm; chromatic circle is not well-tempered), Pointer's gamut, CIE 1931/standard observer, Pixar Color Science, Acerola, Juxtopposed, Computerphile, bird tetrachromacy, OLO, GenColor paper. Full scrapes: huevaluechroma.com and colorandcontrast.comtechniques/ — All tools above documented in detail, plus: CSS Color 4/5, ICC workflows, Tyler Hobbs generative color, Harvey Rayner Fontana approach, Goethe edge colors as design hack, mattdesl workshop + K-M simplex, CSS-native generation, IQ cosine presets, Erika Mulvenna interview, Bruce Lindbloom math reference, image extraction tools, Aladdin color analysisOther measured skills in the registry, with their headline benchmark lift.