Color Matching
Dark Color Algorithm
Color Theory
Light to Dark Conversion
Algorithm Design

An algorithm for selecting a dark color similar to a light color

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

If you want a dark color that still feels like the same color family as a given light color, the simplest useful rule is: preserve hue, reduce lightness, and adjust saturation only enough to keep the result visually alive. Doing this directly in RGB often gives muddy or shifted results, so a lightness-aware color space is a better place to work.

For many practical applications, HSL or HLS is sufficient. For more perceptual accuracy, OKLCH or LAB-style spaces are even better, but the core idea stays the same: similarity should come from preserving hue identity while moving the color into a darker brightness region.

Why Raw RGB Scaling Is Weak

A naive algorithm might multiply each RGB channel by 0.5. That certainly makes the color darker, but it often changes the perceived character of the color in a way that feels dull or dirty.

For example, halving a pale blue in RGB can make it feel grayish rather than like a properly dark blue variant. That is because RGB channels are device-oriented, not especially intuitive for controlled lightness edits.

A Simple HLS-Based Algorithm

Python's colorsys module is a convenient way to prototype a darker-similar-color algorithm:

python
1import colorsys
2
3
4def dark_variant(rgb):
5    r, g, b = [c / 255.0 for c in rgb]
6    h, l, s = colorsys.rgb_to_hls(r, g, b)
7
8    new_l = max(0.12, l * 0.45)
9    new_s = min(1.0, s * 1.05)
10
11    r2, g2, b2 = colorsys.hls_to_rgb(h, new_l, new_s)
12    return tuple(round(c * 255) for c in (r2, g2, b2))
13
14
15print(dark_variant((200, 220, 255)))

This preserves hue, lowers lightness aggressively, and nudges saturation slightly upward so the dark version does not become too flat.

Add Contrast Awareness

Similarity alone is not enough in UI work. The dark variant may also need to meet a contrast target against a background or foreground. That is why a production algorithm often loops until the color is both similar enough and contrast-safe.

Here is a simple relative-luminance contrast helper:

python
1def srgb_to_linear(c):
2    c = c / 255.0
3    return c / 12.92 if c <= 0.04045 else ((c + 0.055) / 1.055) ** 2.4
4
5
6def luminance(rgb):
7    r, g, b = [srgb_to_linear(c) for c in rgb]
8    return 0.2126 * r + 0.7152 * g + 0.0722 * b
9
10
11def contrast_ratio(a, b):
12    l1 = luminance(a)
13    l2 = luminance(b)
14    hi, lo = max(l1, l2), min(l1, l2)
15    return (hi + 0.05) / (lo + 0.05)

With that in place, you can keep darkening slightly until the result passes a required ratio against white or another UI color.

Preserve Hue, But Not Blindly

Hue preservation is the main similarity signal, but not every color should be treated identically. Very low-saturation colors such as pale grays do not have a strong hue identity to preserve. In those cases, the algorithm should mostly manage lightness and perhaps keep saturation low.

Likewise, very bright saturated yellows and cyans often need more careful handling because once darkened, they may lose their visual identity faster than blues or reds. A practical algorithm often uses different lightness scaling for different hue families if the design system is large enough to justify that complexity.

Use Perceptual Spaces When Precision Matters

HSL is easy and common, but it is not perfectly perceptual. If this algorithm drives a design system, a data-visualization palette, or accessibility-sensitive theme generation, using OKLCH or LAB-style spaces usually gives more human-consistent results. Those spaces are designed so that changes in lightness are closer to how people actually perceive brightness changes.

Still, even a simple HSL-based algorithm is often a large improvement over raw RGB darkening.

Common Pitfalls

One common mistake is scaling RGB channels directly and assuming the result will feel like the same color in dark form. Another is darkening the color without checking contrast against the intended background. Designers and developers also often preserve hue but forget that dark colors can desaturate perceptually, so a slight saturation compensation may be needed. Finally, not every color needs the same lightness multiplier. Neutral colors, vivid yellows, and already-dark inputs often need different handling to stay visually coherent.

Summary

  • A good dark-similar-color algorithm preserves hue and reduces lightness in a lightness-aware color space.
  • HSL or HLS is a simple starting point, while perceptual spaces such as OKLCH are better for high-precision work.
  • Slight saturation adjustment can keep the dark result from looking muddy.
  • Contrast checking should be part of the algorithm if the color will be used in UI.
  • Raw RGB darkening is easy, but it usually produces weaker visual similarity than a hue-preserving lightness transform.

Course illustration
Course illustration

All Rights Reserved.