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 →