Skip to content
Color Tools

HSL to RGB Converter

Type an HSL color and watch it become exact RGB channel values in real time — HEX, CMYK and OKLCH included, plus a swatch that updates as you edit.

Accepts hsl() and hsla() — hue as a plain number, deg or turn; saturation and lightness as percentages.

Enter a color to see every format.

What the reverse conversion has to rebuild

An HSL value states intent: which direction on the color wheel (hue), how vivid (saturation), how far toward white (lightness). A display can’t use intent — it needs three channel energies. Converting HSL to RGB therefore runs in three stages: build the strongest possible version of the requested hue, scale its vividness, then lift or dim everything to the requested lightness. The standard algorithm names the three quantities involved C, X and m, and once you know what each one is for, the piecewise formula stops looking arbitrary.

C, X and m, explained intuitively

C is the chroma — the spread between the largest and smallest RGB channel the final color will have: C = (1 − |2L − 1|) × S. The first factor is the room available for color at this lightness: at L = 50% it equals 1 (maximum room) and it shrinks linearly to 0 toward both black and white. That single term encodes why near-black and near-white colors can never look vivid, no matter how high their saturation is set.

X is the middle channel. In every 60° sector of the wheel one channel sits at C, one at 0, and the third — X = C × (1 − |(H ÷ 60) mod 2 − 1|) — ramps between them as a triangle wave. That ramp is what makes hue sweep smoothly: pure red at 0°, green rising until yellow at 60°, red falling until pure green at 120°, and so on around the circle.

m is the white-light floor. A raw C/X/0 mixture has lightness C ÷ 2, which is usually not what was asked for. Adding m = L − C ÷ 2 to all three channels raises lightness to the target without touching hue or chroma — visually, it pours a uniform amount of white light on top of the pure color. Multiply the three sums by 255, round to integers, and you have RGB.

Which channel receives C and which receives X depends only on the sector:

Hue rangeR₁G₁B₁
0° – 60°CX0
60° – 120°XC0
120° – 180°0CX
180° – 240°0XC
240° – 300°X0C
300° – 360°C0X

Worked example: hsl(220, 60%, 49%)

  • C = (1 − |2 × 0.49 − 1|) × 0.60 = (1 − 0.02) × 0.60 = 0.98 × 0.60 = 0.588.
  • H′ = 220 ÷ 60 = 3.6667, which lands in the 180°–240° row → pattern (0, X, C).
  • X = 0.588 × (1 − |3.6667 mod 2 − 1|); the |…| term is exactly two thirds, so the factor is one third and X = 0.588 ÷ 3 = 0.196.
  • m = 0.49 − 0.588 ÷ 2 = 0.49 − 0.294 = 0.196.
  • Channels: R = (0 + 0.196) × 255 = 49.98 → 50; G = (0.196 + 0.196) × 255 = 99.96 → 100; B = (0.588 + 0.196) × 255 = 199.92 → 200.

Result: rgb(50, 100, 200), or #3264c8 in HEX. Run it back the other way and hue returns as exactly 220° and saturation as exactly 60% — the fractions 150 ÷ 250 and (50 − 100) ÷ 150 come out clean — while lightness computes to 49.02% and displays as 49%.

The off-by-percent mistakes everyone makes once

  • Dropping the % from saturation or lightness. CSS requires percent signs on S and L in the classic comma syntax — hsl(220, 60, 49) is invalid stylesheet code. Converters that tolerate bare numbers (this one included) read them on a 0–1 scale, so a lightness typed as 49 clamps to 100% and the output turns pure white. An unexpectedly white result is almost always this bug.
  • Putting a percent sign on hue. Hue is an angle: 220, 220deg, 244.4grad and 0.611turn all point the same way, while 220% parses as nothing at all.
  • Mixing up 0–1 decimals and bare percentages. Writing 0.6 for 60% works here and in many libraries; writing 60 without the sign does not mean 60%.
  • Component order. Python’s colorsys module works in HLS — lightness second, saturation third. Pasting its output straight into hsl() silently swaps the two and produces oddly washed-out or oversaturated colors.
  • Fearing out-of-range hue. Angles wrap: −140° and 580° both normalize to 220°. That part is never an error.

Why the RGB result has such unround numbers

RGB channels are 8-bit integers — a grid of 256 steps — and percentage-based HSL values rarely land on the grid exactly. The example above ideally wants 49.98, 99.96 and 199.92, so each channel gets rounded by at most half a step, a shift of about 0.2% that no eye can register. But the rounding explains two everyday observations: clean HSL inputs produce messy-looking RGB, and repeated round trips through rounded display values can wiggle a final digit. The underlying formulas are exact inverses of one another; only 8-bit quantization and display precision introduce drift.

Conversion here is plain arithmetic executed by the page you’re looking at — nothing is sent to a server, and the tool keeps working offline once loaded. To study the same relationship from the other side, the RGB to HSL converter dissects the forward max/min formula; and when what you ultimately need is a stylesheet-ready code, the HSL to HEX converter jumps straight to hex notation.

Frequently asked questions

What is the formula to convert HSL to RGB?

Compute chroma C = (1 − |2L − 1|) × S, the middle value X = C × (1 − |(H ÷ 60) mod 2 − 1|) and the offset m = L − C ÷ 2. Assign C and X to two channels according to the 60° hue sector, add m to all three, then multiply by 255 and round.

Why did my HSL color convert to pure white or black?

Almost always missing percent signs. Tools that accept bare numbers read them on a 0–1 scale, so a lightness written as 49 instead of 49% clamps to 100% and everything turns white.

Does hue need a unit in hsl()?

No — a bare number means degrees. CSS also accepts 220deg, 244.4grad, 0.611turn or a radian value, but a percent sign on hue is invalid.

Can every HSL color be represented in RGB?

Yes. HSL is a re-parameterization of the same sRGB cube, so each hsl() value maps to exactly one rgb() value; only the 8-bit rounding of channels introduces tiny differences.

Why does hsl(220, 60%, 49%) not produce round RGB numbers?

The ideal channels are 49.98, 99.96 and 199.92, and RGB must store integers from 0 to 255. Each channel is rounded to the nearest step, which is how the result becomes rgb(50, 100, 200).

Is my color data private on this page?

Yes — the conversion is pure arithmetic executed locally by the page, with no request to any server involved.