Skip to content

Color Contrast Checker — PHP source

Check WCAG 2.2 contrast ratio between any two colors with AA / AAA pass-fail for normal and large text, plus a live preview. For accessible, on-brand design.

This is the PHP implementation — the same logic the interactive tool runs, in a shareable, citable form.

<?php
/**
 * Color contrast — WCAG 2.2 color contrast math.
 * Language: PHP.
 *
 * CosmoDev polyglot showcase port of the `contrast` tool.
 * Ported from src/lib/color.ts — functionally equivalent (same inputs -> same outputs).
 *
 * Display source — part of CosmoDev's polyglot tool pages.
 */

/**
 * Parses a CSS-style hex color into [r, g, b] (each 0-255).
 *
 * Accepts an optional leading '#', a 3-digit shorthand ("#abc"), or the
 * 6-digit form ("#aabbcc"). Returns null when the string is not a valid
 * hex color, so callers can distinguish "invalid" from a real color.
 *
 * @param string $hex
 * @return array{0:int,1:int,2:int}|null
 */
function hex_to_rgb(string $hex): ?array
{
    // Trim surrounding whitespace, then drop exactly one leading '#'
    // (ltrim would strip *all* leading '#' characters and diverge from CSS).
    $h = trim($hex);
    if (str_starts_with($h, '#')) {
        $h = substr($h, 1);
    }

    // Expand CSS shorthand: each hex digit is doubled ("abc" -> "aabbcc").
    if (strlen($h) === 3) {
        $h = $h[0] . $h[0] . $h[1] . $h[1] . $h[2] . $h[2];
    }

    if (preg_match('/^[0-9a-fA-F]{6}$/', $h) !== 1) {
        return null;
    }

    return [
        hexdec(substr($h, 0, 2)),
        hexdec(substr($h, 2, 2)),
        hexdec(substr($h, 4, 2)),
    ];
}

/**
 * Linearizes a single sRGB channel value (0-255) per WCAG 2.2.
 *
 * 8-bit color values are gamma-encoded for display; WCAG luminance is
 * computed in linear-light space using the inverse sRGB transfer function.
 * The small-value branch is the linear segment of that curve.
 */
function channel(float $c): float
{
    $c /= 255.0;
    return $c <= 0.03928 ? $c / 12.92 : pow(($c + 0.055) / 1.055, 2.4);
}

/**
 * WCAG relative luminance of a hex color on a 0..1 scale.
 * Returns null if the hex string is invalid.
 *
 * @param string $hex
 * @return float|null
 */
function luminance(string $hex): ?float
{
    $rgb = hex_to_rgb($hex);
    if ($rgb === null) {
        return null;
    }
    // Rec. 709 luma coefficients for the red/green/blue primaries.
    return 0.2126 * channel($rgb[0]) + 0.7152 * channel($rgb[1]) + 0.0722 * channel($rgb[2]);
}

/**
 * WCAG contrast ratio between two hex colors on a 1..21 scale.
 * Returns null if either color is invalid.
 *
 * The 0.05 offset models the ambient luminance assumed by WCAG, keeping the
 * ratio finite (and >= 1) even for identical colors.
 *
 * @param string $fg
 * @param string $bg
 * @return float|null
 */
function contrast_ratio(string $fg, string $bg): ?float
{
    $l1 = luminance($fg);
    $l2 = luminance($bg);
    if ($l1 === null || $l2 === null) {
        return null;
    }
    $lighter = max($l1, $l2);
    $darker = min($l1, $l2);
    return ($lighter + 0.05) / ($darker + 0.05);
}

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →