Skip to content

CSS Gradient Generator — JavaScript source

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

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

/**
 * Pure CSS-gradient builder - JavaScript polyglot showcase port.
 *
 * Language:    JavaScript (ES module, no dependencies, no DOM)
 * 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 object. Deterministic and side-effect free - never throws.
 *
 * Display source - part of CosmoDev's polyglot tool pages.
 */

/**
 * 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 = new Set([
  '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, error } - error is null when ok, a human message otherwise.
 */
export function parseColor(color) {
  const c = (color || '').trim().toLowerCase();
  if (!c) return { ok: false, error: 'empty color' };
  if (NAMED_COLORS.has(c)) return { ok: true, error: null };
  if (HEX_3_OR_6.test(c)) return { ok: true, error: null };
  if (HEX_8.test(c)) return { ok: true, error: null };
  if (RGB_FUNC.test(c)) return { ok: true, error: null };
  if (HSL_FUNC.test(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. The original casing is preserved - we
 * only ever trim, mirroring the TypeScript source.
 */
function normalizeColor(color) {
  const { ok } = parseColor(color);
  return ok ? color.trim() : '#000000';
}

/**
 * Build a complete CSS gradient string from a config:
 *   { type: 'linear'|'radial'|'conic', angle, stops: [{color, position}], radialShape? }
 *
 * Stops are sorted ascending by position. Fewer than 2 stops collapse to a
 * safe black → white default so the output is always renderable. Positions are
 * rounded to integers via Math.round (half rounds toward +∞). `radialShape`
 * uses nullish coalescing: only null/undefined falls back to "circle"; an
 * explicit empty string passes through.
 */
export function buildGradient(config) {
  let stops = [...config.stops].sort((a, b) => a.position - b.position);

  // Drop nonsensical entries; pad if fewer than 2.
  if (stops.length < 2) {
    stops = [
      { color: '#000000', position: 0 },
      { color: '#ffffff', position: 100 },
    ];
  }

  const stopsStr = stops
    .map((s) => `${normalizeColor(s.color)} ${Math.round(s.position)}%`)
    .join(', ');

  switch (config.type) {
    case 'linear':
      return `linear-gradient(${config.angle}deg, ${stopsStr})`;
    case 'radial':
      return `radial-gradient(${config.radialShape ?? 'circle'}, ${stopsStr})`;
    case 'conic':
      return `conic-gradient(from ${config.angle}deg, ${stopsStr})`;
  }
  // Unhandled type - mirrors the TypeScript, which falls off the switch and
  // implicitly returns undefined.
}

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 →