Docs

This page isn't translated yet

next

Color palettes

Generate 11-stop palettes from a brand color with `@systhemaui/core/color` or the CLI.

On this page

Systhema generates Tailwind-style 11-stop palettes (50 to 950) from a seed color, byte-exact with uicolors.app for the same input. The same generator powers the CLI, Systhema Design and your own code.

From the CLILink to this section

systhema generate-palette prints a palette as JSON, TypeScript or DTCG tokens. See systhema generate-palette.

In Systhema DesignLink to this section

The Colors section of Systhema Design seeds primitive palettes from a color and lets you move each palette's foundation anchor.

In codeLink to this section

Color math shared by the systhema generate-palette CLI command and the Systhema Design app — both import it from here. The palette generator is a byte-exact reproduction of uicolors.app (opens in new tab)'s Tailwind generator (it uses the same library, chroma-js, confined to this entry); the lower-level hex converters are pure and dependency-free.

import {
  generatePalette,
  generatePaletteAnchored,
  classifyInputStop,
  hexToRgbaString,
  hexToOklchString,
  parseHex,
  hexToHsl,
  hslToHex,
  hexToOklab,
  type Palette,
  type Stop,
} from '@systhemaui/core/color'

generatePalette(hex)Link to this section

Generate an 11-stop (50–950) Tailwind-style ramp from a single seed hex, identical to uicolors.app's output for the same input. The seed is matched to its closest reference family (22 Tailwind families + 2 extras, by CIEDE2000 ΔE), anchored byte-exact at the family shade closest to it in lightness, and the other ten stops are the family recoloured to the seed's hue and saturation. Throws on invalid hex.

generatePalette('#8b54ec')['500'] // '#8B54EC' — the seed lands on its anchor stop
generatePalette('#8b54ec')        // { '50': '#F6F4FE', …, '500': '#8B54EC', …, '950': '#341560' }

classifyInputStop(hex) returns just the stop ('50'…'950') a hex anchors to (its matched family's shade closest in lightness).

generatePaletteAnchored(hex, anchor)Link to this section

Like generatePalette, but you choose which stop the seed lands on instead of accepting its automatic classification. The seed is placed byte-exact at anchor and the other ten stops are generated around it.

generatePaletteAnchored('#8b54ec', '700')['700'] // '#8B54EC' — the seed locks to 700

generatePalette(hex) is exactly generatePaletteAnchored(hex, classifyInputStop(hex)) — passing the seed's natural classification reproduces the automatic ramp byte-for-byte. Use the anchored form when you want to lock a seed to a specific shade (for example, treating a brand color as the dark 700 rather than the mid-toned 500). anchor is a Stop ('50'…'950'). Throws on invalid hex.

Color-format convertersLink to this section

parseHex accepts #rgb, #rrggbb, and #rrggbbaa and returns { r, g, b, a } (RGB 0–255, alpha 0–1). The string converters emit CSS Color 4 syntax; both append a / alpha component only when the input is translucent.

parseHex('#00000040')              // { r: 0, g: 0, b: 0, a: 0.251 }

hexToRgbaString('#FF0000')         // 'rgb(255 0 0)'
hexToRgbaString('#00000040')       // 'rgb(0 0 0 / 0.251)'

hexToOklchString('#FF0000')        // 'oklch(0.628 0.2577 29.23)'

hexToOklchString takes an optional second argument to control the rounding precision of each component, which is useful for compact UI readouts:

hexToOklchString('#FF0000', { lDigits: 3, cDigits: 3, hDigits: 1, aDigits: 2 })
// 'oklch(0.628 0.258 29.2)'

Each option is the number of decimal places for one component — lDigits (lightness), cDigits (chroma), hDigits (hue), aDigits (alpha). Omitting the argument, or any individual option, keeps the defaults (lDigits: 4, cDigits: 4, hDigits: 2, aDigits: 3), so existing calls produce byte-identical output.

hexToHsl / hslToHex (with the HSL type and roundHsl helper) and hexToOklab / hexToOklabL (with the OKLab type) round out the lower-level conversions (the latter back the OKLCH formatter).