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 →