Documentation
¶
Overview ¶
Package styleengine provides a typed, safe, and fast Go-native CSS construction and emission engine.
It is useful for design systems, theme compilers, server-rendered UIs, email templates, and any situation where you need to build or sanitize CSS programmatically without string concatenation or ad-hoc templates.
A Sheet is the root IR: an ordered, deduped, mergeable collection of [Rule]s and [AtRule]s. The fluent Builder produces a Sheet from typed inputs; [Render] emits CSS (pretty or minified); Parse reverses the process (e.g. for validating or normalizing untrusted CSS fragments).
styleengine is intentionally narrow: no CSS-in-JS, no preprocessor syntax, no third-party language extensions. Modern native CSS only.
Basic usage ¶
Create a sheet with variables and rules, then render:
sheet, err := styleengine.New().
Var("color-primary", "#0a84ff").
Rule(":root").
Decl("color", styleengine.VarRef("color-primary", "black")).
Done().
Build().
Render(styleengine.RenderOptions{Pretty: true})
Sanitizing untrusted input ¶
Always sanitize CSS that comes from users, plugins, or customer overrides:
clean, err := styleengine.Sanitize(userSuppliedCSS)
Parsing existing CSS ¶
Round-trip existing fragments for validation or normalization:
parsed, err := styleengine.Parse(someCSS)
See the [examples] directory and the README for more complete programs.
Example ¶
Example shows basic construction of a sheet with variables and rules, then pretty render.
package main
import (
"fmt"
"github.com/septagon-oss/styleengine"
)
func main() {
sheet, err := styleengine.New().
Var("color-primary", "#0a84ff").
Rule(":root").
Decl("color", styleengine.VarRef("color-primary", "black")).
Done().
Build().
Render(styleengine.RenderOptions{Pretty: true})
if err != nil {
panic(err)
}
fmt.Println(sheet)
}
Output: :root { --color-primary: #0a84ff; color: var(--color-primary, black); }
Example (AtruleMedia) ¶
Example_atruleMedia demonstrates a nested @media at-rule (black-box coverage for at-rules claimed in README).
package main
import (
"fmt"
"github.com/septagon-oss/styleengine"
)
func main() {
sheet, _ := styleengine.New().
Media("(prefers-color-scheme: dark)", func(b *styleengine.Builder) {
b.Rule("body").Decl("background", styleengine.Literal("#000")).Done()
}).
Build().
Render(styleengine.RenderOptions{Pretty: true})
fmt.Println(sheet)
}
Output: @media (prefers-color-scheme: dark) { body { background: #000; } }
Example (ParseRoundtrip) ¶
Example_parseRoundtrip shows a simple parse + render cycle.
package main
import (
"fmt"
"github.com/septagon-oss/styleengine"
)
func main() {
parsed, err := styleengine.Parse(`:root { --foo: red; } .bar { color: var(--foo); }`)
if err != nil {
panic(err)
}
out, _ := parsed.Render(styleengine.RenderOptions{Pretty: false})
fmt.Println("roundtrip ok:", len(out) > 0)
}
Output: roundtrip ok: true
Example (Sanitize) ¶
Example_sanitize demonstrates rejecting dangerous untrusted CSS.
package main
import (
"fmt"
"github.com/septagon-oss/styleengine"
)
func main() {
_, err := styleengine.Sanitize(`body { background: url("javascript:alert(1)"); }`)
fmt.Println("sanitized err:", err != nil)
}
Output: sanitized err: true
Index ¶
- Constants
- func Sanitize(css string) (string, error)
- type AtRule
- type Builder
- func (b *Builder) Build() *Sheet
- func (b *Builder) FontFace(ff FontFaceDecl) *Builder
- func (b *Builder) Keyframes(name string, fn func(*KeyframesBuilder)) *Builder
- func (b *Builder) Layer(name string, fn func(*Builder)) *Builder
- func (b *Builder) Media(query string, fn func(*Builder)) *Builder
- func (b *Builder) Rule(selector string) *RuleBuilder
- func (b *Builder) Supports(condition string, fn func(*Builder)) *Builder
- func (b *Builder) Var(name, value string) *Builder
- type Declaration
- type Diagnostic
- type DiagnosticKind
- type FontFace
- type FontFaceDecl
- type KeyframesBuilder
- type RenderOptions
- type Rule
- type RuleBuilder
- type Selector
- type Sheet
- func (s *Sheet) AddFontFace(ff FontFaceDecl) *Sheet
- func (s *Sheet) AddRule(r Rule)
- func (s *Sheet) Diagnostics() []Diagnostic
- func (s *Sheet) Keyframes(name string, fn func(*KeyframesBuilder)) *Sheet
- func (s *Sheet) Layer(name string, fn func(*Sheet)) *Sheet
- func (s *Sheet) Media(query string, fn func(*Sheet)) *Sheet
- func (s *Sheet) Merge(other *Sheet) *Sheet
- func (s *Sheet) RegisteredVars() []string
- func (s *Sheet) Render(opts RenderOptions) (string, error)
- func (s *Sheet) RenderPretty() string
- func (s *Sheet) Supports(condition string, fn func(*Sheet)) *Sheet
- func (s *Sheet) UndefinedVars() []string
- func (s *Sheet) Var(name, value string) *Sheet
- type Value
Examples ¶
Constants ¶
const DefaultMaxBytes int64 = 1 << 20
DefaultMaxBytes is the default Render byte ceiling (1 MiB).
const ( // DiagUndefinedVar — a var(--x) reference has no matching Var // registration in the Sheet. Subject is the missing name. DiagUndefinedVar = "SE001" )
Stable diagnostic codes. Adding a new code is additive; reusing or reassigning an existing one breaks downstream tooling.
Variables ¶
This section is empty.
Functions ¶
func Sanitize ¶
Sanitize trims and validates a CSS declaration-block fragment. It rejects tokens that could escape the containing rule, import remote resources, or introduce script execution.
Call Sanitize on any CSS that comes from outside your trusted source tree (user input, customer theme overrides, plugin-provided fragments, etc.) before passing it to Literal or embedding it.
Trusted, in-tree CSS (your design system tokens, official themes) should NOT go through Sanitize — it intentionally rejects url(), @font-face, and other constructs that are valid and useful inside first-party stylesheets.
CSS escape sequences (e.g. `\75 rl(`) are rejected outright. Untrusted input has no legitimate need for them; allowing them would create bypasses.
Types ¶
type AtRule ¶
type AtRule struct {
// contains filtered or unexported fields
}
AtRule is opaque; consumers construct via Sheet.Media/Keyframes/Layer/Supports.
type Builder ¶
type Builder struct {
// contains filtered or unexported fields
}
Builder is the fluent surface over Sheet.
func (*Builder) Build ¶
Build returns the underlying Sheet. Subsequent builder calls continue to mutate the same Sheet.
func (*Builder) FontFace ¶
func (b *Builder) FontFace(ff FontFaceDecl) *Builder
FontFace appends a font-face declaration.
func (*Builder) Keyframes ¶
func (b *Builder) Keyframes(name string, fn func(*KeyframesBuilder)) *Builder
Keyframes wraps Sheet.Keyframes.
func (*Builder) Rule ¶
func (b *Builder) Rule(selector string) *RuleBuilder
Rule starts a RuleBuilder for the given selector.
type Declaration ¶
Declaration is property: value [!important].
func Decl ¶
func Decl(property string, value Value) Declaration
Decl is a constructor for Declaration.
func (Declaration) CSS ¶
func (d Declaration) CSS() string
func (Declaration) Important ¶
func (d Declaration) Important() Declaration
Important returns a copy of the declaration with !important set.
type Diagnostic ¶
type Diagnostic struct {
Code string // stable code (e.g., "SE001")
Kind DiagnosticKind // severity
Message string // human-readable description
Subject string // optional reference: selector, var name, etc.
}
Diagnostic is a stable, typed finding produced by Sheet.Diagnostics. Downstream linters, compilers, and validation pipelines can consume these directly instead of regex-scanning rendered CSS text.
type DiagnosticKind ¶
type DiagnosticKind int
DiagnosticKind classifies the severity of a Diagnostic.
const ( // DiagnosticError indicates a finding that must be fixed; emitting the // CSS as-is would produce broken or unsafe output. DiagnosticError DiagnosticKind = iota // DiagnosticWarning indicates a likely bug or stylistic issue that // should be reviewed but does not block emission. DiagnosticWarning // DiagnosticHint indicates an opportunity for improvement; safe to ignore. DiagnosticHint )
func (DiagnosticKind) String ¶
func (k DiagnosticKind) String() string
type FontFace ¶
type FontFace struct {
// contains filtered or unexported fields
}
FontFace is the runtime IR for @font-face.
type FontFaceDecl ¶
type FontFaceDecl struct {
Family string
Src string
Weight string
Style string
Display string
UnicodeRange string
}
FontFaceDecl is the structured form of an @font-face block.
type KeyframesBuilder ¶
type KeyframesBuilder struct {
// contains filtered or unexported fields
}
KeyframesBuilder accumulates animation stops.
func (*KeyframesBuilder) At ¶
func (k *KeyframesBuilder) At(offset string, decls ...Declaration) *KeyframesBuilder
At adds a keyframe stop ("0%", "50%", "from", "to", etc.).
type RenderOptions ¶
RenderOptions configures Sheet.Render.
Pretty and Minify are mutually exclusive: setting both returns an error. MaxBytes caps the final output size; 0 means DefaultMaxBytes.
type Rule ¶
type Rule struct {
Selector Selector
Decls []Declaration
}
Rule is a (selector, []declaration) pair.
type RuleBuilder ¶
type RuleBuilder struct {
// contains filtered or unexported fields
}
RuleBuilder accumulates declarations for one selector.
func (*RuleBuilder) Decl ¶
func (r *RuleBuilder) Decl(property string, value Value) *RuleBuilder
Decl adds a declaration.
func (*RuleBuilder) Done ¶
func (r *RuleBuilder) Done() *Builder
Done commits the rule into the parent Builder's Sheet.
func (*RuleBuilder) ImportantDecl ¶
func (r *RuleBuilder) ImportantDecl(property string, value Value) *RuleBuilder
ImportantDecl adds an !important declaration.
type Selector ¶
type Selector struct {
// contains filtered or unexported fields
}
Selector is a normalized CSS selector. Use NewSelector or MustSelector.
func NewSelector ¶
NewSelector parses and normalizes a selector. Returns error if empty.
type Sheet ¶
type Sheet struct {
// contains filtered or unexported fields
}
Sheet is the root IR. It is an ordered, deduped, mergeable collection of Rules and AtRules.
Sheet is NOT safe for concurrent mutation. The design intent is single-writer construction: a Sheet is constructed in one goroutine, possibly merged with other partial Sheets, then rendered. If a future consumer needs concurrent mutation, wrap calls in a mutex at the caller — Sheet itself stays lock-free to keep render hot-paths fast.
func Parse ¶
Parse converts a CSS string into a Sheet. Only top-level qualified rules (selector { ... }) and their declarations are recognized in v1. At-rules and other grammar constructs are skipped without error.
func (*Sheet) AddFontFace ¶
func (s *Sheet) AddFontFace(ff FontFaceDecl) *Sheet
AddFontFace appends an @font-face block.
func (*Sheet) AddRule ¶
AddRule adds (or merges) a Rule. If a rule with the same normalized selector already exists, the new declarations merge into it; duplicates within a (selector, property, !important) triple are resolved last-write-wins.
func (*Sheet) Diagnostics ¶
func (s *Sheet) Diagnostics() []Diagnostic
Diagnostics returns the current set of typed findings for the Sheet. The result is sorted by (Code, Subject) for deterministic output.
v1 reports SE001 for undefined var refs. Future codes follow the "SE0xx" prefix; consumers should treat unknown codes as informational.
func (*Sheet) Keyframes ¶
func (s *Sheet) Keyframes(name string, fn func(*KeyframesBuilder)) *Sheet
Keyframes adds a @keyframes block.
func (*Sheet) Media ¶
Media nests a @media block. The closure receives a child Sheet that may contain rules, vars, and nested at-rules.
func (*Sheet) Merge ¶
Merge appends every rule, at-rule, and font-face from other into s, preserving Sheet's dedup invariants. The other Sheet is unchanged.
Merge deep-copies AtRule and keyframesBody contents so that subsequent mutation of the source sheet (or its captured inner builders) does not leak into the merged result. Composing several partial Sheets (e.g., token-derived + layered overrides) is safe even if the partial Sheets are reused after merging.
func (*Sheet) RegisteredVars ¶
RegisteredVars returns the sorted list of custom-property names registered via Var (without the "--" prefix).
func (*Sheet) Render ¶
func (s *Sheet) Render(opts RenderOptions) (string, error)
Render emits CSS per options. Returns an error if MaxBytes is exceeded or if both Pretty and Minify are set.
func (*Sheet) RenderPretty ¶
RenderPretty emits the Sheet as indented CSS. AtRules and FontFaces render after top-level rules in insertion order.
func (*Sheet) UndefinedVars ¶
UndefinedVars returns var(--x) references in the Sheet that have no matching Var registration. Output is sorted. The scan covers:
- Top-level rules (excluding the :root rule itself)
- Nested rules inside [@media], [@layer], [@supports] at-rules
- Keyframe-stop declarations inside [@keyframes]
Both typed VarRef values (constructed by the Builder) and var() references that came in through Parse as opaque Literal text are detected.
type Value ¶
type Value interface {
CSS() string
// contains filtered or unexported methods
}
Value is the typed RHS of a declaration. Values self-render to CSS.
func Literal ¶
Literal returns a Value that emits the given string verbatim. Use only with trusted input (token values, programmatically built strings). For untrusted input go through Sanitize.
func VarRef ¶
VarRef returns a Value referencing a CSS custom property. The leading "--" is added automatically. Panics if name is not a valid identifier, or if fallback contains characters that could escape the var() expression — the unmatched grouping/structural characters `)`, `(`, `{`, `}`, `;`, or a comment delimiter `/*`/`*/`. Callers that need a nested var() fallback must compose values through the Builder/Value API rather than embed raw CSS through this helper.