Skip to content

Box-Shadow Generator — Swift source

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

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

// box-shadow-generator — Swift 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 traps 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 Swift sibling of src/lib/boxShadow.ts (the canonical
// TypeScript that powers the live tool). The public surface mirrors the TS:
// a ShadowLayer struct plus parseColor, formatLayer, and buildBoxShadow.
// Regex literals need Swift 5.7+ (pound-delimited #/…/# because the
// patterns themselves contain '#'); the shape checks accept exactly what
// the TS regexes do.
//
// Language: Swift 5.9 (standard library only).
// Ported from src/lib/boxShadow.ts.
// Source: CosmoDev polyglot showcase port.
// License: display source — part of CosmoDev's polyglot tool pages.

/// A single layer in a CSS box-shadow stack.
struct ShadowLayer {
    var inset = false      // draw the shadow inside the box
    var offsetX = 0.0      // horizontal offset in px
    var offsetY = 0.0      // vertical offset in px
    var blur = 0.0         // blur radius in px
    var spread = 0.0       // spread distance in px
    var color = ""         // any CSS color (named, hex, rgb(), hsl(), ...)
}

/// Outcome of validating a color string. `error` is nil when `ok` is true,
/// mirroring the TypeScript `{ ok: boolean; error: string | null }` shape.
struct ColorResult: Equatable {
    let ok: Bool
    let error: String?

    static func okValue() -> ColorResult { .init(ok: true, error: nil) }

    static func errValue(_ message: String) -> ColorResult { .init(ok: false, error: message) }
}

extension String {
    /// Leading/trailing whitespace trimmed — the `.trim()` the TS applies
    /// before validating and when echoing a color back. Hand-rolled with
    /// stdlib Character.isWhitespace to stay Foundation-free.
    func trimmed() -> String {
        var view = self[...]
        while let first = view.first, first.isWhitespace { view = view.dropFirst() }
        while let last = view.last, last.isWhitespace { view = view.dropLast() }
        return String(view)
    }
}

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

    /// Color-shape patterns. The input is already lower-cased + trimmed
    /// 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 let hexShortRe = #/^#[0-9a-f]{3}([0-9a-f]{3})?$/# // #rgb or #rrggbb
    private static let hexAlphaRe = #/^#[0-9a-f]{8}$/#               // #rrggbbaa
    private static let rgbRe = #/^rgba?\([^)]+\)$/#                   // rgb() / rgba()
    private static let hslRe = #/^hsla?\([^)]+\)$/#                   // hsl() / hsla()

    /// 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 func parseColor(_ color: String) -> ColorResult {
        // Lower-case + trim once so every shape check below sees a canonical form.
        let c = color.trimmed().lowercased()
        guard !c.isEmpty else { return .errValue("empty color") }
        if namedColors.contains(c) { return .okValue() }
        if c.wholeMatch(of: hexShortRe) != nil
            || c.wholeMatch(of: hexAlphaRe) != nil
            || c.wholeMatch(of: rgbRe) != nil
            || c.wholeMatch(of: hslRe) != nil
        {
            return .okValue()
        }
        return .errValue("invalid color: \(color)")
    }

    /// Keep a color when it parses and otherwise substitute a neutral
    /// translucent black. This is what makes buildBoxShadow total over
    /// arbitrary input.
    private static func normalizeColor(_ color: String) -> String {
        parseColor(color).ok ? color.trimmed() : "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"). String.init(Double)
    /// keeps the short form for fractions, mirroring JS template-literal
    /// coercion.
    private static func formatNumber(_ value: Double) -> String {
        if value == value.rounded(.towardZero) && Swift.abs(value) < 1e15 {
            return String(Int64(value))
        }
        return String(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 func formatLayer(_ layer: ShadowLayer) -> String {
        let 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 array yields the
    /// CSS keyword "none", matching the property's default value.
    static func buildBoxShadow(_ layers: [ShadowLayer]) -> String {
        layers.isEmpty ? "none" : layers.map(formatLayer).joined(separator: ", ")
    }
}

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 →