The Mystery of Tailwind Colors (v4)

Overview

This article explores how Tailwind CSS v4 has adopted OKLCH color notation and why this change is significant for web developers and designers. You’ll discover the fundamentals of color spaces, learn why OKLCH offers better color ma…


This content originally appeared on DEV Community and was authored by Matteo Frana

Overview

This article explores how Tailwind CSS v4 has adopted OKLCH color notation and why this change is significant for web developers and designers. You'll discover the fundamentals of color spaces, learn why OKLCH offers better color manipulation than HSL, and understand how to replicate Tailwind's color system for your own custom colors.

Introduction

Tailwind CSS recently released v4 with several new features. A key change is that colors are now defined using OKLCH notation, such as oklch(0.685 0.169 237.323). I had never encountered this color notation before and I saw that it enabled more vivid colors. My nerd curiosity kicked in, leading me to explore high-gamut color spaces and discover why OKLCH notation is better than HSL.

During this time, I also needed to create custom colors for a Tailwind CSS project. This presented the perfect opportunity for learning and some late-night hacking sessions. The result was uihue.com. In this article, I'll share what I learned and explain the algorithm I developed to generate color hues.

A bit of Color Theory

The CSS color module level 4 specification introduces new syntax for expressing colors in the standard sRGB color space with rgb(…) or hsl(…). The modern syntax separates color components with spaces instead of commas, uses a slash for the optional alpha value, and allows mixing percentages with numbers. For example, rgba(255, 0, 0, 0.5) becomes rgba(100% 0% 0% / 50%).

More significantly, this specification introduces new ways to define colors using different color spaces. But what exactly is a color space?

Understanding Color Spaces: how we see and define colors

Let's start with what a color is. A color is our visual perception of matter based on its electromagnetic spectrum (the amount of light at each visible frequency that travels from an object to our eyes). For objects that don't emit light, color depends on the spectrum of the light hitting them and how they absorb and reflect it. For objects that do emit light, we must also consider their emission spectrum—how much light they emit at each visible frequency.

The CSS specification defines color as "a definition (numeric or textual) of the human visual perception of a light or a physical object illuminated with light." For the specification's purposes, what matters is how a color can be expressed as a string or number.

In this regard, the specification defines a color space as "an organization of colors with respect to an underlying colorimetric model, such that there is a clear, objectively measurable meaning for any color in that color space." A single color can be expressed in different color spaces, though some colors may only be represented in certain color spaces.

Color Gamut: from sRGB to Modern Display Standards

No display can reproduce all the colors that the human eye can perceive. The range of colors a display can produce is called a "gamut." Most modern displays show colors in the "sRGB" color space's gamut, which covers about 35% of all human-visible colors. These colors can be expressed using the rgb(…) or hex notation (e.g. #f65a8e).

Modern devices, particularly Apple computers and phones, can display a broader range of colors—usually more saturated ones. This capability requires new color spaces and notation forms to express these wider gamuts.

Color spaces, arranged from smallest to largest gamut, are: sRGBDisplay P3 ⇒ Adobe RGB 98 ⇒ REC.2020 ⇒ ProPhoto RGB. Current Apple devices can usually display colors up to the REC.2020 color space gamut.

Color Space Components and Notation

Colors in a CSS color space notation are represented as a list of color components (also called "channels") that represent components along the axes in the color space. Each channel has a minimum and maximum value, and any color with values outside these ranges is considered invalid.

Note about Alpha

We won't discuss the additional alpha component, which controls transparency, as it can be considered a post-processing operation that blends a color with whatever is beneath it.

Examples of color notations:

  • rgb() defines colors in the sRGB color space using red, green, and blue channels
  • hsl() defines colors in the sRGB color space using hue, saturation, and lightness in the HSL cylindrical coordinate model

CSS level 4 introduces additional color notations: hwb for sRGB, lab and lch for CIELAB, and oklab and oklch for Oklab, along with a general color function for various color spaces.

In this article, we'll focus on oklch—the most relevant option and the one Tailwind v4 uses for its predefined colors. We won't delve into technical topics like the differences between CIE Lab and Oklab color spaces or cartesian versus cylindrical coordinates.

OKLCH: A Better Way to Express Color

The OKLCH color notation is based on the Oklab color space, designed to enhance perceptual uniformity, hue and lightness prediction, and color blending. It was introduced by Björn Ottosson in December 2020. OKLCH represents colors using cylindrical coordinates in the Oklab color space.

