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?
Will converting HSL to hex and back give me the exact same HSL numbers?
Why store design tokens as hex if HSL is where I actually design the color?
Can I paste this hex directly into a CSS custom property?
#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?
Related Color Converter pages
Looking for a different pair or algorithm? Here are the other specific versions, plus the general tool for everything else: