🐍 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
Contents
1. Accepted color formats
| Format | Example | Has alpha? |
|---|---|---|
| #RGB (shorthand) | #F0A | No — expands to #FF00AA |
| #RRGGBB | #FF0057 | No |
| #RRGGBBAA | #FF005780 | Yes — 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 named | cornflowerblue | No |
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
| Input | Valid? | Note |
|---|---|---|
| #FFF | ✓ | Shorthand → #FFFFFF |
| #FFFFF | ✗ | 5-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 |
| transparent | ✓ | CSS keyword — hasAlpha: True |
| currentColor | ✗ | CSS variable — not a static value |
| #00000000 | ✓ | Fully 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