Getting Started with CM-Colors

Making your beautiful colors readable for everyone (without ruining your vibe)

Install It

pip install cm-colors

That’s it. Seriously.

The One Function You Actually Need

Skip to this if you just want your colors to work for everyone

from cm_colors import CMColors

cm = CMColors()

# Your original colors (these might be hard to read)
text_color = (100, 100, 100)  # Some gray text
background = (255, 255, 255)  # White background

# ✨ The magic happens here ✨
fixed_text, is_accessible = cm.tune_colors(text_color, background)

print(f"Your original text color: {text_color}")
print(f"New readable text color: {fixed_text}")
print(f"Is it accessible now? {is_accessible}")

Want more details?

# Get the full report
result = cm.tune_colors(text_color, background, details=True)

print(f"Fixed color: {result['tuned_text']}")
print(f"WCAG level: {result['wcag_level']}")
print(f"Status: {result['message']}")
print(f"Improvement: {result['improvement_percentage']:.1f}%")

What just happened?

  • We took your gray text on white background

  • Made the tiniest possible adjustment so people can actually read it

  • You probably can’t even tell the difference visually

  • But now it passes accessibility standards

That’s literally it. Use fixed_text in your CSS and you’re done.

“But Wait, What’s This Accessibility Thing?”

Quick explainer for the curious

The Problem: Some color combinations are gorgeous but impossible to read for people with visual differences, older screens, or even just bright sunlight.

The Solution: There are official rules (called WCAG) that say “your text needs THIS much contrast against the background so humans can read it.”

The Levels:

  • FAIL = People literally can’t read your text 😢

  • AA = Most people can read it comfortably ✅ (This is what you want)

  • AAA = Everyone can read it easily, even in tough conditions ⭐

Our library gets you to AA (or better) with the smallest possible color change.

Other Handy Functions

For when you want to dig deeper

“Are My Colors Already Good?”

# Check if your colors pass the readability test
text = (80, 80, 80)
background = (255, 255, 255)

# Get a simple pass/fail
level = cm.wcag_level(text, background)
print(f"Your colors are: {level}")  # "AA", "AAA", or "FAIL"

# Get the actual contrast number (higher = more readable)
contrast = cm.contrast_ratio(text, background)
print(f"Contrast score: {contrast:.1f}")
# 4.5+ = Good, 7+ = Excellent, under 4.5 = Needs fixing

“How Different Do These Colors Look?”

For the perfectionists who want to know exactly how much we changed

original = (100, 100, 100)
adjusted = (85, 85, 85)

# This measures how different colors look to human eyes
# Under 2.0 = You probably can't tell the difference
difference = cm.delta_e(original, adjusted)
print(f"Visual difference: {difference:.1f}")

“Working with Different Color Formats”

Because not everyone uses RGB tuples

# Parse different color formats
hex_color = "#7B2DC8"  # Purple in hex
rgb_tuple = cm.parse_to_rgb(hex_color)
print(f"Hex {hex_color} as RGB: {rgb_tuple}")

# You can also pass hex or rgb strings directly to tune_colors
fixed, accessible = cm.tune_colors("#7B2DC8", "#FFFFFF")
print(f"Fixed hex color: {fixed}")

“Large Text Gets Special Treatment”

18pt+ text or 14pt+ bold text has relaxed requirements

# Normal text needs higher contrast
normal_fixed, _ = cm.tune_colors((150, 150, 150), (255, 255, 255))

# Large text can get away with less contrast
large_fixed, _ = cm.tune_colors((150, 150, 150), (255, 255, 255), large_text=True)

print(f"Normal text needs: {normal_fixed}")
print(f"Large text needs: {large_fixed}")

Color Science Stuff

Advanced features for color nerds

# Convert between different color systems
rgb_color = (255, 128, 64)  # Orange-ish

