RGB to HSL Converter

hsl(221, 83%, 53%)rgb(37, 99, 235) · #2563EB

RGB Channels (0–255)

Channel Mix Breakdown

R 10%G 27%B 63%

Try a sample color

Have a hex code instead?

HSL Result

221°, 83%, 53%

hsl(221, 83%, 53%)

Hue

221°

Saturation

83%

Lightness

53%

Hex Equivalent

#2563EB

Perceived Brightness vs HSL Lightness

HSL Lightness (math)53%
Perceived Brightness (eye)15%

When the two bars diverge by more than 20 points, your color looks brighter or darker than its HSL value suggests.

CSS Code Snippets

color: hsl(221, 83%, 53%);
background-color: hsl(221, 83%, 53%);
/* Modern syntax (no commas) */
color: hsl(221 83% 53%);
/* With 50% opacity */
color: hsl(221 83% 53% / 50%);

Live Conversion Math for Your Inputs

1. Normalize: R′ = 0.1451, G′ = 0.3882, B′ = 0.9216

2. max = 0.9215686274509803, min = 0.1450980392156863, delta = 0.7765

3. L = (max + min) / 2 = 0.5333 53%

4. S = delta / divisor → 83%

5. H = 221° (blue dominant)

Common RGB to HSL Lookups

ColorRGBHSLPreview
Black0, 0, 00°, 0%, 0%
White255, 255, 2550°, 0%, 100%
Red255, 0, 00°, 100%, 50%
Green0, 255, 0120°, 100%, 50%
Blue0, 0, 255240°, 100%, 50%
Yellow255, 255, 060°, 100%, 50%
Cyan0, 255, 255180°, 100%, 50%
Magenta255, 0, 255300°, 100%, 50%
Mid Gray128, 128, 1280°, 0%, 50%
Tomato255, 99, 719°, 100%, 64%
Royal Blue65, 105, 225225°, 73%, 57%
Forest Green34, 139, 34120°, 61%, 34%

Click any row to load it into the converter above

How to Use This Tool

  1. 1.Drag the Red, Green, and Blue sliders or type values from 0 to 255 into the number inputs to set your starting color
  2. 2.Read the HSL result in the blue panel — hue in degrees (0–360), saturation and lightness as percentages
  3. 3.Compare HSL Lightness against Perceived Brightness in the amber panel to spot colors that look much darker or lighter than the math suggests
  4. 4.Click any preset or table row to load that color into the sliders, then tweak from there
  5. 5.Hit "Copy HSL" or "Copy CSS" to grab the value for your stylesheet, including the modern comma-free syntax with optional alpha

Rate this tool

From Channel Sliders to Color Wheels: Working in RGB and Thinking in HSL

You ship a brand color as RGB(37, 99, 235). A week later, design comes back with a request: the dashboard needs a hover state that's "a touch lighter," a disabled state that's "a bit washed out," and a dark-mode variant that's "the same blue but dimmer." Try doing all three by editing R, G, and B values directly. You'll be there for an hour, and at least one of the variants will end up looking subtly purple. An RGB to HSL converter is the tool that ends that hour — not because the math is hard, but because HSL exposes the three knobs (hue, saturation, lightness) that match how humans actually describe color changes.

RGB to HSL conversion diagram showing an RGB cube alongside an HSL cylinder with the same color plotted in both coordinate systems and connecting lines mapping red green blue values to hue saturation lightness

The Theming Moment That Forces You to HSL

Almost every front-end engineer hits the same wall the first time they build a serious theming system. RGB is wonderful for storage — three integers, eight bits each, 16.7 million possible colors — and it's the format every image file, every canvas API, and every getImageData() call returns. But the moment you need variationsof a color rather than the color itself, RGB stops cooperating. You can't lighten it without nudging all three channels in proportion, and even then the hue drifts. You can't desaturate it without computing a luminance value and blending toward gray. You can't shift it to a related accent color without getting deep into matrix math.

