About OKLCH
8/10/2025
Traditional RGB & HSL Color Spaces: Fundamentals and Limitations
The Basic Principles of RGB and HSL
The RGB color model is an additive model based on the mixing of three primary colors of light (Red, Green, Blue), and it is fundamental to how screens and other luminous devices display color. In CSS, RGB colors are often represented using the rgb(r, g, b)
function or hexadecimal codes (e.g., #RRGGBB
), where the values for r, g, and b typically range from 0 to 255. Although RGB is efficient and direct for computer hardware, its representation poses challenges for human intuition and color manipulation. It is difficult for designers and developers to accurately predict changes in a color's brightness, saturation, or hue simply by adjusting its R, G, and B values. More critically, RGB is not perceptually uniform. This means that the same numerical change in RGB values can result in vastly different perceived color differences depending on the region of the color space. For instance, a small change in RGB might be imperceptible in dark areas but very noticeable in bright areas. Furthermore, traditional RGB is usually limited to the sRGB gamut (a legacy of technologies like early CRT monitors), which cannot fully represent the rich spectrum of colors that modern wide-gamut displays can show.
To compensate for RGB's lack of intuitiveness, the HSL (Hue, Saturation, Lightness) color space was developed. It attempts to organize colors in a way that is more aligned with how humans describe them. HSL can be considered a cylindrical coordinate transformation of the RGB color space:
- H (Hue): Represents the basic attribute of a color, such as red, yellow, or blue, typically expressed as an angle from 0 to 360 degrees.
- S (Saturation): Represents the purity or intensity of a color, usually as a percentage (0% - 100%). 0% is gray, and 100% is the purest color.
- L (Lightness): Represents the brightness of a color, also typically as a percentage (0% - 100%). 0% is black, 100% is white, and 50% is considered the baseline lightness for the most saturated (purest) state of a given hue.
In CSS, HSL colors are represented by the hsl(h, s, l)
function. Compared to RGB, HSL does offer greater intuitiveness in certain scenarios, such as adjusting the saturation and lightness of a specific hue. However, HSL has a core, glaring flaw: the perceptual non-uniformity of its "L" (Lightness) component. Despite its name, HSL's L value does not accurately reflect perceived brightness as seen by the human eye. Its lightness (L) is typically calculated with a simple mathematical formula based on RGB components, such as L = (max(R,G,B) + min(R,G,B)) / 2
. This calculation fails to adequately account for the human eye's differing sensitivity to various colored lights (for example, yellow has an inherently high perceived brightness, while blue is perceived as darker). Consequently, colors with different hues can have vastly different visual brightness levels even if they share the exact same HSL lightness value (L). A classic example is that pure yellow (e.g., hsl(60, 100%, 50%)
) appears much brighter visually than pure blue (e.g., hsl(240, 100%, 50%)
), even though both have an L value of 50%.
This perceptual inconsistency creates numerous problems in practical design work. When a designer tries to use HSL's L value to standardize the visual brightness of different colors or to generate a color series (like tints or shades), the results often don't match expectations. For example, setting the same L value for a blue button and a yellow button will make them appear visually unbalanced in weight. Similarly, applying the same L value adjustment (e.g., increasing L by 10%) to colors of different hues can produce wildly different changes in perceived brightness. This makes it exceptionally difficult to create a consistent visual hierarchy, ensure sufficient color contrast to meet accessibility requirements, and generate smooth, harmonious color derivatives.
The Common Challenges of Traditional Color Spaces
Whether it's RGB, HSL, or HSV (Hue, Saturation, Value), they all face challenges in color adjustment and palette generation stemming from their perceptual non-uniformity. Designers find it difficult to generate visually harmonious palettes with smooth lightness transitions using simple mathematical operations or parameter adjustments. For example, in HSL, if you fix the L and S values and only change H to generate a series of colors, there will be noticeable jumps in their perceived brightness. More seriously, this perceptual non-uniformity poses a barrier to the accessibility of digital products. Ensuring adequate contrast between text and background, and between UI elements, is crucial for users with visual impairments. However, if a color space's lightness parameter doesn't truly reflect perceived brightness, any contrast evaluation based on it is unreliable.
HSL's L value, in particular, is often misleading for judging contrast due to its disconnect from perceived lightness. The "intuitiveness" of HSL is, to some extent, a "false convenience." It was initially created to solve the problem of RGB not being intuitive, introducing concepts familiar to humans like hue, saturation, and lightness. Designers first encountering HSL might find it easier to understand and manipulate colors than with RGB. However, this "ease of use" comes at the cost of perceptual accuracy. In modern design practices, where accessibility and visual consistency are increasingly important, this fundamental flaw makes HSL ill-suited for the job.
Perceptually Uniform Color Spaces: Color Models More Aligned with Human Vision
So, what is a Perceptually Uniform Color Space? In simple terms, it's a special type of color model designed with a core objective: to make the numerical distance between colors (e.g., the Euclidean distance between two points in the space) as directly proportional as possible to the perceived difference in color by the human eye. In such a color space, making an equal adjustment to a color parameter should result in a similar perceived visual change, regardless of the starting color. Its core principle is to simulate how the human visual system processes color information, striving to keep the results of mathematical operations within the color space consistent with real visual perception changes. This stands in stark contrast to traditional color spaces like RGB or HSL.
The importance of perceptual uniformity is evident in several areas:
- Predictability: When you adjust a color, the visual result is more in line with your expectations.
- Consistency: It helps maintain visual harmony and smooth transitions when generating color palettes and gradients. For example, a gradient generated by interpolating between two colors in a perceptually uniform space typically looks more natural than one generated in the sRGB space (which often suffers from a "gray dead zone" or unexpected hue shifts due to direct linear interpolation of RGB values).
- Design Applications: Designers can more easily create visually balanced color palettes and precisely control the visual hierarchy in an interface.
- Accessibility: This is one of the most significant advantages of perceptually uniform color spaces. Their lightness parameter more accurately reflects perceived brightness, providing a reliable foundation for evaluating and ensuring color contrast. For instance, when Stripe built its accessible color system, it utilized the perceptual uniformity of the CIELAB color space, adjusting the L* value of colors to ensure different color combinations achieved a consistent perceived contrast, meeting WCAG requirements.
CIELAB (Lab) and LCh are two well-known examples of perceptually uniform color spaces, primarily used in print and industrial color quality control. CIELAB was defined by the International Commission on Illumination (CIE) and includes three components: L* (perceived lightness), a* (red/green axis), and b* (yellow/blue axis). LCh is its cylindrical coordinate representation, comprising L* (perceived lightness), C* (chroma), and h (hue angle).
Perceptual uniformity isn't just a technical improvement; it fundamentally changes how designers interact with color. With traditional color spaces, designers often rely on extensive experience and repetitive visual tweaking. In contrast, perceptually uniform color spaces (especially with their accurate lightness parameters) make it possible to design color systems based on mathematical rules and algorithms. This is crucial for creating and maintaining complex, modern design systems that support multiple themes, light/dark modes, and strict accessibility standards.
OKLCH: A Next-Generation Color Solution
What is OKLCH?
In the ongoing quest to optimize color expression and control, the OKLCH color space has emerged, marking a significant step towards more accurately simulating human visual perception.
Oklab and its polar coordinate form, OKLCH, are new perceptual color spaces proposed by Björn Ottosson in 2020. The primary motivation for their creation was to address the shortcomings in perceptual uniformity found in existing color spaces, including the widely used CIELAB, which still suffers from issues in certain hue ranges, like the blue region. Ottosson's goal was to create a color space that is not only more perceptually uniform but also computationally simpler and more stable in its performance, making it particularly suitable for web and digital display applications. For specific technical details, you can refer to his article: https://bottosson.github.io/posts/oklab/
Compared to CIELAB/LCH, Oklab/OKLCH demonstrates superior performance across several key perceptual dimensions, with significant improvements in hue linearity and uniformity. This means, for example, that when you decrease the chroma of a blue in Oklab/OKLCH, it will smoothly approach gray without an unexpected shift towards purple or green, as can happen in some areas of CIELAB. At the same time, the perceived differences between various hues on the color wheel are more balanced, making color selection and transitions more natural and harmonious.
Understanding the three core components of OKLCH is crucial for using it effectively:
- L (Lightness): Ranges from 0 (black) to 1 (white). Critically, the "perceptual" aspect here means that changes in the L value correspond closely to how the human eye actually perceives changes in brightness.
- C (Chroma): Represents the "vividness" or "intensity" of a color. It starts from 0 (gray), and the higher the value, the more vivid the color. For the sRGB gamut, the practical upper limit for C is around 0.37, while for the wider Display P3 gamut, it can reach about 0.5. In the CSS
oklch()
function, if C is expressed as a percentage, 0% corresponds to 0 and 100% corresponds to 0.4. - H (Hue): Represents the basic tone of the color, as an angle from 0 to 360 degrees.
Additionally, an Alpha (transparency) component is usually included.
Why Choose OKLCH?
OKLCH is considered a strong contender for the next generation of color solutions primarily due to the following significant advantages:
- Superior Perceptual Uniformity: The L (perceived lightness) component ensures that numerical changes align closely with human perception. The W3C CSS Color Module Level 4 specification notes its superiority over CIE LCH in terms of hue linearity and uniformity.
- More Intuitive, Predictable Color Adjustment and Derivation: Its L, C, and H parameters are well-orthogonal, meaning that adjusting one parameter has minimal impact on the other perceived attributes. This is ideal for generating color palettes. For instance, a designer can create a monochromatic lightness scale by fixing H and C and only adjusting L, perfect for component states (like a button's hover, active, and disabled states). Alternatively, by fixing L and C and changing H, one can obtain a set of multi-colored options with consistent perceived lightness and chroma, which is excellent for ensuring visual balance between different series in data visualizations. Creating UI themes (like a smooth and visually consistent transition from light to dark mode) also becomes more systematic and controllable.
- Wide-Gamut Color Support: OKLCH can easily represent colors from wide gamuts like Display P3, fully leveraging the capabilities of modern displays.
- Enhanced Accessibility (a11y): A reliable L value is fundamental to ensuring visual contrast and meeting standards like WCAG.
- Human Readability:
oklch(0.7 0.15 200)
is more intuitive for conveying a color's general appearance (a relatively light, low-to-medium saturation blue) thanrgb(128, 177, 207)
or#80B1CF
.
Future Trends and Standardization Progress
The many advantages of OKLCH have made it an ideal candidate for color representation in CSS. It has been adopted by the W3C's CSS Color specification and became part of the "Baseline 2023 newly available" set of features, signaling its widespread availability in major browsers.
CSS officially introduced the oklch()
and oklab()
functions in Color Module Level 4. CSS Color Module Level 5 further enhances the utility of OKLCH with core features like relative color syntax and the color-mix()
function.
-
Relative Color Syntax: This allows for the convenient creation and modification of colors based on a "source color," such as
oklch(from var(--brand-blue) calc(l * 0.8) c h)
. It is extremely useful for creating color states and theme variations.:root { --color-btn: oklch(45% 0.3 266); } .btn { padding: 12px; background-color: var(--color-btn); border: none; cursor: pointer; color: white; } .btn:hover { background-color: oklch(from var(--color-btn) calc(l + 0.2) c h); }
-
color-mix()
Function: This allows for mixing colors in a specified color space. Mixing colors in the OKLCH space, for example,color-mix(in oklch, blue, yellow)
, produces an intermediate color that is more aligned with visual expectations. It effectively avoids the common issues seen when mixing in sRGB, such as colors becoming "muddy," losing saturation, or experiencing unexpected hue shifts (like blue and yellow mixing towards gray instead of green), resulting in a more vibrant and pure mixed color..container-1 { background-color: color-mix(in oklch, yellow, blue); width: 20rem; height: 2rem; } .container-2 { margin-top: 1rem; background-color: color-mix(in srgb, yellow, blue); width: 20rem; height: 2rem; }
Currently, an increasing number of projects and teams, such as Linear, Stripe, and Tailwind CSS, are beginning to adopt OKLCH to build accessible and predictable color systems, using it as the foundation for their design systems.
Current Challenges Facing OKLCH
Despite its promising future, there are still some aspects to be aware of during the promotion and application of OKLCH:
Gamut Mapping and Out-of-Gamut Color Handling
OKLCH can define colors that are outside the gamut of a specific display device. When these "out-of-gamut" colors need to be displayed in a narrower gamut (like sRGB), "gamut mapping" must be performed. The CSS Color Module Level 4 specification recommends performing gamut mapping within the OKLCH space. The benefit of this approach is that when a wide-gamut color needs to be compressed into the smaller gamut of a target display (like sRGB), OKLCH can better preserve the original color's perceptual characteristics, such as relative lightness and hue relationships. This avoids the hue shifts or loss of detail often caused by simple clipping methods, producing a more visually natural down-sampled color. However, the specific mapping algorithms and their effects may still have subtle differences between browsers and tools, requiring actual testing.
Browser Compatibility Issues
In the early stages of OKLCH's promotion and browser implementation, there were some compatibility and rendering accuracy issues. For example, colors with specific L values (L=1 or L=0 where C was not 0) were not displayed as pure white or black as expected, along with some other specific rendering bugs. As browser vendors continue to follow and fix issues according to the specification, these more prominent early compatibility problems have been largely resolved in current (as of May 2025) versions of major browsers. However, when dealing with extreme color values or performing complex gamut conversions, it is still necessary to pay attention and test their performance in different environments.
Popularity of Native Support in Design Tools
Although OKLCH has matured at the web standard level, the adoption by the design tool ecosystem is an ongoing process. Early on, native support in mainstream graphic design tools was limited, which created a bottleneck for its adoption. While major design tools can support the OKLCH color space through some tricks and plugins—for example, Figma can display OKLCH colors by changing the preferences to the P3 color space (the change takes effect in new files)—the level of seamless integration still has much room for improvement compared to traditional color models.
OKLCH in Action
After fully understanding the theoretical basis of OKLCH, hands-on practice will help us master its application more deeply. This section will walk you through building a practical color palette that includes a brand color, warning color, success color, and danger color (i.e., semantic colors) using the huetone tool. This process will help you intuitively feel the effectiveness of OKLCH in creating harmonious and predictable color systems. Besides huetone, here are some other representative tools that use OKLCH/Oklab or similar perceptually uniform principles for palette generation and color adjustment for your reference:
- https://huetone.ardov.me/
- https://uicolors.app/
- https://oklch.com/
- https://harmonizer-web.web.app/
- https://github.com/dokozero/okcolor
- https://m3.material.io/blog/material-theme-builder
Before You Start: Radical or Stable?
In the initial phase of a project, there are usually two strategic considerations for applying OKLCH:
- Direct Adoption Strategy: Use the CSS
oklch()
function directly in the production environment to take full advantage of all its features. For example, the latest Tailwind CSS 4.0 adopts this strategy. This approach maximizes the benefits of OKLCH but requires attention to handling compatibility for the very few older browser versions that do not support CSS Color Level 4 (as of 2025, major browsers have provided stable support). - Conversion Output Strategy: Use the perceptual uniformity of OKLCH during the palette construction phase to define colors, and then convert the selected colors to widely compatible RGB or hexadecimal (HEX) values for use in the production environment. This method is similar to how Babel transpiles newer versions of JavaScript into backward-compatible code, aiming to balance advancement with broad compatibility. However, when adopting this strategy, it is crucial to note that converting from a wide gamut like OKLCH to a relatively narrow one like sRGB may cause visual changes or loss of detail in highly saturated colors.
The demonstration in this section will follow the latter approach, focusing on how to use OKLCH for palette construction and discussing the process of exporting it to traditional formats.
Step 1: Brand Color Palette
The brand color is usually determined during the visual identity (VI) system design phase. Even so, it is recommended that the designer in charge of the design system gets involved in this process early to collaborate with graphic designers, ensuring that the brand color is well-suited for digital products, accessible, and scalable from the outset. In this example, we will use a common blue, #0052d9
, as our starting point.
After selecting the base color, the number of color steps needs to be determined. Typically, 10 to 12 steps (e.g., divided into 11 steps from lightest to darkest) can meet the needs of most scenarios, while neutral grays may require more steps for a finer transition.
For naming conventions, you can draw inspiration from mature design systems like Tailwind CSS, using a numerical sequence like 50, 100, 200, ..., 500, ..., 900, 950
. Compared to a simple 1-9
sequence, this method provides structural flexibility for inserting new colors between existing steps later (e.g., adding an 850
between 800
and 900
), enhancing the system's scalability. Note that we will not discuss design token primitive and semantic naming knowledge here.
The primary brand color (Base Color) is generally located in the middle of the palette (e.g., the 500
or 600
step) and serves as a benchmark for extending towards both lighter and darker ends to accommodate light and dark mode color requirements. Of course, the exact position of the main color is not fixed and should be flexibly adjusted based on its inherent Lightness, Chroma, and Hue characteristics.
Once these are determined, we open huetone, delete the other example colors, keep one row, and add two columns named 900 and 950. We will apply our brand color #0052d9
to the 500 step.
Then select "apply hue to the row," and the entire row will use the same Hue value. We then need to slowly adjust the lightness distribution, or we can use a linear or cubic function for quick sampling.
In practice, you might encounter a situation where the chosen initial brand color already has a very high APCA contrast ratio (e.g., against a white background). In this case, placing it at the middle 500
step might make it difficult to generate a sufficient number of visually distinct and contrast-compliant darker steps. For example, if #0052d9
already has an APCA contrast of 80 against white, placing it at the 800
step might be a more reasonable choice, making it a darker color in the palette that still represents the brand's characteristics. This adjustment means the main color is no longer in the center. If the goal is to create a palette that serves both light and dark modes well, a compromise may be needed in the range of available steps or the positioning of the main color. Alternatively, one could consider maintaining separate palette systems for light and dark modes (for instance, the order of steps in dark mode could be reversed from light mode), referencing the method used by Radix UI.
After moving the brand color to 800, we need to adjust the lightness and chroma values. For Chroma, I generally adopt a pattern where the base color has the highest value, which then slightly decreases towards both ends. This ensures the base color is the most saturated, while the other colors can serve as auxiliary roles.
Step 2: Applying to Other Colors
Once you are satisfied with the L and C distribution pattern of the main color palette (which can be seen as defined lightness and chroma curves), you can use Huetone's "Copy Last Row" feature to quickly create initial palettes for other colors (like Success, Warn, and Error). This copy operation will fully inherit the L and C values of all steps from the previous row.
After the new row is copied, the first task is to adjust the Hue (H) value of its main color step (e.g., the 800
step corresponding to the brand's main color) to define the new semantic color (for instance, choosing a green hue's H value for the success color). While adjusting H, it is often necessary to simultaneously fine-tune its Chroma (C). This is partly to ensure the color is within the target output gamut (like sRGB) to avoid clipping, and partly to maintain visual harmony and effective information delivery. The perceptual uniformity of OKLCH once again demonstrates its advantage here: when changing H, the designer can better predict the visual effects of L and C and help maintain the relative stability of perceived lightness.
Once the new hue and chroma are set, use the "Apply current hue to row" feature again to quickly generate the complete color scale for that semantic color. The process for creating other semantic palettes (like warning and error colors) is similar.
Excellent! We now have the beginnings of a color palette!
It is worth emphasizing that although many tools and algorithms can automatically generate a color palette from a single input color, and OKLCH itself performs excellently in terms of perceptual uniformity, achieving the ideal color design effect often requires the designer's visual judgment and meticulous manual adjustments. Tools are efficient assistants, not substitutes for final aesthetic judgment.
Step 3: Iterate, Test, and Export
After the initial palette is generated, iteration and optimization are key to ensuring its final usability. This includes rigorously testing the accessibility of various colors in the actual user interface (e.g., checking text and background contrast using APCA or WCAG standards), evaluating their visual effect in different combinations and lighting environments, and ensuring the overall harmony and unity of the palette.
This stage typically requires close communication and collaboration among members of the design, product, and development teams to gather and integrate feedback from all sides, ensuring the color system both meets specific business needs and can be implemented soundly from a technical perspective. Continuous communication and fine-tuning are essential steps in creating a successful color system.
When all colors and their steps are finalized, they can be exported as CSS custom properties (variables), JSON data, or in formats required by specific design tools for unified application in the project.
/* Example Palette copy color palette */
/* brand */
--brand-50: #e9eff9;
--brand-100: #e0eafb;
--brand-200: #c9daf8;
--brand-300: #acc8f9;
--brand-400: #8bb3f9;
--brand-500: #6fa0f8;
--brand-600: #4d86f0;
--brand-700: #3271e7;
--brand-800: #0052d9;
--brand-900: #0c41a2;
--brand-950: #0d2e6c;
/* success */
--success-50: #e9f1e8;
--success-100: #e0eede;
--success-200: #c9e1c6;
--success-300: #b2d1ad;
--success-400: #96c090;
--success-500: #7fb079;
--success-600: #639b5b;
--success-700: #488b3f;
--success-800: #24741a;
--success-900: #1b5913;
--success-950: #123d0d;
/* warn */
--warn-50: #f6ede4;
--warn-100: #f5e7d8;
--warn-200: #eed4ba;
--warn-300: #e9be93;
--warn-400: #e0a567;
--warn-500: #d78f3d;
--warn-600: #be781f;
--warn-700: #a6691f;
--warn-800: #88530a;
--warn-900: #674012;
--warn-950: #472c0c;
/* error */
--error-50: #f8ebe8;
--error-100: #fae4df;
--error-200: #f6cfc7;
--error-300: #f4b6a9;
--error-400: #e99c8d;
--error-500: #e28471;
--error-600: #d36450;
--error-700: #c74934;
--error-800: #ae270f;
--error-900: #871c0a;
--error-950: #601004;
Related Reading
- 色彩空间与色域知识梳理及设计应用 (Sorting Out Knowledge of Color Spaces and Gamuts and Their Design Applications)
- Stripe: Accessible color systems
- Matthew Strom: Generating Color Palettes
- Evil Martians: OKLCH in CSS: why we moved from RGB and HSL
- Björn Ottosson: A perceptual color space for image processing (Oklab)
In summary, the OKLCH color space, with its excellent perceptual uniformity, strong support for wide gamuts, and immense potential for enhancing the accessibility of digital products, is becoming a major innovation in the field of modern color design and development. It not only provides designers and developers with a more scientific and intuitive color tool but also opens up new paths for creating digital products that are more visually harmonious, experientially consistent, and friendly to all users. Although there is still room for continuous optimization in terms of perfect toolchain integration and handling gamut in extreme cases, OKLCH undoubtedly represents the bright future of web color and is worthy of our continued attention, learning, and practice.