Skip to content

Box-Shadow Generator — JavaScript source

Design layered CSS box-shadows with offset, blur, spread, color, and inset. Live preview and copy-ready CSS.

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

/**
 * box-shadow-generator - JavaScript polyglot showcase port.
 *
 * Pure CSS box-shadow builder. Formats one or more shadow layers and joins
 * them into a single CSS `box-shadow` value. Deterministic, dependency-free,
 * and never throws: invalid colors quietly fall back to a neutral translucent
 * black, so a single bad color never breaks the whole stack.
 *
 * This is the JavaScript sibling of src/lib/boxShadow.ts (the canonical
 * TypeScript that powers the live tool). The logic is identical; the only
 * adaptation is that types are expressed as JSDoc rather than TS interfaces.
 *
 * Ported from src/lib/boxShadow.ts.
 * Display source - part of CosmoDev's polyglot tool pages.
 */

/**
 * A single shadow layer in a CSS box-shadow stack.
 *
 * @typedef {Object} ShadowLayer
 * @property {boolean} inset   - Whether the shadow is drawn inside the box.
 * @property {number}  offsetX - Horizontal offset in px.
 * @property {number}  offsetY - Vertical offset in px.
 * @property {number}  blur    - Blur radius in px.
 * @property {number}  spread  - Spread distance in px.
 * @property {string}  color   - Any CSS color (named, hex, rgb(), hsl(), ...).
 */

/**
 * Outcome of validating a color string.
 *
 * @typedef {Object} ColorCheck
 * @property {boolean} ok    - True when the color string is recognized.
 * @property {(string|null)} error - Failure reason, or null when ok.
 */

/** Curated set of CSS named colors accepted without further inspection. */
const NAMED_COLORS = new Set([
  'transparent', 'black', 'white', 'red', 'green', 'blue', 'yellow',
  'orange', 'purple', 'pink', 'gray', 'grey', 'brown', 'cyan', 'magenta',
]);

/**
 * Validate a CSS color string.
 *
 * Accepts the named colors above plus hex (#rgb, #rrggbb, #rrggbbaa), rgb()/
 * rgba(), and hsl()/hsla() forms. The functional notations are checked for
 * well-formed wrappers only - their contents are otherwise unchecked, matching
 * the live tool's deliberately permissive behavior.
 *
 * @param {string} color
 * @returns {ColorCheck}
 */
export function parseColor(color) {
  // Lower-case + trim once so every shape check below sees a canonical form.
  const c = (color || '').trim().toLowerCase();
  if (!c) return { ok: false, error: 'empty color' };
  if (NAMED_COLORS.has(c)) return { ok: true, error: null };
  if (/^#[0-9a-f]{3}([0-9a-f]{3})?$/.test(c)) return { ok: true, error: null };
  if (/^#[0-9a-f]{8}$/.test(c)) return { ok: true, error: null };
  if (/^rgba?\([^)]+\)$/.test(c)) return { ok: true, error: null };
  if (/^hsla?\([^)]+\)$/.test(c)) return { ok: true, error: null };
  return { ok: false, error: `invalid color: ${color}` };
}

/**
 * Coerce a color to a usable value: keep it when it parses, otherwise fall
 * back to a neutral translucent black. Keeping this total is what lets
 * buildBoxShadow never produce a broken declaration.
 *
 * @param {string} color
 * @returns {string}
 */
function normalizeColor(color) {
  return parseColor(color).ok ? color.trim() : 'rgba(0,0,0,0.5)';
}

/**
 * Render one shadow layer as its CSS fragment, e.g.
 * `"inset 4px 8px 16px 0px #1a2b3c"` or `"0px 2px 4px 0px rgba(0,0,0,0.5)"`.
 *
 * @param {ShadowLayer} layer
 * @returns {string}
 */
export function formatLayer(layer) {
  const { inset, offsetX, offsetY, blur, spread, color } = layer;
  return `${inset ? 'inset ' : ''}${offsetX}px ${offsetY}px ${blur}px ${spread}px ${normalizeColor(color)}`;
}

/**
 * Compose a full CSS `box-shadow` declaration from an ordered list of layers
 * (the first layer renders on top). An empty list yields the CSS keyword
 * `none`, matching the property's default value.
 *
 * @param {ShadowLayer[]} layers
 * @returns {string}
 */
export function buildBoxShadow(layers) {
  if (!layers.length) return 'none';
  return layers.map(formatLayer).join(', ');
}

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 →