Skip to content

Box-Shadow Generator — Java source

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

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

// box-shadow-generator — Java 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 on bad input: invalid colors quietly fall back to a
// neutral translucent black, so a single bad color never breaks the whole
// stack.
//
// This is the Java sibling of src/lib/boxShadow.ts (the canonical
// TypeScript that powers the live tool). The public surface mirrors the TS:
// a ShadowLayer record plus parseColor, formatLayer, and buildBoxShadow.
// (The class is package-private so the showcase filename java.java compiles
// as-is — a public class would demand BoxShadowGenerator.java.)
//
// Language: Java 17 (standard library only).
// Ported from src/lib/boxShadow.ts.
// Source: CosmoDev polyglot showcase port.
// License: display source — part of CosmoDev's polyglot tool pages.

package org.cosmolabs.cosmodev.boxshadow;

import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Pattern;

/** Pure CSS box-shadow builder — the Java sibling of the canonical TS lib. */
final class BoxShadowGenerator {

    /** A single layer in a CSS box-shadow stack. */
    record ShadowLayer(
            boolean inset,   // draw the shadow inside the box
            double offsetX,  // horizontal offset in px
            double offsetY,  // vertical offset in px
            double blur,     // blur radius in px
            double spread,   // spread distance in px
            String color     // any CSS color (named, hex, rgb(), hsl(), ...)
    ) {}

    /**
     * Outcome of validating a color string. {@code error} is {@code null}
     * when {@code ok} is true, mirroring the TypeScript
     * {@code { ok: boolean; error: string | null }} shape.
     */
    record ColorResult(boolean ok, String error) {
        static ColorResult okValue() {
            return new ColorResult(true, null);
        }

        static ColorResult errValue(String message) {
            return new ColorResult(false, message);
        }
    }

    // CSS named colors accepted without further inspection. parseColor()
    // lower-cases its input first, so membership is effectively
    // case-insensitive.
    private static final Set<String> NAMED_COLORS = Set.of(
            "transparent", "black", "white", "red", "green", "blue", "yellow",
            "orange", "purple", "pink", "gray", "grey", "brown", "cyan", "magenta");

    // Color-shape patterns. The input is already lower-cased + stripped
    // before these run, so the hex classes use only [0-9a-f]. The contents
    // of rgb()/hsl() are not validated beyond a well-formed wrapper,
    // matching the live tool.
    private static final Pattern HEX_SHORT = // #rgb or #rrggbb
            Pattern.compile("^#[0-9a-f]{3}([0-9a-f]{3})?$");
    private static final Pattern HEX_ALPHA = // #rrggbbaa
            Pattern.compile("^#[0-9a-f]{8}$");
    private static final Pattern RGB_FN = // rgb() / rgba()
            Pattern.compile("^rgba?\\([^)]+\\)$");
    private static final Pattern HSL_FN = // hsl() / hsla()
            Pattern.compile("^hsla?\\([^)]+\\)$");

    private BoxShadowGenerator() {}

    /**
     * Validate a CSS color string. Accepts the curated named-color set plus
     * hex (#rgb, #rrggbb, #rrggbbaa), rgb()/rgba(), and hsl()/hsla() forms.
     * The functional notations are checked for well-formed wrappers only,
     * not their numeric contents.
     */
    static ColorResult parseColor(String color) {
        // Lower-case + strip once so every shape check below sees a canonical form.
        String c = (color == null ? "" : color).strip().toLowerCase(Locale.ROOT);
        if (c.isEmpty()) return ColorResult.errValue("empty color");
        if (NAMED_COLORS.contains(c)) return ColorResult.okValue();
        if (HEX_SHORT.matcher(c).matches()
                || HEX_ALPHA.matcher(c).matches()
                || RGB_FN.matcher(c).matches()
                || HSL_FN.matcher(c).matches()) {
            return ColorResult.okValue();
        }
        return ColorResult.errValue("invalid color: " + color);
    }

    // Keep a color when it parses; otherwise substitute a neutral
    // translucent black. This is what makes buildBoxShadow total over
    // arbitrary input.
    private static String normalizeColor(String color) {
        return parseColor(color).ok() ? color.strip() : "rgba(0,0,0,0.5)";
    }

    // Render a number with JavaScript parity: whole numbers drop the
    // trailing ".0" (so 5.0 -> "5", 5.5 -> "5.5"). Double.toString keeps the
    // short form for fractions, mirroring JS template-literal coercion.
    private static String formatNumber(double value) {
        if (value == Math.rint(value) && Math.abs(value) < 1e15) {
            return String.valueOf((long) value);
        }
        return String.valueOf(value);
    }

    /**
     * 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)".
     */
    static String formatLayer(ShadowLayer layer) {
        String prefix = layer.inset() ? "inset " : "";
        return prefix + formatNumber(layer.offsetX()) + "px "
                + formatNumber(layer.offsetY()) + "px "
                + formatNumber(layer.blur()) + "px "
                + formatNumber(layer.spread()) + "px "
                + normalizeColor(layer.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.
     */
    static String buildBoxShadow(List<ShadowLayer> layers) {
        if (layers.isEmpty()) return "none";
        StringBuilder out = new StringBuilder();
        for (int i = 0; i < layers.size(); i++) {
            if (i > 0) out.append(", ");
            out.append(formatLayer(layers.get(i)));
        }
        return out.toString();
    }
}

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 →