Skip to content

Architecture: a value stays in its space

Color space conversions lose precision: floats round, and a conversion cannot invent what a color does not carry — every hue of a grey is the same sRGB color. Industry-standard tools (CSS Color Level 4, color.js, Sass) solve this the same way we do:

Store colors in their authored color space. Convert forward only. Never convert back.

A ColorValue is always in one space, and the state holds whichever value was last written or edited. A slider edits in its own channel's space: the color is converted there once, the channel is set, and the result stays there. Reading another space converts forward, once, from that value; nothing is re-derived from a conversion.

flowchart LR
    drag["The user drags the OkLCh chroma slider"] -->|"set chroma"| value[("The value, converted into OkLCh once")]
    value -->|"one conversion"| okhsl["The Okhsl sliders"]
    value -->|"none"| oklch["The OkLCh sliders"]

This means:

  • Editing in a space and reading that space back gives exactly the value set
  • Cross-space reads involve a single forward conversion, never a round-trip
  • No precision loss accumulates over time, regardless of how many edits are made
  • A value outside sRGB and a none component are kept until something writes over them

For more details, see: