Skip to content

CSS Gradient Generator — PHP source

Build linear, radial, and conic CSS gradients with multiple color stops and positions. Live preview and copy-ready CSS.

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

<?php
/**
 * Pure CSS-gradient builder — PHP polyglot showcase port.
 *
 * Language:    PHP (standard library only, no Composer dependencies)
 * Origin:      CosmoDev polyglot showcase — port of the css-gradient-generator tool
 * Ported from: src/lib/cssGradient.ts (the canonical TypeScript implementation)
 *
 * Purpose:     Build linear / radial / conic CSS gradient strings from a small
 *              config array. Deterministic and side-effect free — never throws.
 *
 * Display source — part of CosmoDev's polyglot tool pages.
 */

declare(strict_types=1);

/**
 * Named CSS colors this tool accepts. The full CSS spec defines ~148 names,
 * but we accept only the common, unambiguous set so output stays predictable.
 */
const NAMED_COLORS = [
    'transparent', 'black', 'white', 'red', 'green', 'blue', 'yellow',
    'orange', 'purple', 'pink', 'gray', 'grey', 'brown', 'cyan', 'magenta',
    'none', 'currentcolor',
];

// Regexes mirror the TypeScript source exactly: 3- or 6-digit hex, 8-digit
// alpha hex, and the rgb()/rgba() / hsl()/hsla() functional forms.
const HEX_3_OR_6 = '/^#[0-9a-f]{3}([0-9a-f]{3})?$/';
const HEX_8      = '/^#[0-9a-f]{8}$/';
const RGB_FUNC   = '/^rgba?\([^)]+\)$/';
const HSL_FUNC   = '/^hsla?\([^)]+\)$/';

/**
 * Validate a CSS color string.
 *
 * Returns ['ok' => bool, 'error' => string|null] — error is null when ok.
 * Accepts named colors, #RGB / #RRGGBB / #RRGGBBAA hex, and rgb()/rgba()/
 * hsl()/hsla() functional forms. The input is trimmed and lowercased first.
 */
function parseColor(string $color): array {
    $c = strtolower(trim($color));
    if ($c === '') {
        return ['ok' => false, 'error' => 'empty color'];
    }
    if (in_array($c, NAMED_COLORS, true)) {
        return ['ok' => true, 'error' => null];
    }
    if (preg_match(HEX_3_OR_6, $c) || preg_match(HEX_8, $c)) {
        return ['ok' => true, 'error' => null];
    }
    if (preg_match(RGB_FUNC, $c) || preg_match(HSL_FUNC, $c)) {
        return ['ok' => true, 'error' => null];
    }
    return ['ok' => false, 'error' => "invalid color: {$color}"];
}

/**
 * Coerce a possibly-invalid color to a safe value. Invalid colors become solid
 * black so the gradient always renders. Original casing is preserved — we only
 * ever trim, mirroring the TypeScript source.
 */
function normalizeColor(string $color): string {
    return parseColor($color)['ok'] ? trim($color) : '#000000';
}

/**
 * Round a position the way JavaScript's Math.round does (half rounds up) and
 * format as an integer string. PHP's default round() (PHP_ROUND_HALF_UP)
 * agrees with Math.round for non-negative values — the gradient-position
 * domain (0..100). Cast through int so whole floats render without ".0".
 */
function roundPosition(float $x): string {
    return (string) (int) round($x);
}

/**
 * Build a complete CSS gradient string from a config array:
 *   ['type' => 'linear'|'radial'|'conic', 'angle' => float,
 *    'stops' => [['color' => string, 'position' => float], ...],
 *    'radialShape' => 'circle'|'ellipse'|null]
 *
 * Stops are sorted ascending by position (stable on PHP 8.0+, matching modern
 * JavaScript's stable Array.sort). Fewer than 2 stops collapse to a black →
 * white default so the output is always renderable. `radialShape` uses PHP's
 * `??`, which — like TS — only falls back on null/unset; an explicit empty
 * string passes through.
 *
 * @param array{type: string, angle: int|float, stops: array, radialShape?: string|null} $config
 */
function buildGradient(array $config): string {
    // Copy stops so we never mutate the caller's array, then stable-sort by
    // position ascending.
    $stops = array_values($config['stops'] ?? []);
    usort($stops, static fn (array $a, array $b): int => $a['position'] <=> $b['position']);

    if (count($stops) < 2) {
        $stops = [
            ['color' => '#000000', 'position' => 0],
            ['color' => '#ffffff', 'position' => 100],
        ];
    }

    $parts = array_map(
        static fn (array $s): string =>
            normalizeColor($s['color']) . ' ' . roundPosition($s['position']) . '%',
        $stops
    );
    $stopsStr = implode(', ', $parts);

    switch ($config['type']) {
        case 'linear':
            return "linear-gradient({$config['angle']}deg, {$stopsStr})";
        case 'radial':
            $shape = $config['radialShape'] ?? 'circle';
            return "radial-gradient({$shape}, {$stopsStr})";
        case 'conic':
            return "conic-gradient(from {$config['angle']}deg, {$stopsStr})";
        default:
            // Unknown type — mirrors the TypeScript, which returns undefined.
            return '';
    }
}

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 →