🐍 PythonDesign / UI

Color Validation in Python

Validate color values in Python — accept HEX, RGB, HSL, and CSS named colors from any source, normalise to a canonical hex form for storage, and validate entire brand palettes with asyncio.gather.

Also available in Node.js

1. Accepted color formats

FormatExampleHas alpha?
#RGB (shorthand)#F0ANo — expands to #FF00AA
#RRGGBB#FF0057No
#RRGGBBAA#FF005780Yes — alpha 50%
rgb()rgb(255, 0, 87)No
rgba()rgba(255, 0, 87, 0.5)Yes
hsl()hsl(210, 100%, 50%)No
hsla()hsla(210, 100%, 50%, 0.8)Yes
CSS namedcornflowerblueNo

2. API response structure

Endpoint: GET /v0/color?value=…

{
  "valid": true,
  "format": "hsl",
  "hex": "#1A8CFF",
  "rgb": { "r": 26, "g": 140, "b": 255 },
  "hsl": { "h": 210, "s": 100, "l": 56 },
  "hasAlpha": false,
  "name": null
}

hex is always #RRGGBB uppercase — use it as the canonical storage form regardless of the input format.


3. Normalising to hex

async def normalise_color(value: str) -> str:
    """Accept any supported format, return canonical uppercase hex."""
    result = await iv.color(value)
    if not result.valid:
        raise ValueError(f"Invalid color value: {value!r}")
    return result.hex  # Always "#RRGGBB"

# All of these return the same hex
await normalise_color("#0057FF")          # "#0057FF"
await normalise_color("rgb(0, 87, 255)")  # "#0057FF"
await normalise_color("hsl(218, 100%, 50%)")  # "#0057FF"
await normalise_color("#05F")             # "#0055FF" (shorthand expanded)

4. Handling alpha (transparency)

ℹ️Many storage formats (e.g., CSS custom properties, design tokens) distinguish between opaque and transparent colors. Use has_alpha to enforce opaque-only rules in brand guidelines or database columns that expect #RRGGBB.
async def validate_opaque_color(value: str) -> str:
    result = await iv.color(value)
    if not result.valid:
        raise ValueError(f"Invalid color: {value!r}")
    if result.has_alpha:
        raise ValueError(
            f"Transparent colors are not allowed. "
            f"Got {value!r} — use an opaque color (#RRGGBB, rgb(), hsl())."
        )
    return result.hex

5. Brand palette validation

BRAND_PALETTE = {
    "primary":    "#0057FF",
    "secondary":  "#1A1A2E",
    "accent":     "rgb(255, 87, 34)",
    "background": "hsl(0, 0%, 100%)",
    "surface":    "rgba(255,255,255,0.9)",  # alpha — will be flagged
}

async def validate_brand_palette(palette: dict[str, str]) -> dict:
    tasks = {name: iv.color(value) for name, value in palette.items()}
    results = await asyncio.gather(*tasks.values(), return_exceptions=True)

    valid = {}
    errors = []
    for (name, raw), result in zip(palette.items(), results):
        if isinstance(result, Exception):
            errors.append({"name": name, "error": str(result)})
        elif not result.valid:
            errors.append({"name": name, "error": "invalid", "value": raw})
        elif result.has_alpha:
            errors.append({"name": name, "error": "alpha_not_allowed"})
        else:
            valid[name] = result.hex

    return {"valid": valid, "errors": errors}

6. Edge cases

InputValid?Note
#FFFShorthand → #FFFFFF
#FFFFF5-digit hex — invalid
rgb(256, 0, 0)R > 255 — out of range
hsl(360, 100%, 50%)360° = 0° = red
hsl(400, 100%, 50%)H > 360 — invalid
transparentCSS keyword — hasAlpha: True
currentColorCSS variable — not a static value
#00000000Fully transparent black — hasAlpha: True

7. Full example with asyncio

import asyncio
import os
from isvalid_sdk import IsValidConfig, create_client

iv = create_client(IsValidConfig(api_key=os.environ["ISVALID_API_KEY"]))

async def normalise_color(value: str) -> str:
    """Accept any color format, return canonical uppercase hex."""
    result = await iv.color(value)
    if not result.valid:
        raise ValueError(f"Invalid color: {value!r}")
    return result.hex  # e.g. "#1A2B3C"

# Validate a brand palette — all colors must parse, no alpha allowed
BRAND_PALETTE = {
    "primary": "#0057FF",
    "secondary": "#1A1A2E",
    "accent": "rgb(255, 87, 34)",
    "background": "hsl(0, 0%, 100%)",
}

async def validate_brand_palette(palette: dict[str, str]) -> dict:
    tasks = {name: iv.color(value) for name, value in palette.items()}
    results = await asyncio.gather(*tasks.values(), return_exceptions=True)

    validated = {}
    errors = []
    for (name, raw), result in zip(palette.items(), results):
        if isinstance(result, Exception):
            errors.append({"name": name, "error": str(result)})
        elif not result.valid:
            errors.append({"name": name, "error": "invalid_color", "value": raw})
        elif result.has_alpha:
            errors.append({"name": name, "error": "alpha_not_allowed", "value": raw})
        else:
            validated[name] = result.hex

    return {"valid": validated, "errors": errors}

async def main():
    hex_color = await normalise_color("rgb(0, 87, 255)")
    print(hex_color)  # "#0057FF"

    report = await validate_brand_palette(BRAND_PALETTE)
    print(report["valid"])   # {'primary': '#0057FF', ...}
    print(report["errors"])  # []

asyncio.run(main())

8. Summary checklist

Accept HEX, RGB, HSL, and CSS named color formats
Store result.hex as canonical form (#RRGGBB uppercase)
Check has_alpha before storing in opaque-only fields
Validate entire brand palettes with asyncio.gather
Expand shorthand #RGB to full #RRGGBB before storing
Validate HSL range: H 0–360, S/L 0–100%
Reject CSS variables (currentColor) — not static values
Return 422 with reason on invalid color input

See also

Ready to integrate?

Free tier — 1,000 requests/month. No credit card required.

Get your API key →