CSS Color Spaces: hex, rgb, hsl, lab, and lch
CSS gives you several ways to describe color, from familiar hexadecimal values to perceptual spaces like lab() and lch(). This article explains what each color space is, how the syntax works, when to use each one, and how to avoid common mistakes when colors do not look the way you expect.
Quick answer: Use hex or rgb() for simple, widely supported color values, hsl() when you want to reason about hue, saturation, and lightness, and lab() or lch() when you want more perceptually uniform color control.
Difficulty: Beginner to Intermediate
You'll understand this better if you know: basic CSS syntax, how selectors apply styles, and the difference between values like numbers, percentages, and named colors.
1. What Is CSS Color?
CSS color is the value you use to describe the foreground, background, border, shadow, and other paintable parts of an element. A color value can be written in different color spaces, each with its own syntax and strengths.
- hex uses hexadecimal notation and is common in design tools and codebases.
- rgb() describes color by red, green, and blue channels.
- hsl() describes color by hue, saturation, and lightness.
- lab() and lch() are more perceptual and are useful for advanced color work.
- All of these can be used anywhere CSS accepts a color value.
These formats solve the same problem: how to tell the browser what color to draw. The difference is how humans think about the color and how precisely the browser can interpolate or adjust it.
2. Why CSS Color Spaces Matter
Color choice affects readability, branding, accessibility, and visual consistency. Using the right color space makes it easier to build UI states, themes, and palettes that behave predictably.
For example, if you want a lighter version of a brand color, hsl() may be easier to adjust than hex. If you want smoother gradients or better perceptual stepping between colors, lab() and lch() often produce more natural results than older RGB-based workflows.
Color spaces also matter when mixing colors, animating colors, or maintaining design systems across many components. A value that is easy to read by a designer may not be the easiest value for a developer to maintain, so the best format depends on the job.
3. Basic Syntax or Core Idea
CSS color values appear directly in properties such as color, background-color, and border-color. The browser interprets the color space from the function or notation you choose.
Hex color syntax
hex uses a leading # followed by three, four, six, or eight hexadecimal digits. Six digits are most common.
p {
color: #336699;
}In this example, the color is defined with red, green, and blue channel pairs: 33, 66, and 99.
RGB syntax
rgb() lets you specify channels as integers from 0 to 255 or as percentages in modern CSS.
p {
color: rgb(51, 102, 153);
}You can also add alpha transparency with slash notation.
p {
color: rgb(51 / 80%);
}HSL syntax
hsl() describes color using hue in degrees, saturation as a percentage, and lightness as a percentage.
p {
color: hsl(210 50% 40%);
}The hue chooses the base color family, saturation controls color intensity, and lightness moves the color toward black or white.
Lab and LCH syntax
lab() and lch() are newer CSS color functions. They are designed to match human perception better than RGB-based spaces.
p {
color: lab(54% -10 -20);
background-color: lch(65% 40 250);
}In lab(), the first value is lightness, followed by two color-opponent axes. In lch(), the values are lightness, chroma, and hue.
4. Step-by-Step Examples
This section shows how the same basic color can be written in different color spaces and why you might choose one over another.
Example 1: The same blue in hex and rgb
Hex is compact, while RGB makes the channel values explicit.
.button {
background-color: #336699;
color: rgb(255 / 100%);
}This works well when you already know your brand color and just need a readable way to apply it.
Example 2: Lightening a color with hsl
HSL is often easier when you want a color family with several lighter or darker variants.
.card {
background-color: hsl(210 60% 92%);
border-color: hsl(210 60% 70%);
}Both colors stay in the same blue family, but the different lightness values create visual hierarchy.
Example 3: Transparent overlays with rgb alpha
Alpha values let you create overlays and soft backgrounds without a separate opacity property.
.overlay {
background-color: rgb(0 0 0 / 60%);
}This is a common way to dim the page behind a modal or tooltip.
Example 4: Using lch for smooth color steps
lch() is useful when you need a sequence of colors that feels evenly spaced to the eye.
.swatch-1 { background-color: lch(80% 25 40); }
.swatch-2 { background-color: lch(70% 25 40); }
.swatch-3 { background-color: lch(60% 25 40); }Because the colors share the same hue and chroma, only the lightness changes from one swatch to the next.
5. Practical Use Cases
- Use hex for design tokens, static brand colors, and quick hand-written styles.
- Use rgb() when you need channel-by-channel control or transparency.
- Use hsl() for theme systems where lightness and hue adjustments matter more than raw channel values.
- Use lab() or lch() for color ramps, advanced palette generation, and smoother interpolation.
- Use alpha channels for overlays, disabled states, and subtle surface tints.
In a design system, you might store a brand color in hex, generate lighter variants with hsl(), and use lch() for data visualization palettes that need consistent visual spacing.
6. Common Mistakes
Mistake 1: Mixing up HSL lightness with brightness
hsl() lightness does not behave exactly like a simple brightness slider. Beginners often expect increasing lightness to preserve the same visual appearance, but some hues shift in a way that feels less uniform than expected.
Problem: This color looks washed out or unexpectedly dull because the saturation and lightness combination is not tuned for the effect you want.
.badge {
background-color: hsl(30 100% 90%);
}Fix: Lower the lightness and tune saturation so the color still reads clearly as the intended hue.
.badge {
background-color: hsl(30 85% 60%);
}The corrected version works better because the color keeps more visual strength while still being lighter.
Mistake 2: Expecting hex to be easy for transparency math
Hex can include alpha in eight-digit form, but it is not the clearest format when you want to reason about opacity values quickly.
Problem: This code may be syntactically valid, but it is hard to read and easy to miscalculate when you are adjusting opacity by hand.
.panel {
background-color: #00000099;
}Fix: Use rgb() with alpha when you want the transparency to be obvious at a glance.
.panel {
background-color: rgb(0 0 0 / 60%);
}The corrected version is easier to maintain because the opacity is expressed directly as a percentage.
Mistake 3: Using lab or lch without browser support in mind
lab() and lch() are modern, but older browsers may not support them. If you rely on them without a fallback, the color may be ignored.
Problem: In unsupported browsers, the declaration is dropped and the element may fall back to inherited color or default styling.
.heading {
color: lch(55% 40 240);
}Fix: Provide a widely supported fallback before the modern color value.
.heading {
color: #336699;
color: lch(55% 40 240);
}The fallback works because browsers that do not understand lch() still apply the earlier hex value.
Mistake 4: Assuming color keywords are the same as precise color spaces
Named colors like red and rebeccapurple are convenient, but they are not a substitute for deliberate palette design.
Problem: This can create inconsistent branding because the chosen keyword may not match your palette or contrast goals.
.cta {
background-color: red;
}Fix: Use a deliberate color value in the space that matches your workflow, such as hex, rgb(), or hsl().
.cta {
background-color: hsl(8 90% 55%);
}The corrected version works better because it gives you a controllable, repeatable color choice.
7. Best Practices
Prefer the color space that matches your task
If you are assigning a known brand color, hex is usually concise and readable. If you are adjusting intensity and transparency, rgb() or hsl() may be easier to work with.
.brand {
color: #1f4fff;
}Choosing the right format reduces mental overhead when you or a teammate revisits the CSS later.
Use alpha in the same color value when transparency is part of the design
Putting opacity inside the color value often makes the stylesheet easier to understand than combining a solid color with a separate opacity property.
.tooltip {
background-color: rgb(17 24 39 / 92%);
}This keeps the translucency tied to the color itself, which is especially useful for overlays and surfaces.
Provide a fallback for modern color spaces
For broader compatibility, write a supported color first and a newer one after it. Browsers that understand the new syntax will use it, and older browsers will keep the fallback.
.accent {
border-color: #336699;
border-color: lch(50% 30 260);
}This pattern is simple and protects your design when support varies.
8. Limitations and Edge Cases
- hex is compact, but it does not describe hue or lightness in a human-friendly way.
- rgb() can feel mechanical when you want to build a palette by eye.
- hsl() is intuitive for many designers, but equal changes in lightness do not always look equally spaced.
- lab() and lch() are better for perceptual control, but browser support and team familiarity may be lower.
- Color appearance can vary depending on surrounding colors, contrast, and display characteristics, so the same value may feel different in context.
- Some browsers or tools may display advanced color spaces differently when copy-pasted from design software or converted by editors.
If a color looks “wrong,” the issue is often not the syntax itself. It may be the chosen color space, the nearby UI colors, or the fact that the browser is interpolating between values in a different space than you expected.
9. Practical Mini Project
Here is a small card component that uses different color spaces for different parts of the design. It combines a solid brand color, a translucent surface, and an accessible accent.
.card {
padding: 1.5rem;
border-radius: 1rem;
background-color: hsl(210 40% 96%);
border: 1px solid lch(80% 20 250);
box-shadow: 0 10px 30px rgb(15 23 42 / 10%);
}
.card h2 {
color: #1e3a8a;
}
.card a {
color: hsl(220 80% 45%);
}This example shows a practical pattern: use one color space where it makes the code easiest to understand, and choose another space when you need more control over visual behavior.
10. Key Points
- hex is compact and common for fixed colors.
- rgb() is good when you want direct channel control and alpha transparency.
- hsl() is helpful for building color variants by hue and lightness.
- lab() and lch() are better suited to perceptual color work and smoother color transitions.
- Fallbacks improve compatibility when using newer color spaces.
- The best color space depends on readability, maintenance, and browser support.
11. Practice Exercise
Try rewriting the same UI palette in three different color spaces so you can feel the difference in how each one works.
- Create a primary button background in hex.
- Create a hover state for the same button in hsl() by changing only the lightness.
- Create a translucent overlay using rgb() with alpha.
Expected output: a button with a solid brand color, a clearly lighter hover state, and a soft dimmed overlay behind it.
Hint: Keep the hue the same for the button states and change only one or two values at a time.
.button {
background-color: #2563eb;
color: #ffffff;
padding: 0.75rem 1rem;
border: none;
border-radius: 0.5rem;
}
.button:hover {
background-color: hsl(217 91% 55%);
}
.backdrop {
background-color: rgb(15 23 42 / 50%);
}Use the exercise to notice which syntax is easiest for each visual task.
12. Final Summary
CSS color spaces give you different ways to express the same visual idea. hex is compact and familiar, rgb() is explicit and flexible, hsl() is intuitive for hue-based adjustments, and lab() and lch() offer more perceptual control for advanced work.
The best choice depends on what you are trying to do. If you need quick, readable values, start with hex or rgb(). If you are creating variants, themes, or design-system colors, hsl() can be more convenient. If you need modern perceptual color handling, add lab() and lch() with a safe fallback.
Once you understand how these spaces differ, you can choose colors more intentionally and write CSS that is easier to maintain, easier to debug, and better aligned with design goals. A good next step is to explore color contrast and CSS gradients, since both topics build directly on the color values covered here.