Skip to main content
C
CodeUtil

Color Theory for Developers: RGB, HSL, and CSS Colors

Master color models for web development. Learn RGB, HSL, HEX, and CMYK differences, when to use each format, CSS color functions, accessibility contrast ratios, and how to build effective color palettes.

2025-03-0614 min
Related toolColor Converter

Use the tool alongside this guide for hands-on practice.

The color code that made me finally learn this stuff

I used to just copy HEX codes from Figma and call it a day. Then a designer asked me to make a button 15% darker on hover. I had no idea how to do that without asking for another color code.

That's when I realized I'd been working with colors for years without actually understanding them. Now I can build entire color systems, create accessible palettes, and have actual conversations with designers. Here's what I wish I'd learned earlier.

RGB: Where it all starts

Every color on your screen is red, green, and blue light mixed together. That's it. Once this clicked for me, everything else made sense.

  • Each channel: 0-255 (0 = none of that color, 255 = maximum)
  • rgb(0, 0, 0) = black (all lights off)
  • rgb(255, 255, 255) = white (all lights full blast)
  • rgb(255, 0, 0) = pure red
  • 16.7 million possible combinations (256³)

The part that confused me: additive mixing

RGB mixing is the opposite of paint. Adding colors makes things BRIGHTER, not darker. It took me embarrassingly long to internalize this.

  • Red + Green = Yellow (not brown like paint!)
  • Red + Blue = Magenta
  • Green + Blue = Cyan
  • All three at max = White
  • Equal R, G, B values = some shade of gray

HEX: Just compressed RGB

HEX isn't a different color system - it's just RGB written in hexadecimal. #FF0000 is rgb(255, 0, 0). Once I understood this, converting became trivial.

  • #RRGGBB format: two hex digits per channel
  • #FF0000 = rgb(255, 0, 0) = red
  • #F00 is shorthand for #FF0000
  • #RRGGBBAA adds alpha (transparency)
  • Case doesn't matter: #ff0000 = #FF0000

Quick JS conversion

When I need to convert manually (rare these days with tools):

  • HEX to decimal: parseInt("4A", 16) → 74
  • Decimal to HEX: (74).toString(16) → "4a"
  • Or just use the Color Converter - I built it for exactly this reason

HSL: The game changer

HSL changed how I work with colors. Instead of guessing RGB values, I think in terms humans understand: what COLOR (hue), how VIVID (saturation), how LIGHT or DARK (lightness).

  • Hue: 0-360° on the color wheel. 0=red, 120=green, 240=blue
  • Saturation: 0%=gray, 100%=fully vivid
  • Lightness: 0%=black, 50%=pure color, 100%=white
  • CSS: hsl(220, 80%, 50%) - easy to read and adjust

Why I actually prefer HSL now

Remember that hover state problem? With HSL it's trivial:

  • Darker hover: just subtract 10% from lightness
  • Lighter variant: add 10% to lightness
  • Less saturated: reduce saturation
  • Related color: shift hue by 30°
  • No more guessing RGB values - the changes are predictable

HSV/HSB (why designers and devs speak different languages)

Figma and Photoshop use HSB (Hue, Saturation, Brightness), not HSL. The values look similar but aren't interchangeable. I've been burned by this when copying values directly.

  • Hue is the same as HSL
  • But Saturation and Brightness calculate differently
  • CSS doesn't support HSB directly - you must convert
  • Use a converter when working with design tool values

CMYK (when print people call)

I rarely deal with CMYK, but occasionally a client needs print materials to match their web colors. Heads up: not all screen colors can be printed. CMYK's gamut is smaller.

  • Subtractive mixing (like paint) - opposite of RGB
  • Not supported in CSS at all
  • Some vibrant screen colors look dull when printed
  • Always do a print test for brand colors

Named colors (my guilty pleasure)

I use named colors more than I probably should. They're readable and great for prototyping. tomato, coral, rebeccapurple - they just make code nicer to read.

  • 147 named colors in CSS
  • rebeccapurple (#663399) - added to honor Eric Meyer's daughter
  • transparent = rgba(0,0,0,0)
  • currentColor = inherits text color (super useful)

Modern CSS color functions

CSS keeps getting better. color-mix() and relative color syntax mean I rarely need Sass for color manipulation anymore.

  • color-mix(in srgb, red 30%, blue) - blend colors natively
  • hsl(from var(--primary) h s calc(l - 10%)) - darken by modifying lightness
  • light-dark() - automatic light/dark mode colors
  • Check browser support before going all-in on these

CSS variables changed everything

I use CSS custom properties for every color now. Theming and dark mode become trivial.

  • :root { --primary: #3498db; }
  • Use anywhere: background: var(--primary);
  • Fallbacks: var(--primary, blue)
  • Change with JS: element.style.setProperty("--primary", "#new")
  • Dark mode: swap values in @media (prefers-color-scheme: dark)

The HSL variable trick

Here's a technique I use constantly: store HSL components separately. This lets you do math on colors.

  • :root { --hue: 220; --sat: 80%; --light: 50%; }
  • Primary: hsl(var(--hue), var(--sat), var(--light))
  • Lighter: hsl(var(--hue), var(--sat), calc(var(--light) + 15%))
  • Change --hue to completely retheme the site

Accessibility (this part is non-negotiable)

WCAG contrast requirements aren't suggestions. I've had to fix accessibility issues in production too many times. Now I check contrast as I go.

  • Normal text: 4.5:1 contrast minimum (WCAG AA)
  • Large text (18pt+): 3:1 minimum
  • UI components: 3:1 against adjacent colors
  • Chrome DevTools shows contrast ratios in the color picker

Contrast mistakes I kept making

Took me a while to break these habits:

  • #999 on white is only 2.85:1 - fails. I used this everywhere
  • Light blue links (#0088cc) fail for normal text
  • Placeholder text is almost always too light
  • Hover states often reduce contrast too much

Color blindness (8% of men)

Red-green colorblindness affects 8% of men. Never rely on color alone to convey meaning.

  • Add icons or text alongside color indicators
  • Error states need icons, not just red
  • Charts need patterns or direct labels
  • Chrome DevTools has built-in colorblind simulation

Building a color palette

At Šikulovi s.r.o., we structure palettes like this:

  • Primary: Main brand color for CTAs
  • Secondary: Accent color
  • Neutrals: Grays for text, borders, backgrounds
  • Semantic: Red/green/yellow for errors/success/warnings
  • Each color gets 5-10 shades (Tailwind 50-900 convention)

Color harmony (the quick version)

When I need to pick colors that work together:

  • Complementary: Opposite on wheel (hue ± 180°) - bold contrast
  • Analogous: Adjacent (hue ± 30°) - safe and harmonious
  • Triadic: Evenly spaced - vibrant but balanced
  • 60-30-10 rule: 60% dominant, 30% secondary, 10% accent

Generating shades with HSL

This is my formula for consistent shade scales:

  • Base color at lightness ~50%
  • Lightest (50): 95% lightness
  • Light (100-300): 85-65% lightness
  • Medium (400-600): 55-45% lightness
  • Dark (700-900): 35-15% lightness
  • Slightly decrease saturation for lighter shades

Dark mode (harder than it looks)

Dark mode isn't just inverting colors. I learned this the hard way when our first dark mode attempt looked terrible.

  • Do not use pure black (#000): Too harsh, causes eye strain
  • Use dark gray backgrounds: #121212 to #1a1a1a
  • Reduce white text brightness: Use #e0e0e0 instead of #ffffff
  • Increase color saturation slightly for dark backgrounds
  • Maintain WCAG contrast ratios (they work in both directions)
  • Shadows become less effective—use subtle borders or elevation
  • Test extensively: Colors that work light may not work dark

Automatic dark mode with CSS

Modern CSS makes detecting user preferences easy:

  • @media (prefers-color-scheme: dark) { }
  • color-scheme: light dark; - enables automatic form styling
  • System colors like Canvas and CanvasText match OS theme

JavaScript color work

When I need to manipulate colors in JS (data viz, dynamic theming):

  • getComputedStyle(el).backgroundColor - always returns rgb()
  • chroma.js - my go-to library for serious color work
  • Convert to HSL, adjust, convert back - predictable results

Wide gamut colors (P3)

Display P3 is 25% bigger than sRGB. Most new Apple devices support it. But honestly, I rarely bother - the difference is mostly noticeable in very vivid colors.

  • CSS syntax: color(display-p3 1 0.5 0)
  • Always provide sRGB fallback
  • Mostly affects super vibrant colors

Tools I actually use

My color workflow tools:

  • Chrome DevTools color picker - has contrast checker built in
  • CodeUtil's Color Converter - obvious choice
  • Coolors.co - when I need palette inspiration
  • WebAIM contrast checker - for verification

What I learned the hard way

Understanding color models wasn't optional - it was holding me back. Now I can actually talk to designers, build dark mode that works, and create accessible interfaces without guessing.

  • Black: #000000 = rgb(0,0,0) = hsl(0,0%,0%)
  • White: #ffffff = rgb(255,255,255) = hsl(0,0%,100%)
  • Pure red: #ff0000 = rgb(255,0,0) = hsl(0,100%,50%)
  • Pure green: #00ff00 = rgb(0,255,0) = hsl(120,100%,50%)
  • Pure blue: #0000ff = rgb(0,0,255) = hsl(240,100%,50%)
  • 50% gray: #808080 = rgb(128,128,128) = hsl(0,0%,50%)
  • Transparent: transparent = rgba(0,0,0,0) = hsla(0,0%,0%,0)

Conclusion: Practical color mastery

Understanding color theory transforms how you write CSS and build interfaces. RGB and HEX are the foundation, HSL makes manipulation intuitive, and accessibility requirements guide your choices.

Start by using HSL for your color systems—it makes creating variations and maintaining consistency far easier than RGB. Always check contrast ratios, never rely on color alone for meaning, and test across different color vision types. With these principles, you will build more accessible, maintainable, and visually consistent applications.

FAQ

What is the difference between RGB and HEX colors?

RGB and HEX represent the same colors—HEX is just a compact hexadecimal notation of RGB values. #FF5733 is identical to rgb(255, 87, 51). HEX is shorter to write, while RGB is more readable when you need to understand or modify individual color channels.

When should I use HSL instead of RGB?

Use HSL when you need to create color variations (lighter, darker, desaturated), build consistent palettes, or make colors programmatically adjustable. HSL is more intuitive because hue, saturation, and lightness map to how humans think about color, making it easier to predict results when adjusting values.

What contrast ratio do I need for accessibility?

WCAG AA requires 4.5:1 for normal text and 3:1 for large text (18pt or 14pt bold). WCAG AAA requires 7:1 for normal text and 4.5:1 for large text. UI components and graphical objects need at least 3:1 contrast against adjacent colors.

How do I test my colors for color blindness?

Chrome DevTools has a built-in color blindness simulator: open DevTools, press Ctrl+Shift+P, search for "Rendering", and enable "Emulate vision deficiencies". Also, ensure you never use color alone to convey information—always add icons, patterns, or text labels.

How do I create a color palette for dark mode?

Start with dark gray backgrounds (#121212 to #1a1a1a) instead of pure black. Use slightly muted text colors (#e0e0e0) instead of pure white. Increase saturation slightly for accent colors on dark backgrounds. Always verify WCAG contrast ratios still pass in dark mode.

What is the best way to store colors in CSS?

Use CSS custom properties for maintainability. Store HSL components separately (--hue, --sat, --light) to enable easy manipulation. Group colors by purpose (--color-primary, --color-error) rather than appearance. This approach enables theming and makes your color system flexible.

How do I convert CMYK to RGB for web use?

CMYK to RGB conversion requires mathematical transformation since they use different color models. Use a color converter tool like our Color Converter at /color for accurate results. Note that some CMYK colors cannot be exactly reproduced in RGB due to gamut differences.

What are P3 colors and should I use them?

Display P3 is an extended color space with about 25% more colors than sRGB, supported on modern displays. Use them for vivid accents on supporting devices, but always provide sRGB fallbacks. The difference is most noticeable in bright, saturated colors.

Martin Šikula

Founder of CodeUtil. Web developer building tools I actually use. When I'm not coding, I experiment with productivity techniques (with mixed success).

Related articles