01

Start with the color space, not the punctuation

A CSS color token combines a color space, coordinates in that space, and sometimes an alpha component. The visible punctuation is only the serialization. Six-digit HEX stores three sRGB bytes, while rgb() expresses the same red, green, and blue channels as numbers or percentages. HSL and HWB are alternative cylindrical or intuitive views of sRGB. lab() and oklch() are not just reformatted RGB: the browser converts their coordinates through color management before compositing and display. Therefore a textual conversion should preserve both coordinates and the intended color space.

Named colors and the transparent keyword are useful constants, but they do not provide a scalable editing model. A named color such as rebeccapurple resolves to a specified sRGB value; its name does not carry semantic information about a product role. For a reusable system, put the chosen value behind a custom property whose name describes purpose. That separation lets a team change notation without renaming every component, and it prevents accidental claims that two numerically similar values belong to the same color space or produce the same rendered color.

02

HEX and rgb() expose sRGB directly

HEX uses #RRGGBB or #RRGGBBAA, with each hexadecimal pair ranging from 00 to FF. rgb() uses the same sRGB channels and supports modern space-separated syntax, optional percentages, and a slash before alpha. Short #RGB and #RGBA forms are exact only when each expanded pair repeats one digit. They are not a rounding mechanism. Converting #4F46E5 to rgb(79 70 229) is exact because 4F, 46, and E5 equal 79, 70, and 229. Converting rounded HSL coordinates back may change a byte.

css
.syntax-demo {
  --accent: #4f46e5;
  color: rgb(79 70 229 / 85%);
  border-color: hsl(243.4 75.4% 58.6%);
  outline-color: hwb(243.4 27.5% 10.2%);
}
03

HSL and HWB are convenient sRGB controls

hsl() uses hue, saturation, and HSL lightness; hwb() uses hue, whiteness, and blackness. Both ultimately describe sRGB colors. Hue angles wrap around, and powerless hue can occur when the remaining channels produce an achromatic result. HSL lightness is not a measurement of perceived brightness, so equal numerical steps do not guarantee equal-looking steps. HWB can be practical when a designer thinks in terms of mixing white or black into a hue, but it has the same sRGB gamut and the same need for contrast testing.

  • hsl() and hwb() are sRGB notations, not wider-gamut spaces.
  • HSL lightness is not perceived lightness and cannot replace a contrast calculation.
  • A powerless or missing hue needs deliberate handling during interpolation.
  • Rounded conversions can move channel values even when the intended color is unchanged.
04

Lab and OKLCH describe different coordinates

lab() uses CIE lightness with a and b opponent axes. lch() expresses a similar model as lightness, chroma, and hue. oklab() and oklch() use the OKLab model, designed so coordinate differences behave more regularly for many editing tasks. These functions can specify colors that do not fit in sRGB. A browser may map an out-of-gamut result for the actual display. The source coordinates remain meaningful, but a screenshot on an sRGB display cannot prove that every user saw the same wide-gamut color.

SyntaxCoordinate modelTypical use
#RRGGBBsRGB bytesExact compact sRGB value
rgb()sRGB channelsChannels and alpha
hsl() / hwb()sRGB-derived controlsManual hue-oriented edits
lab() / oklch()Perceptual coordinatesOrganized or wider-gamut workflows
05

Modern separators and alpha

Modern functional notation separates channels with spaces and places alpha after a slash. Legacy comma syntax remains supported for compatibility in rgb() and hsl(), but mixing comma and space grammars in one function is invalid. The keyword none can represent a missing component in syntax where CSS Color 4 permits it; missing is not automatically the same concept as numeric zero during interpolation. Percentages also depend on the function. Copy the grammar for the selected function instead of assuming that every channel accepts the same range or unit.

css
.wide-gamut {
  color: #4f46e5;
}

@supports (color: oklch(0% 0 0)) {
  .wide-gamut { color: oklch(62% 0.2 265); }
}
06

Choose syntax for maintenance

Use HEX when an exact opaque sRGB value arrives from a byte-oriented source and compact review matters. Use rgb() when code manipulates sRGB channels or needs an obvious alpha. Use hsl() or hwb() for constrained edits that match those models, while remembering their perceptual limitations. Use oklch() when a workflow benefits from lightness, chroma, and hue coordinates with more perceptual regularity. Use lab() when that coordinate system or an interoperability requirement is explicit. A design token can hide the serialization from components.

  • Record the color space together with numeric coordinates.
  • Keep a stable semantic custom-property name above the raw notation.
  • Provide a tested fallback when the supported browser set requires one.
  • Measure contrast after alpha compositing against the real background.
07

Test the computed result

Verification should inspect the browser’s computed or resolved color, not only the source string. Test fallbacks on the browsers in scope, check out-of-gamut values on representative displays, and measure text contrast from the final composited colors. Alpha, backgrounds, filters, blend modes, and color-scheme can change the result after parsing. Automated tests can assert token presence and syntax support, but visual and accessibility checks still need the rendered context. Keep the original source value when repeated conversions would otherwise accumulate rounding differences.

S

Primary sources

The factual claims in this article are checked against these published specifications.

  1. W3C CSS Color 4 — color syntax