HSL to HEX Converter

Optional shortcut — sets the fields below. No transparency support (a browser limitation of this control), so use the HEX/RGB/HSL fields directly for a translucent color.

#RGB, #RGBA, #RRGGBB, or #RRGGBBAA — the # is optional

rgb(R, G, B) or rgba(R, G, B, A) — R/G/B 0-255, A 0-1

hsl(H, S%, L%) or hsla(H, S%, L%, A) — H 0-360, A 0-1

HSL is where a lot of color decisions actually get made — a picker with hue, saturation, and lightness sliders lets you dial in “a bit more green,” “less washed out,” or “slightly darker” as one independent adjustment at a time, which is a much more direct way to explore a color than typing hex digits and hoping. hsl(150, 60%, 45%) is the kind of value that workflow produces: a specific hue (150°, in the green range), a specific vividness (60% saturation), and a specific brightness (45% lightness), each one chosen deliberately. The problem is that almost nothing downstream — a CSS file, a Tailwind config, a design-token JSON, a style guide doc — accepts HSL as the format it actually stores or displays. Converting to hex is the “commit” step at the end of that exploration, not a separate, unrelated task.

The math runs through the same RGB intermediate step as any HSL conversion, just formatted differently at the end: hue, saturation, and lightness combine (via the standard HSL-to-RGB formula) into three 0-255 red/green/blue channel values, and each of those channels is then formatted as a 2-digit hex pair and joined together. hsl(150, 60%, 45%) works out to RGB channels of 46, 184, and 115 — and those become #2eb873. There’s no way to skip straight from hue/saturation/lightness to hex digits without passing through RGB in between, since hex is RGB, just written in base 16 instead of base 10.

One practical pattern worth knowing if you’re building a design system rather than picking a single color: some systems deliberately keep a color’s base hue and saturation fixed in HSL and generate an entire lightness scale from it — a “100” shade at 95% lightness through a “900” shade at 15% lightness, for instance — computing each step programmatically rather than hand-picking ten separate hex codes that need to visually relate to each other. Each generated HSL value in that scale still needs converting to hex exactly once, typically at build time, before it ships as a CSS variable or Tailwind token.

Converting HSL to hex programmatically

JavaScript, through the RGB intermediate step:

function hueToChannel(p, q, t) {
  if (t < 0) t += 1;
  if (t > 1) t -= 1;
  if (t < 1 / 6) return p + (q - p) * 6 * t;
  if (t < 1 / 2) return q;
  if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
  return p;
}

function hslToHex(h, s, l) {
  const hn = h / 360, sn = s / 100, ln = l / 100;
  let r, g, b;
  if (sn === 0) {
    r = g = b = ln;
  } else {
    const q = ln < 0.5 ? ln * (1 + sn) : ln + sn - ln * sn;
    const p = 2 * ln - q;
    r = hueToChannel(p, q, hn + 1 / 3);
    g = hueToChannel(p, q, hn);
    b = hueToChannel(p, q, hn - 1 / 3);
  }
  const toHex = (v) => Math.round(v * 255).toString(16).padStart(2, "0");
  return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
}

console.log(hslToHex(150, 60, 45));
// "#2eb873"

Python, via colorsys.hls_to_rgb (note the argument order: hue, lightness, saturation):

import colorsys

def hsl_to_hex(h, s, l):
    r, g, b = colorsys.hls_to_rgb(h / 360, l / 100, s / 100)
    return f"#{round(r * 255):02x}{round(g * 255):02x}{round(b * 255):02x}"

print(hsl_to_hex(150, 60, 45))
# "#2eb873"

Frequently asked questions

Why do I end up with an HSL value I need to convert, instead of just picking a hex directly?
Most color pickers with hue/saturation/lightness sliders — browser DevTools, Figma, Sketch, plenty of design system tools — let you dial a color in visually along three independent, meaningful axes, which is far easier than guessing at hex digits. Once you've landed on a color you like, though, CSS files, design tokens, and style guides almost always expect it as hex, so converting is the last step of that workflow, not an alternative to it.
Will converting HSL to hex and back give me the exact same HSL numbers?
Almost always, but not guaranteed byte-for-byte. Hex stores exact 0-255 RGB channel values with nothing rounded; when those get converted back to HSL, the hue/saturation/lightness percentages are rounded to whole numbers for display, so a value like 45.6% lightness becomes 46% on the way out — and converting THAT back to RGB can land a channel 1 unit away from where it started. This is expected floating-point rounding, not a bug, and it essentially never changes what the color visibly looks like.
Why store design tokens as hex if HSL is where I actually design the color?
Because hex is what nearly every consumer of a design token actually expects — CSS custom properties, Tailwind config, most icon and illustration tools. Some design systems do store base colors as HSL specifically so hue/saturation stay fixed while generating a whole lightness scale (a color's 100-900 shade range) programmatically, then convert each generated shade to hex once, at build time, rather than storing HSL everywhere and converting on every render.
Can I paste this hex directly into a CSS custom property?
Yes — the hex output here is already in the exact #rrggbb form CSS expects, whether you're setting it as a background-color, a Tailwind config color, or a --brand-color custom property.
Is my color sent to a server?
No. The conversion runs entirely in your browser using plain arithmetic — nothing is uploaded, logged, or stored.

Related Color Converter pages

Looking for a different pair or algorithm? Here are the other specific versions, plus the general tool for everything else: