The HSL Color Model, Explained Simply
Every time you open a colour picker and drag a slider around a rainbow ring, you are working with HSL. It stands for hue, saturation, and lightness — three numbers that describe a colour in the way a designer thinks rather than the way a screen thinks. RGB tells a monitor how much red, green, and blue light to emit. HSL tells you what colour family you are in, how vivid it is, and how light or dark it sits. That shift in perspective makes colour manipulation far more intuitive, which is why HSL is the foundation of most professional colour pickers and a key tool for anyone building random colour palettes or systematic UI colour scales.
Hue: the Colour Wheel Angle
Hue is measured as a degree on a 360-degree circle. Think of it as pointing at a position on a rainbow bent into a ring. At 0 degrees you get red. Travelling clockwise, you reach yellow at 60 degrees, green at 120, cyan at 180, blue at 240, magenta at 300, and then back to red at 360. Every hue you can name — coral, teal, lavender, amber — lives somewhere on that wheel as a specific angle.
The practical power of this representation is arithmetic. Want the colour directly opposite on the wheel? Add 180 degrees. Want a three-colour split-complementary scheme? Add and subtract 150 degrees. Want an analogous palette that feels harmonious? Step by 30 degrees in each direction. None of these operations are possible in RGB without converting to another space first, but in HSL they are single additions.
The hue angle also makes it easy to understand why certain colour combinations work. Colours close together on the wheel share undertones and tend to feel cohesive. Colours far apart create contrast and tension. The random colour generator on this site shows the HSL values of every result so you can see the hue angle directly.
Saturation: From Grey to Vivid
Saturation controls how vivid or muted a colour appears, expressed as a percentage from 0 to 100. At S=0%, every hue becomes the same neutral grey — the hue angle is still stored in the value but has no visible effect. At S=100%, you get the most intense version of the hue that the sRGB colour space allows at the current lightness.
Reducing saturation is how you create toned-down, sophisticated versions of a colour. A fully saturated red at hsl(0, 100%, 50%) is visually aggressive. Dropping saturation to 40% gives you a brick red that feels warmer and more settled. Dropping further to 10% gives you a near-neutral with just a hint of warmth — the kind of colour used for backgrounds in interfaces that want to feel slightly warm without being colourful.
One important constraint: the full range of saturation only has meaningful visual effect near the middle of the lightness scale. When lightness approaches 0 or 100, all colours converge on black or white regardless of saturation. This coupling between S and L is a known limitation of the HSL model that more advanced spaces like OKLCH address.
Lightness: Black to White Through the Full Colour
Lightness runs from 0% (absolute black) through 50% (the pure colour at full saturation) to 100% (absolute white). It is computed from RGB as the average of the highest and lowest channel values — so a colour like hsl(210, 80%, 55%) has its lightness determined by finding the brightest and darkest of its red, green, and blue components, then taking their midpoint.
This makes lightness the most directly useful axis for building colour scales. If you fix H and S and sweep L from 10% to 90% in steps of 10, you get a nine-step tint-and-shade ramp for a single colour family. This is exactly how design token systems like Tailwind's default colour palette work: each colour name maps to a fixed hue and saturation, with numbered variants (100 through 900) representing steps along the lightness axis.
The important caveat is that HSL lightness is not the same as perceived brightness. The human eye is far more sensitive to green than to blue, so two colours at L=50% can look dramatically different in apparent brightness. A vivid yellow and a vivid blue have the same HSL lightness but the yellow looks much brighter to most observers. For accessibility work — checking contrast ratios — you need to use the WCAG relative luminance formula rather than relying on the L value.
The Geometry of HSL: Double Cone or Cylinder
HSL is often visualised as a double cone (two cones joined at their widest points) or as a cylinder. In the double-cone model, the vertical axis is lightness, the horizontal distance from the centre is saturation, and position around the circumference is hue. The cone narrows to a point at the top (white, L=100%) and at the bottom (black, L=0%), which visually represents how saturation loses meaning at the extremes.
The cylinder model keeps the same width at all lightness levels, which is mathematically simpler but slightly misleading because it implies that a highly saturated colour near L=10% is meaningfully different from one at L=90% — in practice both are very dark or very light and the saturation makes little visual difference.
Understanding this geometry helps when you are using a colour picker. The circular hue ring is a horizontal slice through the middle of the model. The saturation-lightness square (or triangle, in some pickers) is a vertical cross-section. Moving the selector left-right changes saturation; moving it up-down changes lightness.
Why HSL Beats RGB for Design Work
In RGB, making a colour lighter means raising all three channel values — but by how much, and in what proportion, depends on the starting colour. Desaturating in RGB means moving each channel toward the average of all three. Neither operation has an obvious single-knob control. HSL solves this by giving each of those operations its own axis.
The common design tasks that HSL makes straightforward include:
- Tints and shades: Fix H and S, vary L to create a full colour ramp from near-black to near-white.
- Complementary colours: Add 180 to the hue to get the colour directly opposite on the wheel.
- Triadic schemes: Add and subtract 120 from the hue for three evenly spaced colours.
- Hover and active states: Increase or decrease L by a fixed amount to create a consistent lighter or darker version of any button colour.
- Muted variants: Reduce S to create a more subdued version of a colour for secondary text or disabled states.
All of these are single-value edits in HSL. The colour palette generator on this site uses HSL internally to produce harmonious sets, then exposes both the HSL and hex colour codes so you can copy whichever format your project needs.
Converting Between HSL and RGB
Browsers and graphics libraries convert between HSL and RGB automatically, but understanding the direction of the conversion helps when debugging colour values. Going from RGB to HSL, the algorithm first finds the maximum and minimum values among the red, green, and blue channels. Lightness is their average, normalised to a 0-100% scale. Saturation is derived from the difference between max and min, divided by a factor that prevents the denominator from going to zero at the lightness extremes.
The hue is computed by looking at which channel holds the maximum value. If red is highest, the hue falls in the 0-60 or 300-360 range. If green is highest, it falls in the 60-180 range. If blue is highest, it falls in the 180-300 range. Within each range, the exact angle is determined by the relative difference between the two non-maximum channels.
Going from HSL back to RGB is a piecewise linear mapping. The algorithm computes two intermediate values based on lightness and saturation, then distributes the resulting channel values according to which 60-degree sector the hue falls in. The result is exact and lossless for values that can be represented in both spaces — there is no approximation involved.
HSL Limitations and When to Use Other Models
HSL is excellent for practical colour manipulation but has known limitations that matter in specific situations. The most important is perceptual non-uniformity: equal steps along any HSL axis do not produce equal steps in how different the colours look to a human observer. A 10-point lightness step from L=40% to L=50% looks larger than a step from L=80% to L=90%, even though both are the same numerical distance.
For data visualisation, accessibility tooling, and any context where you need colours that look equally distinct from one another, perceptually uniform spaces like CIELAB, HCL (Hue-Chroma-Luminance), or OKLCH are better choices. OKLCH in particular has gained significant traction in CSS because it is supported natively in modern browsers via the oklch() function and produces visually consistent results across all hues.
For typical UI work — building component colour systems, defining hover states, generating palette variants — HSL is practical and well-supported everywhere. The limitations only become significant when you are generating many colours that all need to appear equally prominent, or when precise contrast ratios matter for accessibility compliance. In those cases, compute in OKLCH or CIELAB and export to HSL or hex for delivery.
Using HSL in CSS
CSS has supported the hsl() function for a long time, and modern CSS also supports the hsla() variant for colours with transparency. The syntax is straightforward:
hsl(210 80% 55%)— modern space-separated syntaxhsl(210, 80%, 55%)— older comma-separated syntax, equally validhsl(210 80% 55% / 0.7)— with 70% opacity using the slash syntax
CSS custom properties pair particularly well with HSL because you can separate the hue, saturation, and lightness into individual variables and then recombine them. A common pattern is to store the hue and saturation of a brand colour in two variables and then define each shade as a variation on just the lightness, making theme changes a matter of updating a single hue value.
When building with design systems or utility frameworks, check whether your tool exposes HSL values alongside hex. Being able to work in HSL during design and export to hex for production is the most flexible setup — you get the ergonomics of HSL for iteration and the compactness of hex for delivery.
HSL vs HSV and RGB: Conversion Formulas Explained
HSL (Hue, Saturation, Lightness) and HSV (Hue, Saturation, Value) look similar but differ critically in how they define the top of the scale. In HSL a lightness of 100% is always white, regardless of saturation. In HSV a value of 100% with full saturation produces the pure hue — not white. This makes HSL more intuitive for typography and UI components, where you need predictable white and black endpoints.
RGB → HSL step by step. Given R, G, B each in [0, 1]:
- Let
Cmax = max(R,G,B),Cmin = min(R,G,B),Δ = Cmax − Cmin. - Lightness:
L = (Cmax + Cmin) / 2. - Saturation:
S = Δ / (1 − |2L − 1|)(0 when Δ = 0). - Hue: if Cmax = R →
H = 60 × ((G−B)/Δ mod 6); if Cmax = G →H = 60 × ((B−R)/Δ + 2); if Cmax = B →H = 60 × ((R−G)/Δ + 4).
HSL → RGB inverts this via a chroma intermediate: C = (1 − |2L − 1|) × S, then X = C × (1 − |(H/60) mod 2 − 1|), and m = L − C/2. Add m to each computed R′, G′, B′ channel and you have the final [0,1] values.
HSV → HSL conversion avoids going through RGB. Given (H, Sv, V) where V and Sv are in [0,1]:
L = V × (1 − Sv/2)Sl = (L = 0 or L = 1) ? 0 : (V − L) / min(L, 1−L)
A concrete example: the Photoshop orange swatch at H 30°, S 100%, V 100% in HSV maps to H 30°, S 100%, L 50% in HSL — the midpoint lightness confirms it is the pure hue, not a tint or shade. Push V to 60% with S still 100% and the HSL equivalent becomes L 30%, S 100% — a dark orange, not a desaturated one. Understanding this distinction prevents the common mistake of reusing Figma HSV swatches directly in CSS hsl() calls and getting unexpectedly washed-out colours.
Saturation and Lightness Rules for Design Systems
A well-structured design system treats the hue as a brand constant and varies only saturation and lightness to produce the full token set. The following rules are derived from production systems used at scale and hold across light and dark modes.
| Token role | Lightness (light mode) | Lightness (dark mode) | Saturation note |
|---|---|---|---|
| Background / surface | 95–98% | 8–12% | 2–6% (near-neutral) |
| Primary action (button) | 45–55% | 50–60% | 80–100% (vivid) |
| Hover / pressed state | −8 to −10% from base | +6 to +8% from base | Keep S constant |
| Disabled state | 70–75% | 25–30% | 30–40% (muted) |
| Body text | 15–20% | 85–90% | 0–5% |
| Secondary / caption text | 40–50% | 55–65% | 5–15% |
| Destructive / error | 45–50% at H 0–10° | 55–60% at H 0–10° | 85–100% |
Rule 1 — constant hue, variable lightness for scales. If your brand blue is H 220°, S 90%, L 50%, define the 9-step scale as L values 10%, 20%, 30%, 40%, 50%, 60%, 70%, 80%, 90% with S locked at 90%. This keeps every shade recognisably the same colour family and makes the CSS easy: --blue-500: hsl(220 90% 50%), --blue-400: hsl(220 90% 60%), and so on.
Rule 2 — reduce saturation as lightness approaches extremes. At very high or very low lightness, full saturation produces harsh, unnatural colours. A practical formula: if L > 80% or L < 20%, applyS_adjusted = S × (1 − |L − 50| / 60). At L 90% with S 100%, the adjusted saturation becomes roughly 33% — which matches how human perception expects very light tints to look.
Rule 3 — inter-component consistency via relative lightness. Secondary buttons, badges, and chips typically live at the same hue and saturation as the primary action but with lightness raised by 30–35 percentage points in light mode. This ensures brand coherence without requiring you to define each variant from scratch.
Accessible Contrast with HSL: Practical Guidelines
WCAG 2.1 defines contrast ratio as (L1 + 0.05) / (L2 + 0.05) where L1 and L2 are the relative luminances of the two colours — not the HSL lightness values. The confusion here is a common source of accessibility failures: a colour with HSL lightness 50% can have a relative luminance anywhere from about 0.07 (a dark red) to 0.28 (a bright yellow-green), because hue and saturation both affect luminance.
Converting HSL lightness to relative luminance. The path is HSL → linear RGB → luminance. For each linearised channel C:
- If the 8-bit sRGB value is ≤ 10:
C_lin = C_srgb / 12.92 - Otherwise:
C_lin = ((C_srgb + 0.055) / 1.055) ^ 2.4 L = 0.2126 × R_lin + 0.7152 × G_lin + 0.0722 × B_lin
Notice the weighting coefficients: green contributes 71.5% of luminance, red 21.3%, and blue only 7.2%. This is why a saturated blue at HSL 220° 100% 50% can appear very dark to the algorithm even though its HSL lightness is 50%. Always verify contrast programmatically rather than eyeballing HSL values.
Minimum contrast targets by use case.
| Use case | WCAG AA | WCAG AAA | Typical HSL delta-L |
|---|---|---|---|
| Normal text (≥ 18px regular) | 4.5 : 1 | 7 : 1 | ≥ 40% (neutral greys) |
| Large text (≥ 18px bold / ≥ 24px) | 3 : 1 | 4.5 : 1 | ≥ 30% (neutral greys) |
| UI component boundary | 3 : 1 | n/a | ≥ 25% (neutral greys) |
| Placeholder / hint text | 4.5 : 1 | 7 : 1 | Same as normal text |
| Focus indicator (3px outline) | 3 : 1 vs adjacent | n/a | Varies by background |
Edge case — coloured text on coloured background. A red label on a light-pink card might look high-contrast visually but often fails algorithmically because both colours share a similar luminance. Concretely, HSL(0° 80% 45%) on HSL(0° 60% 88%) yields a contrast ratio of roughly 2.8:1 — below the 4.5:1 AA threshold for normal text. The fix is not to desaturate the red; it is to darken it: HSL(0° 80% 30%) on the same pink background clears 4.5:1 with a ratio of about 5.2:1.
Edge case — dark-mode inversion. Simply swapping L values (e.g. 15% ↔ 85%) preserves relative luminance relationships only for achromatic colours. Saturated hues shift their luminance non-linearly when inverted. After building a dark palette from HSL lightness mirrors, audit every coloured text token against its new background — do not assume symmetry.
Practical HSL trick for passing contrast. If a brand colour at L 50% fails on a white background, calculate the minimum L that clears 4.5:1. For low-luminance hues (blues, purples) you may need L as low as 25–30%. For high-luminance hues (yellows, yellow-greens) you may already pass at L 40%. Store these audit-approved values as named tokens (--color-text-brand) separate from the decorative palette tokens so designers cannot accidentally use the un-audited version for readable text.
Frequently Asked Questions
What do the three numbers in an HSL value mean?+
An HSL value looks like hsl(210, 80%, 55%). The first number is the hue angle on a 360-degree colour wheel — 0 and 360 are red, 120 is green, 240 is blue. The second number is saturation: 0% means pure grey, 100% means the most vivid version of that hue. The third number is lightness: 0% is always black, 100% is always white, and 50% gives you the full colour. Together these three values can describe any colour in the sRGB gamut, the same space covered by six-digit hex codes and RGB triples.
How is HSL different from RGB?+
RGB stores a colour as three channel intensities — red, green, and blue — each from 0 to 255. To make a colour lighter in RGB you typically raise all three channels, but the amount you raise each one depends on the starting colour, so it is not obvious and error-prone. HSL reorganises the same information around questions designers actually ask: what hue is this, how vivid is it, and how light is it? Changing only the L value makes a colour lighter or darker while keeping the hue and saturation intact, which maps directly to how a designer thinks about a colour family.
Is the L in HSL the same as perceived brightness?+
No, and this is one of the most important things to understand about HSL. Lightness in HSL is a mathematical average of the maximum and minimum RGB channel values, not a model of how the human eye perceives brightness. Because the eye is far more sensitive to green than to blue, a pure yellow (H=60, S=100%, L=50%) looks much brighter to most people than a pure blue (H=240, S=100%, L=50%) even though both have the same L value. Colour spaces designed for perceptual uniformity — such as CIELAB, HCL, and OKLCH — fix this problem by weighting the channels according to human vision research.
Why does saturation seem to disappear near L=0 or L=100?+
At L=0% every colour is black regardless of the hue and saturation values. At L=100% every colour is white. This means the full range of saturation is only available near the midpoint of lightness. As L moves toward 0 or 100, the actual colour difference between S=0% and S=100% shrinks to zero. This is called the saturation-lightness coupling problem and it is one reason HSL can feel unpredictable when you try to build systematic colour scales by varying only one axis at a time.
When should I use HSL instead of hex or RGB?+
HSL is most useful when you are generating or manipulating colours programmatically rather than just specifying fixed values. If you need a set of tints and shades for a single brand colour, fix the hue and saturation and step through lightness values. If you need a complementary colour, add 180 to the hue. If you need an analogous palette, step the hue by 30 or so in each direction. These operations are simple arithmetic in HSL but require lookup tables or complex math in RGB. For delivering fixed colour values to a production stylesheet, hex is more compact and equally precise.
How does HSL relate to the colour wheel in design tools?+
Most colour pickers in design tools — Figma, Adobe tools, browser developer tools — display a circular hue ring and a square or triangle for saturation and lightness. The hue ring is literally the H axis of HSL mapped onto a circle. When you drag around the ring you are changing H while keeping S and L fixed. When you drag from the vivid edge of the square toward the grey centre you are reducing S. When you drag up or down you are changing L. Understanding this mapping means you can predict exactly what will happen when you move the picker, instead of guessing.
Can I use HSL for accessible colour contrast?+
You can use HSL as a starting point for building accessible palettes, but you cannot rely on the L value alone to guarantee sufficient contrast. The WCAG contrast ratio formula uses relative luminance, which weights the RGB channels by the eye's sensitivity to each wavelength. Two colours with a 50-point difference in HSL lightness may or may not meet the 4.5:1 ratio required for normal text. The reliable workflow is to pick colours in HSL for design convenience, then verify every text-on-background combination with a contrast ratio calculator that uses the actual WCAG formula.