# Convert to OKLCH (a fancy color system that matches human vision better)
l, c, h = cm.rgb_to_oklch(rgb_color)
print(f"In human-vision color space: Lightness={l:.2f}, Colorfulness={c:.2f}, Hue={h:.0f}°")

# Convert back to RGB
back_to_rgb = cm.oklch_to_rgb((l, c, h))
print(f"Back to RGB: {back_to_rgb}")

# Or convert to LAB color space (used in professional color matching)
l_star, a_star, b_star = cm.rgb_to_lab(rgb_color)
print(f"In LAB space: L*={l_star:.1f}, a*={a_star:.1f}, b*={b_star:.1f}")

Real-World Examples

“I’m Building a Website”

def fix_my_website_colors(text_color, bg_color):
    """
    Takes your website colors and makes them readable.
    Returns CSS-ready colors.
    """
    cm = CMColors()

    # Get detailed info about the fix
    result = cm.tune_colors(text_color, bg_color, details=True)

    # Convert to CSS format if needed
    fixed_text = result['tuned_text']
    if isinstance(fixed_text, tuple):
        css_text = f"rgb({fixed_text[0]}, {fixed_text[1]}, {fixed_text[2]})"
    else:
        css_text = fixed_text  # Already a string

    return {
        'text_color': css_text,
        'background_color': bg_color,
        'wcag_level': result['wcag_level'],
        'is_accessible': result['status'],
        'improvement': f"{result['improvement_percentage']:.1f}%"
    }

# Use it
colors = fix_my_website_colors((120, 80, 200), (255, 255, 255))
print(f"CSS: color: {colors['text_color']};")
print(f"Accessibility: {colors['wcag_level']} ({colors['improvement']} better)")

“I Need to Check a Bunch of Colors”

# Your brand color palette
brand_colors = [
    ("Purple text", (120, 80, 200)),
    ("Gray text", (100, 100, 100)),
    ("Dark blue", (30, 50, 100))
]

white_bg = (255, 255, 255)

print("Color Accessibility Report:")
print("-" * 40)

for name, color in brand_colors:
    level = cm.wcag_level(color, white_bg)
    contrast = cm.contrast_ratio(color, white_bg)

    status = "✅ Good" if level in ["AA", "AAA"] else "❌ Needs fixing"
    print(f"{name}: {status} (Level: {level}, Contrast: {contrast:.1f})")

    if level == "FAIL":
        fixed, _ = cm.tune_colors(color, white_bg)
        print(f"  → Suggested fix: {fixed}")

“Batch Processing Colors”

# Process multiple color pairs at once
color_pairs = [
    ((120, 80, 200), (255, 255, 255)),  # Purple on white
    ((100, 100, 100), (240, 240, 240)), # Gray on light gray
    ((200, 50, 50), (255, 255, 255))    # Red on white
]

print("Batch processing results:")
for i, (text, bg) in enumerate(color_pairs, 1):
    fixed, accessible = cm.tune_colors(text, bg)
    print(f"Pair {i}: {text}{fixed} (accessible: {accessible})")

Why This Matters

  • Legal stuff: Many places require accessible websites by law

  • Human stuff: 1 in 12 people have vision differences that make bad contrast painful

  • Practical stuff: Your content is useless if people can’t read it

  • Professional stuff: Shows you actually know what you’re doing

Questions?

Q: Will this ruin my carefully chosen colors?

A: Nope! We make the tiniest possible changes. The math ensures you won’t notice, but screen readers will.

Q: What if my colors are already perfect?

A: We’ll tell you they’re great and leave them alone.

Q: I picked terrible colors, can you help?

A: We’ll try our best! But if you chose neon yellow on white… pick better starting colors first 😅

Q: Do I need to understand color science?

A: Not at all! That’s exactly why this library exists.

Q: What’s the difference between details=True and details=False?

A: details=False (default) gives you just the fixed color and a yes/no on accessibility. details=True gives you the full report with WCAG levels, improvement percentages, and helpful messages.


Making the web readable for everyone, one tiny color tweak at a time 🌈✨