← All Posts

How to Convert Between HEX, RGB, and HSL Colors

Published on February 10, 2026

Color is one of the most powerful tools in a designer or developer's toolkit. The same logical color — say, a vivid orange — can be expressed as #FF6B00 in HEX, rgb(255, 107, 0) in RGB, or hsl(25, 100%, 50%) in HSL. Understanding these three color models, when to use each, and how to convert between them is essential knowledge for anyone working with CSS, design tools, or digital media.

Understanding Color Models

A color model is a mathematical system for representing colors as numbers. Each model approaches color description from a different angle, which makes certain models better suited for certain tasks. The three most common models in web development are HEX, RGB, and HSL.

HEX Colors: The #RRGGBB Format

HEX (hexadecimal) is the most widely used color format on the web. A HEX color code is a six-character string prefixed with #, where each pair of characters represents the Red, Green, and Blue channels respectively, encoded as hexadecimal values from 00 to FF (0 to 255 in decimal).

// HEX color anatomy
#FF6B00
 ││││││
 ││││└┘── Blue:  00 (hex) = 0   (decimal)
 ││└┘──── Green: 6B (hex) = 107 (decimal)
 └┘────── Red:   FF (hex) = 255 (decimal)

// Common HEX examples
#000000  →  Black     (no light)
#FFFFFF  →  White     (full light, all channels)
#FF0000  →  Pure Red
#00FF00  →  Pure Green
#0000FF  →  Pure Blue
#808080  →  Medium Gray (equal parts R, G, B)

// Shorthand HEX (3 characters)
#F60     →  Expands to #FF6600
#333     →  Expands to #333333
#FFF     →  Expands to #FFFFFF

// 8-digit HEX (with alpha transparency)
#FF6B0080  →  The same orange at 50% opacity
         ││
         └┘── Alpha: 80 (hex) = 128 (decimal) ≈ 50%

HEX colors are compact and universally supported. They are the default output of most color pickers, and developers can quickly recognize common values like #333 (dark gray) or #F00 (red). However, HEX is not intuitive for making color adjustments — if you want to make a color "slightly lighter" or "more saturated," you cannot easily do that by editing the hex digits.

RGB Colors: The (0-255) Channels

RGB represents colors as a combination of Red, Green, and Blue light channels, each ranging from 0 (none) to 255 (maximum intensity). This directly mirrors how screens produce color: each pixel contains tiny red, green, and blue sub-pixels that blend together at different intensities.

/* CSS rgb() function */
color: rgb(255, 107, 0);      /* Vivid orange */
color: rgb(0, 0, 0);          /* Black */
color: rgb(255, 255, 255);    /* White */
color: rgb(128, 128, 128);    /* Medium gray */

/* Modern CSS allows space-separated values */
color: rgb(255 107 0);

/* With alpha (transparency) */
color: rgba(255, 107, 0, 0.5);   /* 50% transparent orange */
color: rgb(255 107 0 / 50%);     /* Modern syntax, same result */

/* Percentage-based RGB (less common) */
color: rgb(100%, 42%, 0%);       /* Same orange */

RGB is conceptually straightforward — it describes how much of each primary light color to mix. It is the native color model of screens and image processing libraries. However, like HEX, RGB is not intuitive for human-friendly color manipulation. Wanting "a darker shade of this blue" is not obviously expressed as "subtract 40 from each channel."

HSL Colors: Hue, Saturation, Lightness

HSL is designed to match how humans naturally think about color. Instead of mixing light primaries, HSL describes color using three intuitive dimensions.

  • Hue (0-360) — The color itself, represented as a degree on the color wheel. 0 (and 360) is red, 120 is green, 240 is blue. Think of it as spinning around a rainbow circle.
  • Saturation (0-100%) — How vivid the color is. 100% is the purest, most vivid version of that hue. 0% is completely desaturated — a shade of gray.
  • Lightness (0-100%) — How light or dark the color is. 0% is always black, 100% is always white, and 50% gives you the purest version of the color.
/* CSS hsl() function */
color: hsl(25, 100%, 50%);     /* Vivid orange */
color: hsl(0, 0%, 0%);        /* Black (any hue, 0% lightness) */
color: hsl(0, 0%, 100%);      /* White (any hue, 100% lightness) */
color: hsl(0, 100%, 50%);     /* Pure red */
color: hsl(120, 100%, 50%);   /* Pure green */
color: hsl(240, 100%, 50%);   /* Pure blue */

/* The color wheel at a glance */
hsl(0,   100%, 50%)  →  Red
hsl(30,  100%, 50%)  →  Orange
hsl(60,  100%, 50%)  →  Yellow
hsl(120, 100%, 50%)  →  Green
hsl(180, 100%, 50%)  →  Cyan
hsl(240, 100%, 50%)  →  Blue
hsl(270, 100%, 50%)  →  Purple
hsl(300, 100%, 50%)  →  Magenta
hsl(330, 100%, 50%)  →  Pink