Here are the three key aspects of the OKLCH notation:

  1. It allows colors to be expressed in the P3 or REC.2020 gamut—colors beyond what rgb(), hsl(), or hex formats can represent. This future-proof notation can even define colors that current devices cannot yet display.
  2. Its coordinates are:
    1. L (Perceived Lightness): ranges from 0 to 1, or 0% to 100%
    2. C (Chroma, or saturation): ranges from 0 to infinity (but it always stays below 0.37 in the P3 color space)
    3. H (Hue): ranges from 0 to 360 degrees
  3. It uses perception-based lightness and saturation, solving major perceptual issues found in HSL notation and enabling easier color manipulation (see next section)

HSL's Perceptual Limitations

Let's explore the two major perceptual limitations of HSL and the sRGB color space.

Variable Maximum Saturation

In HSL, the maximum saturation remains constant across all hues. However, in reality, maximum saturation varies depending on both hue and lightness—this applies to both displays and the human eye.

I recommend checking out the excellent OKLCH color picker at oklch.com. Be sure to enable the "Show 3D" switch (because who doesn't love going full nerd?) to view the 3D model of the color space. At the "base" of these "color mountains" you'll find a Hue/Lightness diagram with zero Chroma (showing only grays), while the mountains' heights represent the maximum Chroma available across different hues and lightness levels.

You can see from these two images of the 3D OKLCH color space (from oklch.com) how greens have a higher maximum chroma compared to yellows and how blue reaches its highest chroma at low lightness levels.

Oklch color space 3D

Non-Uniform Lightness in HSL

A critical issue with HSL is that its lightness values don't align with human visual perception across different hues. Two colors can have the same HSL lightness value yet appear completely different in brightness to our eyes.

The Oklch notation in the Oklab color space solves this problem by ensuring that identical lightness values create the same perceived brightness.

The example below illustrates this difference: colors with the same HSL lightness can look dramatically different in brightness. However, when we change only the hue in Oklch while keeping the lightness constant, all colors appear equally bright.

HSL non-uniform lightness issue

OKLCH in Practice: Three Game-Changing Benefits

Better Color Manipulation

As we saw, OKLCH ensures that Lightness and Chroma values are perceptually consistent across all hues. This enables better mathematical color manipulation. When you need a specific lightness level to ensure sufficient contrast with white text, OKLCH provides reliable results, unlike HSL. This makes color adjustment functions like darken/lighten more reliable.

Better Color Gradients

While HSL gradients tend to create unwanted gray areas when colors mix, OKLCH's perceptually uniform model produces smooth, visually balanced gradient transitions.

OKLCH better color gradients

Wider Gamut

OKLCH lets us express and use colors with a higher gamut, producing more vibrant, eye-catching designs that modern devices can display. While the image on the left appears more saturated, it has been converted to sRGB due to Dev.to's image processing, but it still illustrates the concept.

OKLCH Wider Gamut P3 REC.2020

Browser Compatibility and Fallback Strategy

With all these advantages of OKLCH in mind, you might wonder about browser support. The good news is that as of early 2025, OKLCH enjoys full support across all major modern browsers. And when colors fall outside a display's gamut, browsers automatically handle the conversion to a supported gamut.

Decoding Tailwind's Color Architecture

Do you know how many color types (different hues) Tailwind includes? You might guess 10 or 12? Maybe 15?

Actually, there are 22! They are: Red, Orange, Amber, Yellow, Lime, Green, Emerald, Teal, Cyan, Sky, Blue, Indigo, Violet, Purple, Fuchsia, Pink, Rose, Slate, Gray, Zinc, Neutral, and Stone—plus black and white, making it 24 in total.

Each of these color types has a palette of 11 different shades (50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950). The 50 shade represents the lightest color, while 950 represents the darkest.

Before Tailwind v4, these colors were defined in the sRGB color space using HSL or HEX format. With Tailwind v4, colors are now defined using the OKLCH format in the Oklab color space. This change allowed Tailwind expert designers to choose colors outside the sRGB gamut—in fact, you will find some more saturated colors that exist only in the P3 gamut.

Tailwind v4 Sky color palette with some P3 colors

Anatomy of a Tailwind Color Palette

You might expect the hue and saturation to remain constant across the whole palette, and the lightness to decrease linearly from 50 to 950—especially since the Oklab model is perceptually uniform, right?

I am sorry to disappoint you: all these assumptions are wrong. You can see the charts for all the new Tailwind colors, showing how lightness, chroma, and hue change across different shades, here: https://www.uihue.com/tailwind-colors-charts

Here are three examples:

Tailwind colors' palettes LCH charts lightness chroma hue

As you can see, none of these diagrams follows a linear pattern. I stumbled upon this surprising discovery while trying to replicate Tailwind's palette-building approach for custom color palettes.

Don't worry though—let's try to break down the underlying patterns.

Lightness

The Lightness follows a non-linear curve that remains fairly consistent across color palettes, though grays show a steeper decrease in the darker shades.

Chroma

The chroma (saturation) follows a Gaussian curve pattern, peaking between the "400" and "600" shades, with a sharper decrease in the lighter shades (toward the left side).

Hue

In the charts, hue values have been normalized by subtracting the minimum hue value. Without this normalization, hues near 0° (like reds) would show larger min-max differences than higher hues (like violet). This normalization allows you to better compare hue variations across the entire spectrum.

Looking at all the charts, you'll notice that hue remains fairly consistent across different shades, with two notable exceptions: yellows shift toward orange in darker shades, and blues shift toward violet in darker shades.

From Analysis to Algorithm: Recreating Tailwind's Colors

My mathematical mind's first instinct was to derive three average curves—for lightness, chroma, and hue—across all colors (maybe with separate rules for colors and grays). I planned to use a Fourier transform to approximate these curves and create a clean rule for generating new colors.

However, this approach wouldn't work. I wanted to achieve the highest possible fidelity in replicating Tailwind colors, but averaging would have flattened the subtle hue differences that make each palette special. I would have lost crucial nuances, like how yellows become more orange when darker, or how azure shifts toward blue-violet. Then a much simpler idea struck me.

I could simply identify the nearest Tailwind color for any given input color and apply the same rules that Tailwind uses for that nearest color. So, the first step was to find the nearest Tailwind color to the user's chosen color.

Finding the Nearest Color

Given a collection of colors and a target color, how do we determine which color in the collection is closest to our target? Simple: we test each color, measure the distance, and choose the one with the minimum distance. But this raises another question: how do we define the distance between two colors?

The simplest approach uses Euclidean distance across the N axes of the color representation (essentially applying the Pythagorean theorem in N dimensions). In the sRGB color space, for example, we could calculate the Euclidean distance using the R, G, and B axes:

distance=(R2−R1)2+(G2−G1)2+(B2−B1)2 distance = \sqrt{(R_2-R_1)^2 + (G_2-G_1)^2 + (B_2-B_1)^2} distance=(R2R1)2+(G2G1)2+(B2B1)2

Unfortunately, this distance calculation isn't very effective. Consider the example in the following image: blue and violet have a greater Euclidean distance than yellow and orange, even though they appear much more similar to our eyes.

Color difference with euclidean distance on sRGB

If we switch to a perceptually uniform color space, such as Lab, the Euclidean distance becomes much more reliable.

I then learned about a family of algorithms called "DeltaE" (DeltaE) (ΔE), specifically designed to calculate the difference between two colors. The first version, the CIE 1976 formula, simply used the Euclidean distance of colors in the Lab color space. However, when Lab proved less perceptually uniform than initially thought, the algorithm went through several revisions in 1984, 1994, and finally 2000—resulting in the most accurate, though most complex, Lab-based DeltaE algorithm to date.

I used the DeltaE 2000 algorithm implementation from the Colorjs.io library to iterate through the Tailwind CSS colors and find the nearest match. I'm also interested in testing a simpler Euclidean distance calculation in a more advanced color space like JzCzhz.

Generating Color Palettes: The Algorithm

Now that we have the nearest Tailwind color (let's say it's "sky-700"), we can proceed with generating a complete palette.

The user's selected color becomes the "700" shade in the palette—our "base shade." From there, we need to generate both lighter and darker shades.

A simple approach would be to take the hue of the user's selected color and apply the same lightness and chroma values from the nearest Tailwind color for each shade.

However, this would cause us to lose the unique characteristics of the user's color, for example lower saturation or slightly lower lightness compared to the Tailwind color.

Simply applying Tailwind's lightness and chroma deltas from the base shade seemed promising, but this approach could produce completely desaturated colors at the extreme ends of the palette.

Instead, I applied these deltas with a smoothing effect as we approach the extreme light and dark hues. This preserves the color's distinctive features where they matter most—in the middle shades—while ensuring balanced results for the lightest and darkest shades.

Generate Tailwind-like color palettes

Name that Color!

As developers, we know naming things is one of the hardest tasks. When I needed to give each user's color pick in uihue a beautiful name, I faced quite a challenge.

Initially, I considered using the standard HTML 4.01 named colors like "red," "lime," "aliceblue," or "papayawhip." But with only 148 named colors available, this wouldn't provide enough unique names for the vast spectrum of possible colors.

I then found a massive list containing over 30k colors. However, calculating 30k color distances for each color pick would be unnecessarily resource-intensive. Instead, I settled on using NTC colors—a collection of 1,566 names that provides enough variety to find beautiful color names without excessive CPU usage.

User-Friendly OKLCH Color Selection

As far as I know, there's just one dedicated color picker for the OKLCH color space: the excellent tool at oklch.com. While it's really great and features the neat 3D representation of the color space, this kind of interface might intimidate users who aren't familiar with how OKLCH works.

OKLCH Color Picker from oklch.com

Instead, I decided to implement a standard HSL color picker in the sRGB color space, convert the color to OKLCH, and then let users increase the chroma with a simple slider. The interface clearly shows when colors enter higher gamuts like P3 or REC2020.

uihue OKLCH color picker with sRGB + chroma adjustment

I'm very satisfied with the result: users find it both user-friendly and fun to use.

Implementing Accessible Color Contrast

In the uihue.com app, I display color shade numbers over the colored squares of the generated palette, using either light or dark text.

Accessible Color Contrast

How do I determine which text color provides better contrast against each shade's background? Simple—I measure the contrast ratio for both options and choose the higher one.

As you might expect, there's science behind this. Several algorithms exist for calculating contrast between text and background colors, including: Weber contrast, Michelson contrast, Advanced Perceptual Contrast Algorithm (APCA), Lightness difference in the CIE Lightness L* space, Delta Phi Star (ΔΦ*), and WCAG 2.1.

I chose to implement the APCA algorithm (through the Colorjs.io library), as it offers the best performance and is being considered for inclusion in version 3 of the W3C Web Content Accessibility Guidelines (WCAG).

Conclusion

In this journey through color spaces and Tailwind's color system, we've explored the advantages of OKLCH over traditional color notations, particularly in web development. The transition to OKLCH in Tailwind v4 represents a significant step forward in how we handle colors in modern web design, offering better perceptual uniformity, wider gamut support, and more reliable color manipulations.

As display capabilities improve, the OKLCH color space will become increasingly important. It enables us to create more vibrant, accessible, and visually appealing designs while maintaining precise control over color relationships and contrast ratios.

Through the development of uihue.com, we've seen how understanding color spaces and implementing smart color-matching algorithms can bridge the gap between technical color theory and practical web development needs. The complexity behind Tailwind's carefully crafted color palettes reveals that even seemingly simple color choices involve intricate patterns and thoughtful design decisions.

References


This content originally appeared on DEV Community and was authored by Matteo Frana


Print Share Comment Cite Upload Translate Updates
APA

Matteo Frana | Sciencx (2025-02-20T19:07:56+00:00) The Mystery of Tailwind Colors (v4). Retrieved from https://www.scien.cx/2025/02/20/the-mystery-of-tailwind-colors-v4/

MLA
" » The Mystery of Tailwind Colors (v4)." Matteo Frana | Sciencx - Thursday February 20, 2025, https://www.scien.cx/2025/02/20/the-mystery-of-tailwind-colors-v4/
HARVARD
Matteo Frana | Sciencx Thursday February 20, 2025 » The Mystery of Tailwind Colors (v4)., viewed ,<https://www.scien.cx/2025/02/20/the-mystery-of-tailwind-colors-v4/>
VANCOUVER
Matteo Frana | Sciencx - » The Mystery of Tailwind Colors (v4). [Internet]. [Accessed ]. Available from: https://www.scien.cx/2025/02/20/the-mystery-of-tailwind-colors-v4/
CHICAGO
" » The Mystery of Tailwind Colors (v4)." Matteo Frana | Sciencx - Accessed . https://www.scien.cx/2025/02/20/the-mystery-of-tailwind-colors-v4/
IEEE
" » The Mystery of Tailwind Colors (v4)." Matteo Frana | Sciencx [Online]. Available: https://www.scien.cx/2025/02/20/the-mystery-of-tailwind-colors-v4/. [Accessed: ]
rf:citation
» The Mystery of Tailwind Colors (v4) | Matteo Frana | Sciencx | https://www.scien.cx/2025/02/20/the-mystery-of-tailwind-colors-v4/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.