HSL collapses all three of those operations into "change one number." That's why CSS3 added hsl() in 2003, why design tokens in Tailwind, Radix, and shadcn/ui all store colors as HSL channels, and why component libraries that need automated dark-mode generation universally do the conversion to HSL first.

What Changes When You Start From RGB

The RGB to HSL formula is the same regardless of whether you arrived at the input via hex parsing, a color picker, or a canvas pixel sample. What changes is the prep work: hex inputs need base-16 parsing, picker inputs are already integers, and canvas inputs come pre-normalized as a Uint8ClampedArray. Starting from raw RGB skips one parsing step but introduces a different gotcha — you must remember to divide by 255 before applying the conversion math. Skip that step and your "lightness" values will exceed 12,000.

Once normalized to the 0–1 range, the algorithm is straightforward:

  • Step 1: Find max and min of the three normalized channels.
  • Step 2: Compute delta = max − min — this drives saturation.
  • Step 3: Lightness is just the midpoint: L = (max + min) / 2.
  • Step 4: Saturation depends on lightness. When L ≤ 0.5, S = delta / (max + min). When L > 0.5, S = delta / (2 − max − min). The split prevents a divide-by-zero at the lightness extremes.
  • Step 5: Hue branches on which channel was max. If red is max, H = 60 × ((G − B) / delta mod 6). If green is max, H = 60 × ((B − R) / delta + 2). If blue is max, H = 60 × ((R − G) / delta + 4).

The conditional in step 5 is the part that catches people: hue isn't a single formula, it's a piecewise function that depends on which channel dominates. Get the wrong branch and your color shows up 120 degrees off — turning red into green or green into blue.

Worked Example: RGB(255, 99, 71) Tomato

The CSS named color tomato is RGB(255, 99, 71). Let's convert it by hand and verify against the live converter above.

  • Normalize: R′ = 255/255 = 1.000, G′ = 99/255 = 0.3882, B′ = 71/255 = 0.2784
  • Max/min: max = 1.000 (R), min = 0.2784 (B), delta = 0.7216
  • Lightness: L = (1.000 + 0.2784) / 2 = 0.6392 → 64%
  • Saturation: L is greater than 0.5, so S = 0.7216 / (2 − 1.000 − 0.2784) = 0.7216 / 0.7216 = 1.000 → 100%
  • Hue: Red is max. H = 60 × ((0.3882 − 0.2784) / 0.7216) = 60 × 0.1522 = 9.13 →

Final result: hsl(9, 100%, 64%). The 9° hue puts tomato just barely past pure red on the wheel, the 100% saturation says it's as vivid as red can be at this lightness, and the 64% lightness explains why it reads as a warm pinkish-orange rather than a deep red. Paste 255, 99, 71 into the converter to confirm.

The 0–255 vs Percent Trap

Modern CSS accepts RGB in two forms: integers (rgb(255, 99, 71)) and percentages (rgb(100%, 38.8%, 27.8%)). Both are valid. Both produce the same color. But they trip up conversion code in two specific ways.

First, if your input source is a CSS string and you're doing your own parsing, you have to detect the format. rgb(50, 50, 50) is dark gray. rgb(50%, 50%, 50%)is medium gray — the percent values normalize to 127, 127, 127. Treating them identically is a bug that manifests as colors looking "off" only for some users, depending on what their style source generated.

Second, percent inputs let you express colors that integer RGB cannot. rgb(50.5%, 50.5%, 50.5%) normalizes to 128.775 per channel — a value that has no integer representation in the 0–255 space. The W3C's CSS Color Module Level 4 specificationmandates that browsers preserve sub-integer precision through the rendering pipeline, but if your converter rounds to integers up front, you lose that fidelity. For most UI work it doesn't matter; for image processing and color science work, it does.

Round-Trip Precision Loss