/* Creating color variations is intuitive */
hsl(25, 100%, 50%)   →  Base orange
hsl(25, 100%, 70%)   →  Lighter orange (just increase lightness)
hsl(25, 100%, 30%)   →  Darker orange (decrease lightness)
hsl(25, 50%, 50%)    →  Muted orange (decrease saturation)
hsl(25, 100%, 90%)   →  Very light orange (good for backgrounds)

/* With alpha transparency */
color: hsla(25, 100%, 50%, 0.5);   /* 50% transparent */
color: hsl(25 100% 50% / 50%);     /* Modern syntax */

HSL is the best model for building design systems and theming. You can define a brand hue (say, 25 for orange), then generate an entire palette by varying saturation and lightness. Need a hover state? Increase lightness by 10%. Need a disabled state? Decrease saturation by 40%. This is why CSS custom properties combined with HSL are so powerful.

Converting Between Color Models

HEX to RGB

Converting HEX to RGB is straightforward: parse each two-character pair as a hexadecimal number.

function hexToRgb(hex: string): { r: number; g: number; b: number } {
  // Remove the # prefix if present
  const clean = hex.replace(/^#/, "");

  // Handle 3-character shorthand (#F60 → FF6600)
  const full =
    clean.length === 3
      ? clean[0] + clean[0] + clean[1] + clean[1] + clean[2] + clean[2]
      : clean;

  const num = parseInt(full, 16);
  return {
    r: (num >> 16) & 255,    // Extract bits 16-23
    g: (num >> 8) & 255,     // Extract bits 8-15
    b: num & 255,            // Extract bits 0-7
  };
}

// Examples
hexToRgb("#FF6B00");   // { r: 255, g: 107, b: 0 }
hexToRgb("#333");      // { r: 51,  g: 51,  b: 51 }
hexToRgb("0088FF");    // { r: 0,   g: 136, b: 255 }

RGB to HEX

function rgbToHex(r: number, g: number, b: number): string {
  const toHex = (n: number) => n.toString(16).padStart(2, "0");
  return "#" + toHex(r) + toHex(g) + toHex(b);
}

// Examples
rgbToHex(255, 107, 0);     // "#ff6b00"
rgbToHex(0, 0, 0);         // "#000000"
rgbToHex(255, 255, 255);   // "#ffffff"

RGB to HSL

The RGB-to-HSL conversion is more complex because it involves transforming between two fundamentally different color spaces.

function rgbToHsl(
  r: number, g: number, b: number
): { h: number; s: number; l: number } {
  // Normalize RGB values to 0-1 range
  r /= 255;
  g /= 255;
  b /= 255;

  const max = Math.max(r, g, b);
  const min = Math.min(r, g, b);
  const delta = max - min;

  // Calculate Lightness
  let l = (max + min) / 2;

  // Calculate Saturation
  let s = 0;
  if (delta !== 0) {
    s = delta / (1 - Math.abs(2 * l - 1));
  }

  // Calculate Hue
  let h = 0;
  if (delta !== 0) {
    if (max === r) {
      h = 60 * (((g - b) / delta) % 6);
    } else if (max === g) {
      h = 60 * ((b - r) / delta + 2);
    } else {
      h = 60 * ((r - g) / delta + 4);
    }
  }
  if (h < 0) h += 360;

  return {
    h: Math.round(h),
    s: Math.round(s * 100),
    l: Math.round(l * 100),
  };
}

// Examples
rgbToHsl(255, 107, 0);     // { h: 25,  s: 100, l: 50 }
rgbToHsl(128, 128, 128);   // { h: 0,   s: 0,   l: 50 }
rgbToHsl(0, 136, 255);     // { h: 208, s: 100, l: 50 }

HSL to RGB

function hslToRgb(
  h: number, s: number, l: number
): { r: number; g: number; b: number } {
  s /= 100;
  l /= 100;

  const c = (1 - Math.abs(2 * l - 1)) * s;  // Chroma
  const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
  const m = l - c / 2;

  let r1 = 0, g1 = 0, b1 = 0;

  if (h < 60)       { r1 = c; g1 = x; b1 = 0; }
  else if (h < 120) { r1 = x; g1 = c; b1 = 0; }
  else if (h < 180) { r1 = 0; g1 = c; b1 = x; }
  else if (h < 240) { r1 = 0; g1 = x; b1 = c; }
  else if (h < 300) { r1 = x; g1 = 0; b1 = c; }
  else              { r1 = c; g1 = 0; b1 = x; }

  return {
    r: Math.round((r1 + m) * 255),
    g: Math.round((g1 + m) * 255),
    b: Math.round((b1 + m) * 255),
  };
}

// Examples
hslToRgb(25, 100, 50);    // { r: 255, g: 106, b: 0 }
hslToRgb(0, 0, 50);       // { r: 128, g: 128, b: 128 }
hslToRgb(208, 100, 50);   // { r: 0,   g: 136, b: 255 }

CSS Color Functions: When to Use Each

CSS supports all three color models natively. Here is guidance on when each format is the best choice.

  • Use HEX when you need compact color values in stylesheets, when copying colors from design tools, or when working with brand guidelines that specify HEX codes. HEX is the most universally recognized format.
  • Use RGB when you need to manipulate individual color channels programmatically, when working with canvas or image processing APIs, or when you need alpha transparency (rgba).
  • Use HSL when building design systems, creating color palettes, implementing themes, or when you need to create programmatic color variations (hover states, active states, disabled states). HSL makes it trivial to generate harmonious colors.
/* Design system using HSL with CSS custom properties */
:root {
  /* Define brand colors by hue */
  --brand-hue: 25;
  --brand-saturation: 100%;

  /* Generate a full palette from a single hue */
  --brand-50:  hsl(var(--brand-hue), var(--brand-saturation), 95%);
  --brand-100: hsl(var(--brand-hue), var(--brand-saturation), 90%);
  --brand-200: hsl(var(--brand-hue), var(--brand-saturation), 80%);
  --brand-300: hsl(var(--brand-hue), var(--brand-saturation), 70%);
  --brand-400: hsl(var(--brand-hue), var(--brand-saturation), 60%);
  --brand-500: hsl(var(--brand-hue), var(--brand-saturation), 50%);
  --brand-600: hsl(var(--brand-hue), var(--brand-saturation), 40%);
  --brand-700: hsl(var(--brand-hue), var(--brand-saturation), 30%);
  --brand-800: hsl(var(--brand-hue), var(--brand-saturation), 20%);
  --brand-900: hsl(var(--brand-hue), var(--brand-saturation), 10%);
}

/* Changing the entire brand color is now a single line */
:root[data-theme="blue"] {
  --brand-hue: 220;
}

/* Interactive states are simple arithmetic */
.button {
  background: hsl(var(--brand-hue), var(--brand-saturation), 50%);
}
.button:hover {
  background: hsl(var(--brand-hue), var(--brand-saturation), 45%);
}
.button:active {
  background: hsl(var(--brand-hue), var(--brand-saturation), 40%);
}
.button:disabled {
  background: hsl(var(--brand-hue), 20%, 70%);
}

Accessibility and Color Contrast

Choosing colors is not just about aesthetics — it is about accessibility. The Web Content Accessibility Guidelines (WCAG) define minimum contrast ratios between text and its background to ensure readability for people with visual impairments.

  • WCAG AA (minimum) — Requires a contrast ratio of at least 4.5:1 for normal text and 3:1 for large text (18px bold or 24px regular).
  • WCAG AAA (enhanced) — Requires at least 7:1 for normal text and 4.5:1 for large text.

The contrast ratio is calculated using the relative luminance of two colors, which depends on their RGB values. Understanding color conversion helps you pick accessible combinations.

// Calculate relative luminance from RGB
function relativeLuminance(r: number, g: number, b: number): number {
  const [rs, gs, bs] = [r, g, b].map((c) => {
    c /= 255;
    return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
  });
  return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
}

// Calculate contrast ratio between two colors
function contrastRatio(
  rgb1: [number, number, number],
  rgb2: [number, number, number]
): number {
  const l1 = relativeLuminance(...rgb1);
  const l2 = relativeLuminance(...rgb2);
  const lighter = Math.max(l1, l2);
  const darker = Math.min(l1, l2);
  return (lighter + 0.05) / (darker + 0.05);
}

// Check if a color combination meets WCAG standards
const white: [number, number, number] = [255, 255, 255];
const orange: [number, number, number] = [255, 107, 0];
const darkGray: [number, number, number] = [51, 51, 51];

contrastRatio(orange, white);     // ~3.0:1 — Fails AA for normal text
contrastRatio(darkGray, white);   // ~10.7:1 — Passes AAA

A practical tip: when using HSL, colors with a lightness below 45% generally have sufficient contrast against white backgrounds, while colors with lightness above 55% work well against dark backgrounds. This is a useful heuristic, but always verify with an actual contrast ratio calculation.

Quick Reference Table

Here is a summary comparing the three color models across key dimensions.

FeatureHEXRGBHSL
Format#RRGGBBrgb(R, G, B)hsl(H, S%, L%)
Range00-FF per channel0-255 per channel0-360, 0-100%, 0-100%
Human-readableLowMediumHigh
Easy to adjustNoSomewhatYes
Alpha support#RRGGBBAArgba()hsla()
Best forStatic valuesProgrammatic useDesign systems

Need to extract data from websites?

PulpMiner turns any webpage into structured JSON data. No scraping code needed.

Try PulpMiner Free

No credit card required