Box-Shadow Generator — Go source
Design layered CSS box-shadows with offset, blur, spread, color, and inset. Live preview and copy-ready CSS.
This is the Go implementation — the same logic the interactive tool runs, in a shareable, citable form.
// Package boxshadow is the Go twin of CosmoDev's src/lib/boxShadow.ts (dual
// source: the web lib is TypeScript, the CLI lib is Go — kept in lock-step).
// Pure + deterministic, never panics. The table-driven tests in
// box-shadow-generator_test.go share vectors with src/lib/boxShadow.test.ts so
// the two implementations are held to the same contract.
//
// It builds CSS box-shadow strings: validate a color, format one shadow layer,
// and join many layers. Invalid colors fall back to a translucent black
// (rgba(0,0,0,0.5)) rather than erroring — exactly like the TS lib.
package boxshadow
import (
"regexp"
"strconv"
"strings"
)
// ShadowLayer is one box-shadow layer. It mirrors the ShadowLayer interface in
// src/lib/boxShadow.ts. The numeric fields are float64 to match TypeScript's
// number (fractional pixels like 0.5px are valid CSS and supported by the web
// tool); integers format without a trailing decimal.
type ShadowLayer struct {
Inset bool
OffsetX float64
OffsetY float64
Blur float64
Spread float64
Color string
}
// ColorResult is the outcome of ParseColor: OK reports validity, Error is the
// ""empty when OK. It mirrors { ok: boolean; error: string | null } in the TS lib.
type ColorResult struct {
OK bool
Error string
}
// namedColors holds the CSS named colors ParseColor recognizes. Compared
// case-insensitively (input is lowercased before lookup). Mirrors NAMED_COLORS.
var namedColors = map[string]bool{
"transparent": true, "black": true, "white": true, "red": true,
"green": true, "blue": true, "yellow": true, "orange": true,
"purple": true, "pink": true, "gray": true, "grey": true,
"brown": true, "cyan": true, "magenta": true,
}
var (
reHex3or6 = regexp.MustCompile(`^#[0-9a-f]{3}([0-9a-f]{3})?$`)
reHex8 = regexp.MustCompile(`^#[0-9a-f]{8}$`)
reRGBA = regexp.MustCompile(`^rgba?\([^)]+\)$`)
reHSLA = regexp.MustCompile(`^hsla?\([^)]+\)$`)
)
// ParseColor validates a CSS color string. It accepts the named colors above,
// 3/6/8-digit hex, and rgb()/rgba()/hsl()/hsla() functional notation. It is the
// Go twin of parseColor() in src/lib/boxShadow.ts and never panics: empty input
// yields {OK:false, Error:"empty color"}, any other unparseable input yields
// {OK:false, Error:"invalid color: <original>"}.
func ParseColor(color string) ColorResult {
c := strings.ToLower(strings.TrimSpace(color))
if c == "" {
return ColorResult{OK: false, Error: "empty color"}
}
if namedColors[c] {
return ColorResult{OK: true}
}
if reHex3or6.MatchString(c) || reHex8.MatchString(c) {
return ColorResult{OK: true}
}
if reRGBA.MatchString(c) || reHSLA.MatchString(c) {
return ColorResult{OK: true}
}
return ColorResult{OK: false, Error: "invalid color: " + color}
}
// normalizeColor returns the trimmed color when it is valid, otherwise the
// fallback translucent black. Mirrors normalizeColor() in the TS lib.
func normalizeColor(color string) string {
if ParseColor(color).OK {
return strings.TrimSpace(color)
}
return "rgba(0,0,0,0.5)"
}
// formatNum renders a shadow length the way the TS template literal does:
// integers have no decimal point, fractions keep their minimal form
// (strconv FormatFloat 'f' -1 → "4", "-5", "1.5", "0").
func formatNum(n float64) string {
return strconv.FormatFloat(n, 'f', -1, 64)
}
// FormatLayer renders one ShadowLayer as its CSS box-shadow fragment. It is the
// Go twin of formatLayer() in src/lib/boxShadow.ts. An invalid color falls back
// to rgba(0,0,0,0.5) rather than erroring, matching the TS lib.
func FormatLayer(layer ShadowLayer) string {
var b strings.Builder
if layer.Inset {
b.WriteString("inset ")
}
b.WriteString(formatNum(layer.OffsetX))
b.WriteString("px ")
b.WriteString(formatNum(layer.OffsetY))
b.WriteString("px ")
b.WriteString(formatNum(layer.Blur))
b.WriteString("px ")
b.WriteString(formatNum(layer.Spread))
b.WriteString("px ")
b.WriteString(normalizeColor(layer.Color))
return b.String()
}
// BuildBoxShadow joins one or more shadow layers into a full CSS box-shadow
// value. It is the Go twin of buildBoxShadow() in src/lib/boxShadow.ts: an empty
// slice returns "none", otherwise layers are formatted and joined with ", ".
func BuildBoxShadow(layers []ShadowLayer) string {
if len(layers) == 0 {
return "none"
}
parts := make([]string, len(layers))
for i, l := range layers {
parts[i] = FormatLayer(l)
}
return strings.Join(parts, ", ")
}
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 →