Converting RGB to HSL and back is lossy by design. Standard sRGB stores 256 distinct values per channel, giving 2563= 16,777,216 unique colors. Integer HSL with hue in degrees (0–360), saturation (0–100), and lightness (0–100) only addresses 360 × 101 × 101 = 3,672,360 combinations. That's about 22% of the RGB space. The other 78% gets quantized into nearby buckets during the round trip.

For example, RGB(127, 128, 128) and RGB(128, 128, 128) are both nearly identical grays, off by a single bit on the red channel. Both round to HSL(180, 1%, 50%) and HSL(0, 0%, 50%) respectively, and converting either back gives RGB(128, 128, 128). The original red-127 gray is gone. This matters when:

  • You're storing user-picked colors and then reconstructing them — always store the original RGB or hex, never the HSL.
  • You're writing a color-difference algorithm — HSL distance is unreliable around grays because the hue value loses meaning.
  • You're running a unit test that asserts rgb → hsl → rgb round-trip equality — it will fail for ~78% of inputs.

The fix is structural: treat HSL as a view of an RGB color, not a replacement for it. Our RGB to hex converter handles the lossless side of the workflow when you need persistent storage.

Where HSL Lightness Lies to You

The amber panel in the converter above isn't decorative. It's comparing HSL's mathematical lightness against ITU-R BT.709 relative luminance — the formula your eyes actually use. Try the converter with RGB(255, 255, 0) (yellow) and then RGB(0, 0, 255)(pure blue). Both report 50% HSL lightness. But yellow has a perceived brightness around 93%, while blue measures only 7%. Your retina sees a 13× brightness difference between two colors HSL claims are equal.

This is why sorting palettes by HSL lightness gives uneven-looking results, and why darken/lighten functions in CSS preprocessors like SASS produce ramps that look flat in the dark range and uniform in the bright range. The math is consistent; perception isn't. The newer oklch() color space, defined in CSS Color Level 4, fixes this by using perceptually uniform lightness — an OKLCH lightness of 0.5 actually looks half-bright, no matter the hue. For now, when HSL lightness and perceived brightness diverge by more than 20 points in our converter, treat that as a flag: you may need to nudge the HSL value to compensate.

A JavaScript Reference Implementation

The full conversion takes 14 lines of vanilla JavaScript — no dependencies, no library:

function rgbToHsl(r, g, b) {
  r /= 255; g /= 255; b /= 255;
  const max = Math.max(r, g, b);
  const min = Math.min(r, g, b);
  const l = (max + min) / 2;
  if (max === min) return { h: 0, s: 0, l: l * 100 };
  const d = max - min;
  const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
  let h;
  if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
  else if (max === g) h = ((b - r) / d + 2) / 6;
  else h = ((r - g) / d + 4) / 6;
  return { h: h * 360, s: s * 100, l: l * 100 };
}

Three things are worth noting about this implementation. The max === min early return handles grays cleanly — without it, the hue formula divides by zero. The ternary on saturation switches branches at the 50% lightness midpoint to keep the denominator non-negative. And the (g < b ? 6 : 0) trick on the red branch handles hue wrap-around when green is less than blue, which would otherwise produce a negative hue value that the browser would reject in a CSS string.

When to Skip HSL and Stay in RGB

HSL is a tool, not a religion. There are workflows where converting from RGB introduces more pain than it solves:

  • Pixel manipulation in canvas. The ImageData.data array is RGBA. Converting every pixel to HSL, modifying it, and converting back triples your processing time for no benefit. Most filters (brightness, blur, threshold) work directly on RGB channels.
  • WCAG contrast checking. Contrast ratios are computed from relative luminance, which is a weighted sum of linearized RGB. HSL lightness is not a substitute. Use our contrast checker when you need WCAG compliance.
  • Print output. Print needs CMYK ink percentages, not screen color models. Going RGB → HSL → CMYK adds rounding error. Our RGB to CMYK converter does the conversion in one step with the proper ICC-aware math.
  • Storage and serialization. Always store hex or RGB. HSL loses precision on round-trip and takes more bytes to serialize as a string.

