Color Function
Edit the modern CSS color functions — color-mix(), light-dark(), and relative color — with compile-time validation that dispatches on the leading function name. color-mix(in srgb shorter hue, …) → never (hue method on a rectangular space); oklch(from #f00 r g b) → never (rgb channels in an oklch relative color); rgb(255 0 0) → never (a bare color is color-picker's job). The strict tier validates each family's grammar to never on violation.
Build a modern color function
Controlled value + onChange. Pick the interpolation colorspace, the two colors and their mix ratio — the editor emits a valid color-mix() string, and the trigger swatch shows the resolved color.
color-mix(in oklch, oklch(0.7 0.2 30) 60%, oklch(0.6 0.2 250))Each family, edited live with a result swatch
Three modes, three panels. Every control composes the typed string; the swatch beneath each renders the produced value as a computed background-color. The light-dark() swatch carries a scheme toggle so the browser resolves it both ways.
color-mix(in oklch, oklch(0.75 0.18 35) 50%, oklch(0.6 0.2 255) 50%)oklch(from oklch(0.7 0.2 30) l c calc(h + 180))light-dark(#fef3c7, #1e293b)Three usage tiers
From useState-and-go to per-family grammar validation at compile time.
Pass any string
useState<string>. No compile-time validation; the runtime parser handles whatever you type — including var() colors and bare keyword colors like white.
color-mix(in srgb, var(--brand), white 20%)const [value, setValue] = useState<string>("color-mix(in srgb, var(--brand), white 20%)")
Suggestion-shaped hints
State typed as ColorFunctionString — the union of the three family shapes. It is also the onChange return type, and it narrows to one family via the mode prop. Precise validation lives in the strict tier.
light-dark(#ffffff, #1a1a1a)const [value, setValue] = useState<ColorFunctionString>("light-dark(#ffffff, #1a1a1a)")
Per-family grammar at compile time
cssColorFn() dispatches on the function name and resolves a bad colorspace, a hue method on a rectangular space, or wrong relative-color channel keywords to never — a type error before you run the code.
color-mix(in oklch shorter hue, #ff0000, #0000ff)cssColorFn("color-mix(in oklch shorter hue, #f00, #00f)") // ✓ // @ts-expect-error hue method on a rectangular space cssColorFn("color-mix(in srgb shorter hue, …)") // @ts-expect-error rgb channels in an oklch relative color cssColorFn("oklch(from #f00 r g b)")
Note: calc() channel bodies and channel magnitudes are weak-validated — any balanced calc(…) passes. The runtime parser is equally tolerant.
API
Public surface — component props, runtime helpers, and the type exports.
§ ColorFunction / ColorFunctionPanel
<ColorFunction
mode?: 'color-mix' | 'relative' | 'light-dark'
value: ColorFunctionStringMap[mode] | (string & {})
onChange: (next: ColorFunctionStringMap[mode]) => void
className?: string
aria-label?: string
/>ColorFunction is popover-wrapped; ColorFunctionPanel renders the same editor inline. Both are controlled. With mode set the panel shows only that family and onChange narrows; omit it for a family selector that edits any of the three.
| Prop | Type | Description |
|---|---|---|
| mode | 'color-mix' | 'relative' | 'light-dark' | Optional. Which family to edit. Narrows the onChange string type; when omitted the panel shows a family <select>. |
| value | ColorFunctionStringMap[mode] | (string & {}) | Current CSS color-function value. Required. |
| onChange | (next: ColorFunctionStringMap[mode]) => void | Fires when the edited value changes. Emits the canonical string for the family. |
§ Sub-components
<ColorMixEditor state onChange />
The color-mix editor: interpolation-space select, a hue-method select (cylindrical spaces only), two ColorPickers with mix-ratio sliders, and a swap-colors affordance. Emits a ColorMixState.
<RelativeColorEditor state onChange />
The relative-color editor: function select, a from-color ColorPicker, three channel inputs (with per-function keyword placeholders), an optional color() space ident, and an alpha input. Emits a RelativeColorState.
<LightDarkEditor state onChange />
Two ColorPickers — the light-scheme and dark-scheme colors. Emits a LightDarkState.
<ColorFunctionPreview value />
The result swatch — renders the produced string as a computed background-color, with a color-scheme toggle for light-dark().
§ Runtime helpers
cssColorFn<S extends string>(value: S & ColorFunctionLiteral<S>): S
Call-site validator. Mirrors cssBoxShadow() / cssFilter() / color().
parseColorFunction(src: string): ColorFunctionState | null
String → typed state, or null on a syntax / unknown-function / arity error. Tolerant of calc()/var() and bare keyword colors; lenient on channel magnitudes.
formatColorFunction(state: ColorFunctionState): string
Canonical re-serialization for each family (parse ∘ format is idempotent).
colorFunctionKind(src: string): 'color-mix' | 'relative' | 'light-dark' | null
Runtime mirror of KindOf — the family of a value, or null.
defaultState(mode): ColorFunctionState
Seed a fresh value per mode (each round-trips through format).
MIX_COLOR_SPACES / CYLINDRICAL_SPACES / HUE_METHODS / RELATIVE_FNS / CHANNEL_KEYWORDS
The dispatch tables that drive both the parser and the UI.
§ Types
- ColorFunctionLiteral<S>
- Strict validator — dispatches on the leading function name into ColorMixLiteral / LightDarkLiteral / RelativeColorLiteral; S on success, never otherwise.
- ColorMixLiteral<S>
- Full color-mix validation: in <space> with an optional <method> hue (cylindrical spaces), then two <color> <pct>?.
- LightDarkLiteral<S>
- Full light-dark validation: exactly two <color> arguments.
- RelativeColorLiteral<S>
- Relative-color validation with per-space channel keywords; lenient on calc() bodies and magnitudes.
- ColorFunctionString / …StringMap / …Mode
- Suggestion union, the mode→string map, and the mode key — the onChange return type.
- KindOf / MixSpaceOf / RelativeFnOf / ColorsOf
- Literal-level operators: the family, the mix colorspace, the relative function, and the raw color arguments.
- ColorFunctionState
- Discriminated-union state (color-mix | relative | light-dark), exported for advanced use.
§ Strict-tier scope
- color-mix() and light-dark() are fully validated: the 14-member interpolation colorspace set, a
<method> huesuffix only on the four cylindrical spaces (hsl / hwb / lch / oklch), exactly two colors with optional trailing<percentage>weights. - Relative color enforces per-space channel keywords (
oklch → l c h,rgb → r g b, …) and thefromkeyword + source color + exactly three channels + optional/ <alpha>. - Weak-validated (documented):
calc()channel bodies are not parsed (any balancedcalc(…)passes), channel magnitudes are not range-checked, and thecolor()colorspace ident is accepted leniently.var()is accepted wherever a<color>is expected. - A bare color literal (
rgb(255 0 0)with nofrom) resolves tonever— that is color-picker's domain, not this component's.
Drop it in
One command via the shadcn CLI.
$ pnpm dlx shadcn@latest add https://turtiesocks.github.io/ridiculous/r/color-function.json