How to Create Custom Color Blends Using Color and ColorTriplet Classes in Rich
To create custom color blends in Rich, parse colors into Color objects, extract their ColorTriplet RGB values, use blend_rgb() to interpolate between them, and wrap the result back into a Color instance for styling.
The Textualize/Rich library provides a sophisticated terminal color system that supports creating smooth gradients and custom color blends. Whether you're building progress bars with dynamic colors or implementing color transitions in CLI applications, understanding how to manipulate the Color and ColorTriplet classes unlocks precise control over RGB interpolation. This guide demonstrates how to leverage the blending utilities implemented in rich/color.py to create seamless color transitions.
Understanding Rich's Color Architecture
Rich implements a layered approach to color management with three distinct components working together. The Color class serves as the terminal-aware abstraction that handles color names, terminal color systems (standard, 8-bit, truecolor, or Windows), and ANSI code generation. It encapsulates an optional ColorTriplet when dealing with truecolor values. The ColorTriplet class, defined in rich/color_triplet.py, provides an immutable RGB tuple with helper methods for hex conversion, CSS-style formatting, and normalized value access. Finally, the blend_rgb utility function in rich/color.py performs the actual linear interpolation between two triplets.
When you parse a color string such as "#ff8800" using Color.parse(), the library creates a Color instance containing a ColorTriplet representing the RGB values. This architecture separates the terminal rendering concerns from the mathematical color operations, allowing you to blend colors mathematically while maintaining automatic downgrade capabilities for limited terminals.
The Color Blending Workflow
Creating custom color blends follows a predictable four-step process. First, parse input strings into Color objects using the parse() method. Second, extract the underlying RGB data via get_truecolor(), which returns a ColorTriplet with red, green, and blue components as integers (0-255). Third, pass these triplets to blend_rgb() along with a cross-fade factor between 0.0 and 1.0 to generate an interpolated result. Fourth, convert the blended triplet back into a Color object using Color.from_triplet() for use in Rich styles and text objects.
This workflow ensures that color mathematics occurs at the RGB triplet level while the final Color wrapper maintains Rich's terminal-aware capabilities, including automatic conversion to 8-bit or standard colors when necessary.
Practical Implementation Examples
Blend Two Hex Colors for Styled Text
The following example demonstrates blending red and blue to create a purple intermediate, using the exact methods found in rich/color.py:
from rich.console import Console
from rich.text import Text
from rich.color import Color, blend_rgb
# Parse the input colours (hex strings)
red = Color.parse("#ff0000") # pure red
blue = Color.parse("#0000ff") # pure blue
# Get their true‑color triplets
red_triplet = red.get_truecolor()
blue_triplet = blue.get_truecolor()
# Blend 30 % red → 70 % blue
purple_triplet = blend_rgb(red_triplet, blue_triplet, cross_fade=0.3)
# Wrap the blended triplet back into a Color object
purple = Color.from_triplet(purple_triplet)
# Use the colour in a Rich Text object
text = Text("Custom purple text", style=f"color({purple_triplet.hex})")
Console().print(text)
The Color.parse method handles the initial conversion at lines 31-48 of rich/color.py, while get_truecolor (lines 49-78) ensures you receive a ColorTriplet regardless of the original color system. The blend_rgb function at lines 80-91 performs the linear interpolation.
Dynamic Runtime Color Transitions
For applications requiring animated or data-driven color shifts, you can wrap the blending logic in a function that accepts runtime parameters:
import random
from rich.console import Console
from rich.style import Style
from rich.color import Color, blend_rgb
from rich.color_triplet import ColorTriplet
def random_color():
return Color.from_triplet(
ColorTriplet(random.randint(0,255), random.randint(0,255), random.randint(0,255))
)
def blended_style(c1: Color, c2: Color, factor: float) -> Style:
triplet = blend_rgb(c1.get_truecolor(), c2.get_truecolor(), factor)
return Style(color=triplet.hex) # hex works directly in Style
console = Console()
c_a = random_color()
c_b = random_color()
for i in range(5):
factor = i / 4 # 0.0 → 1.0
style = blended_style(c_a, c_b, factor)
console.print(f"Blend {i}", style=style)
The ColorTriplet class definition resides in rich/color_triplet.py at lines 4-13, providing the immutable container that blend_rgb expects.
Handling Terminal Color Limitations with Downgrade
Rich's color system automatically adapts to terminal capabilities. When you need to explicitly control this behavior or support legacy terminals, use the downgrade() method demonstrated below:
from rich.console import Console
from rich.color import Color, blend_rgb, ColorSystem
# Blend two colours
c1 = Color.parse("#ff8800")
c2 = Color.parse("#00ff88")
blended = Color.from_triplet(blend_rgb(c1.get_truecolor(),
c2.get_truecolor(),
cross_fade=0.5))
# Simulate a limited terminal (8‑bit only)
downgraded = blended.downgrade(ColorSystem.EIGHT_BIT)
console = Console()
console.print("Original (truecolor)", style=f"color({blended.triplet.hex})")
console.print("Downgraded (8‑bit)", style=f"color({downgraded.triplet.hex})")
The downgrade method implementation at lines 112-155 of rich/color.py maps truecolor values to the closest available color in the target color system, ensuring your blends remain visible even on restricted terminals.
Summary
- Parse input strings using
Color.parse()to create terminal-aware color objects from hex codes, RGB values, or color names. - Extract RGB data with
get_truecolor()to obtain immutableColorTripletinstances suitable for mathematical operations. - Interpolate colors by calling
blend_rgb()with two triplets and a cross-fade factor between 0.0 and 1.0 to generate intermediate shades. - Reconstruct Color objects using
Color.from_triplet()to apply blended results to RichStyleandTextcomponents. - Maintain compatibility by leveraging automatic or explicit
downgrade()to adapt blended colors for terminals with limited color support.
Frequently Asked Questions
What is the difference between Color and ColorTriplet in Rich?
Color is a terminal-aware abstraction defined in rich/color.py that tracks the color system (standard, 8-bit, truecolor, or Windows), color names, and ANSI escape sequences. ColorTriplet, defined in rich/color_triplet.py, is a simple immutable tuple of three integers (0-255) representing red, green, and blue values. While Color handles rendering concerns, ColorTriplet provides the raw RGB data needed for mathematical operations like blending.
How does the blend_rgb function calculate the interpolated color?
The blend_rgb function performs linear interpolation between two ColorTriplet instances. It accepts a cross_fade parameter between 0.0 and 1.0, where 0.0 returns the first color and 1.0 returns the second. For each RGB component, it calculates the weighted average: color1 + (color2 - color1) * cross_fade. This implementation appears at lines 80-91 in rich/color.py.
Can blended colors be used with Rich's Style and Text classes?
Yes. After blending ColorTriplet instances and converting back to a Color object via Color.from_triplet(), you can apply the result anywhere Rich expects color specifications. Pass the Color instance to Style(color=...) or use the hex representation (accessible via .triplet.hex) in style strings for Text objects.
How do I handle terminals that don't support truecolor?
Rich handles this automatically through the downgrade() method found at lines 112-155 of rich/color.py. You can explicitly convert a blended color to 8-bit or standard colors by calling color.downgrade(ColorSystem.EIGHT_BIT) or color.downgrade(ColorSystem.STANDARD). This maps the RGB values to the closest available color in the target palette, ensuring your blends display correctly on older terminals.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →