colors #

fuz_css's colors are derived: a handful of high-leverage knobs produce every color variables in pure CSS, in the oklch colorspace, adapting to the color-scheme automatically. OKLCH lightness is perceptually uniform (equal lightness reads equally light in every hue), so rotating a hue knob is safe: contrast and visual weight hold.

Hues use letters so themes can reassign colors without breaking semantics ("a" is blue by default but could be any color). Meaning attaches through the intent knobs layered on top: --hue_accent (links, focus, selection, selected states - what other systems call "primary") defaults to --hue_a, --hue_negative to --hue_c, and so on. Retarget an intent to move just that meaning; rotate a letter to move the palette.

Hue variables
#

Hue variables contain a single OKLCH hue angle. Because lightness and chroma are shared across all hues at each stop, the scales are interchangeable: setting a hue alone is enough, no per-hue tuning required. The one deliberate exception is the per-slot chroma multiplier - the brown slot ships muted because no hue angle renders brown at full palette chroma.

Hue variables are also useful to construct custom colors not covered by the palette. For example, fuz_css's selection color derives from --hue_accent (try selecting some text - same hue!).

Hue variables are the same in both light and dark modes (non-adaptive).

  • 250
    blue · default accent
  • 144
    green · default positive
  • 29
    red · default negative
  • 296
    purple
  • 98
    yellow
  • 60
    brown · default neutral
  • 358
    pink
  • 41
    orange · default caution
  • 210
    cyan · default info
  • 180
    teal

Palette variables
#

Each hue has intensity variants from subtle to bold (00, 05, 10, 20, ..., 80, 90, 95, 100), with 50 at the middle of the ramp. The 60 variant is the text-safe stop: links, the labels of .palette_X buttons and chips, and a selected .palette_X button's fill use it, and check_theme gates those pairings at AA.

Palette variables work for both text and backgrounds through utility classes: .color_a_50 sets the text color and .bg_a_50 the background, and the intent scales below do the same. The shade and text scales are split by role instead, one for surfaces and one for text.

Palette stops are adaptive: they switch between light and dark ramps based on color scheme. There are no absolute variants; for a color that doesn't adapt, write the literal color or define one custom property.

Intent variables
#

Intent scales name colors by meaning: --accent_NN, --positive_NN, --negative_NN, --caution_NN, and --info_NN, with the palette's stops, derived through the same ramps from each intent's hue knob (--hue_accent and the rest). Each hue binds to a palette letter by default, so an intent scale matches its letter's until a theme rebinds it: accent → a, positive → b, negative → c, caution → h, info → i.

Reach for an intent when the color carries meaning, like an error or a success, and for a letter when it's only a color, so a theme can move the meaning without recoloring every use of the letter. The classes follow the palette's: .positive_50 sets the text color and .bg_positive_50 the background. Border, outline, and shadow classes exist only for the letters, so reach an intent there with a literal like border-color:var(--negative_50).

Color knobs
#

The knobs are the theme API, and the first few carry most themes:

  • intent hues - --hue_accent, --hue_positive, --hue_negative, --hue_caution, --hue_info - each pointing a meaning at a palette letter (var(--hue_c)) or a literal angle, and deriving its full stop scale (--accent_00 … --accent_100)
  • --hue_neutral + --neutral_chroma - the temperature and strength of every surface, text, border, and shadow tint (the neutral intent, whose scales are the shade and text ramps)
  • --chroma_scale - one multiplier from grayscale (0) through calm (1) to vivid (above 1, deliberately clipping the weakest hues)
  • lightness ramps - --palette_lightness_00/_100/_curve (and the same trio for shade_ and text_): the endpoint stops plus a curve exponent bending the ramp between them, per color scheme
  • chroma curve - --palette_chroma_min/_max and --chroma_curve: a mid-peaked curve, clamped per stop by gamut caps computed from the worst hue
  • per-slot chroma multipliers - --palette_a_chroma_scale … --palette_j_chroma_scale and intent twins (--accent_chroma_scale, …), each multiplying one slot's chroma under --chroma_scale. A binding shares only the hue angle, so an intent bound to the muted brown slot needs its twin set too, and validate_theme warns when it isn't
  • --hue_a … --hue_j - the palette letters' angles. Rotating one recolors every use of that letter, so registered themes leave them alone and move intents instead

Every intermediate value these produce is also its own variable (--palette_lightness_30, --palette_chroma_50, …), so a theme can pin any individual stop as an escape hatch.

// a warm, slightly vivid theme in three moves const warm_theme: Theme = { name: 'warm', variables: [ {name: 'hue_neutral', light: '55'}, {name: 'neutral_chroma', light: '0.03'}, {name: 'chroma_scale', light: '1.15'}, ], };