Color

Color math without hand-rolled hex parsing and RGB mixing. Colors in are "#rrggbb" / "#rrggbbaa" strings or { r, g, b, a } tables (0-255, a optional); colors out are { r, g, b, a } tables unless the name says hex. Mixing and adjusting default to OKLab, a perceptual space where equal steps look equal; `space` can be "oklab", "oklch", "rgb" or "hsl". Results are clamped to sRGB by reducing chroma, so hue and lightness hold. Lists (ramp, shades) are built in one call. (11 functions)

Color math without hand-rolled hex parsing and RGB mixing. Colors in are "#rrggbb" / "#rrggbbaa" strings or { r, g, b, a } tables (0-255, a optional); colors out are { r, g, b, a } tables unless the name says hex. Mixing and adjusting default to OKLab, a perceptual space where equal steps look equal; space can be "oklab", "oklch", "rgb" or "hsl". Results are clamped to sRGB by reducing chroma, so hue and lightness hold. Lists (ramp, shades) are built in one call.

app.color.adjust(c, opts?) — pure

Changes a color's lightness, chroma, hue, saturation or alpha, absolutely or relatively. The result is gamut-clamped to sRGB by reducing chroma (lightness and hue are kept), not by clipping channels.

  • c (color) — A color: "#rrggbb", "#rrggbbaa" or { r, g, b, a }.
  • opts (table, optional) — Absolute keys set a value; the d-prefixed keys add to it (the set is applied first, then the delta). Omitted keys keep the color's own value.
    • lightness (number) — Set the lightness: OKLab/OKLCH L (0 black - 1 white), or HSL lightness with space = "hsl".
    • chroma (number ≥ 0) — Set the OKLCH chroma (colorfulness, 0 = gray; sRGB tops out near 0.32). Not available with space = "hsl".
    • hue (number) — Set the hue angle in degrees (wraps around 360).
    • saturation (number 0–1) — Set the saturation 0-1: HSL saturation with space = "hsl"; otherwise chroma as a fraction of the most chroma sRGB allows at that lightness and hue.
    • alpha (number 0–255) — Set the alpha, 0-255.
    • dLightness (number) — Add to the lightness (negative darkens).
    • dChroma (number) — Add to the chroma.
    • dHue (number) — Rotate the hue by this many degrees.
    • dSaturation (number) — Add to the saturation (0-1 scale).
    • dAlpha (number) — Add to the alpha (0-255 scale).
    • space (string; default "oklab") — "oklab" | "oklch" (the same here) | "hsl". "rgb" has no lightness or hue axis and is rejected.

Returns (table): { r, g, b, a }, each 0-255.

app.color.contrast(a, b) — pure

WCAG contrast ratio between two colors (alpha ignored), from 1 (identical) to 21 (black on white). 4.5 is the usual minimum for readable text.

  • a (color) — A color: "#rrggbb", "#rrggbbaa" or { r, g, b, a }.
  • b (color) — A color: "#rrggbb", "#rrggbbaa" or { r, g, b, a }.

Returns (number): The ratio, 1-21.

app.color.darken(c, amount, space?) — pure

Shortcut for app.color.adjust(c, { dLightness = -amount }).

  • c (color) — A color: "#rrggbb", "#rrggbbaa" or { r, g, b, a }.
  • amount (number) — Subtracted from the lightness (OKLab L units, 0-1).
  • space (string, optional) — Where lightness is measured: "oklab" (default), "oklch" or "hsl".

Returns (table): { r, g, b, a }, each 0-255.

app.color.distance(a, b) — pure

Euclidean distance between two colors in OKLab (alpha ignored). 0 = identical; black to white is 1; about 0.02 is barely noticeable.

  • a (color) — A color: "#rrggbb", "#rrggbbaa" or { r, g, b, a }.
  • b (color) — A color: "#rrggbb", "#rrggbbaa" or { r, g, b, a }.

Returns (number): The distance, 0 or more.

app.color.hex(c, withAlpha?) — pure

A color as a lowercase hex string.

  • c (color) — A color: "#rrggbb", "#rrggbbaa" or { r, g, b, a }.
  • withAlpha (boolean, optional) — true for "#rrggbbaa"; default "#rrggbb".

Returns (string): "#rrggbb", or "#rrggbbaa" with withAlpha.

app.color.lighten(c, amount, space?) — pure

Shortcut for app.color.adjust(c, { dLightness = amount }).

  • c (color) — A color: "#rrggbb", "#rrggbbaa" or { r, g, b, a }.
  • amount (number) — Added to the lightness (OKLab L units, 0-1).
  • space (string, optional) — Where lightness is measured: "oklab" (default), "oklch" or "hsl".

Returns (table): { r, g, b, a }, each 0-255.

app.color.mix(a, b, t, space?) — pure

Blends two colors (alpha too) in a perceptual space; OKLab by default, which avoids the muddy midpoints of RGB mixing.

  • a (color) — A color: "#rrggbb", "#rrggbbaa" or { r, g, b, a }.
  • b (color) — A color: "#rrggbb", "#rrggbbaa" or { r, g, b, a }.
  • t (number) — 0 = a, 1 = b; clamped to 0-1.
  • space (string, optional) — Interpolation space: "oklab" (default), "oklch" (hue travels the short way round), "rgb" or "hsl".

Returns (table): { r, g, b, a }, each 0-255.

app.color.nearest(c, palette) — pure

The palette entry closest to c by OKLab distance (alpha ignored). Ties go to the earlier entry.

  • c (color) — A color: "#rrggbb", "#rrggbbaa" or { r, g, b, a }.
  • palette (table) — A non-empty list of colors.

Returns (number): The 1-based index into palette.

app.color.parse(c) — pure

Normalizes any accepted color to { r, g, b, a } (a = 255 when the input has none).

  • c (color) — A color: "#rrggbb", "#rrggbbaa" or { r, g, b, a }.

Returns (table): { r, g, b, a }, each 0-255.

app.color.ramp(stops, n, space?) — pure

A gradient sampled into a list of n colors, first to last stop, both endpoints included. One call regardless of n.

  • stops (table) — A list of colors, or of { color =, pos = } with pos 0-1 (stops without pos are spaced evenly). Needs at least one.
  • n (number) — How many colors to return, 1-4096. 1 returns the first stop.
  • space (string, optional) — Interpolation space: "oklab" (default), "oklch" (hue travels the short way round), "rgb" or "hsl".

Returns (table): A list of n { r, g, b, a } colors.

app.color.shades(base, n, opts?) — pure

A shadow-to-highlight list of n colors around base, for shading. The base sits at index floor(n / 2) + 1 (the middle for odd n) and is returned exactly. With f running from -1 (darkest) through 0 (base) to +1 (lightest), shade i has OKLab lightness L + f * spread / 2 (clamped to 0-1), hue H + f * hueShift, and chroma C * (1 - 0.4 * f^2) so the extremes stay inside sRGB (anything still outside loses chroma, never lightness). Alpha is kept. Lightness is monotonic along the list.

  • base (color) — A color: "#rrggbb", "#rrggbbaa" or { r, g, b, a }.
  • n (number) — How many colors to return, 1-4096.
  • opts (table, optional)
    • spread (number; default 0.5) — Total OKLab lightness range, shadow to highlight (the base is the middle: half the spread down, half up). Clamped to 0-1.
    • hueShift (number; default 8) — Degrees the hue turns at the extremes: shadows by -hueShift, highlights by +hueShift, scaling linearly to 0 at the base.

Returns (table): A list of n { r, g, b, a } colors, darkest first.

As Markdown: color.md.