Reach for HSL when you're generating variationsof a color — tints, shades, hover states, dark-mode pairs, harmony palettes. That's where the three-knob model pays off, and it's the only reason this conversion exists in the first place.

Marko Sinko
Marko SinkoTechnical Tools Editor

Croatian developer with a Computer Science degree from University of Zagreb and expertise in advanced algorithms. Marko builds and verifies the technical tools, number system converters, and scientific calculators across UnitCalcTools, ensuring mathematical precision and developer-friendly interfaces.

Last updated: April 18, 2026LinkedIn

Frequently Asked Questions

RGB(37, 99, 235) — Tailwind's blue-600 — converts to HSL(217, 83%, 53%). The blue channel is the maximum at 235, the red channel is the minimum at 37, giving a delta of 198 out of 255. That high delta combined with a lightness of 53% produces the strong 83% saturation, and the dominant blue channel places the hue at 217 degrees on the color wheel.
When all three RGB channels are equal, the max and min channel values are identical, so the delta is zero. Saturation is calculated as delta divided by a function of lightness, and zero divided by anything is zero. This is mathematically why every shade of gray — RGB(64, 64, 64), RGB(128, 128, 128), RGB(200, 200, 200) — has 0% saturation regardless of how light or dark the gray is.
Yes. The HSL formula expects normalized values between 0 and 1, so you must divide each RGB channel by 255 before applying the conversion math. Skipping this step will give you nonsensical lightness values in the hundreds and saturation that exceeds 100%. The output of the divide step for RGB(255, 0, 0) is (1.0, 0.0, 0.0), which then yields HSL(0, 100%, 50%).
Both colors share identical saturation and lightness, but human eyes see green and yellow wavelengths as much brighter than blue. RGB(255, 255, 0) — the yellow at HSL(60, 100%, 50%) — has a perceptual luminance of about 0.928 on a 0-1 scale, while RGB(0, 0, 255) — the blue at HSL(240, 100%, 50%) — measures only 0.072. HSL treats them as equally light, but your retina disagrees by a factor of 13x.
Any RGB triplet where the maximum and minimum channel values average to 127.5 produces 50% lightness. RGB(255, 0, 0), RGB(0, 255, 0), RGB(255, 255, 0), and RGB(127, 127, 127) all sit at exactly 50% lightness. The lightness formula is (max + min) / 2 after normalizing to 0-1, so 50% lightness means the brightest and darkest channels of your color average to the midpoint of the 0-255 range.
Yes, the conversion is about 15 lines of vanilla JavaScript. Normalize each channel by dividing by 255, find the max and min of the three normalized values, calculate lightness as (max + min) / 2, derive saturation from the delta, and pick the hue formula based on which channel was the maximum. The full implementation lives inside this page's converter — view the source for a working reference, or check the W3C CSS Color Module spec for the canonical algorithm.
Standard sRGB stores 256 values per channel (8 bits), giving 16,777,216 distinct colors. HSL with integer hue (0-360), saturation (0-100), and lightness (0-100) only addresses 360 × 101 × 101 = 3.67 million unique combinations. That makes round-tripping RGB → HSL → RGB lossy: about 78% of original RGB triplets won't survive the conversion exactly. For storage, always keep the original RGB or hex value.
Convert to HSL, subtract 30 percentage points from the lightness value, then convert back to RGB. RGB(37, 99, 235) is HSL(217, 83%, 53%). Reducing lightness by 30 gives HSL(217, 83%, 23%), which converts back to RGB(11, 31, 73). Doing the same darkening directly in RGB by multiplying each channel by 0.7 gives RGB(26, 69, 165) — a similar brightness but a slightly shifted hue, which is why HSL is preferred for clean tint and shade ramps.

Related Tools