Typed CSS design tokens and emission engine for the tinywasm framework.
This module acts as the single source of truth for design decisions and theme construction. It replaces string-based .css files with Go-typed design tokens, exposing both RootCSS() and RenderCSS() with strictly separate responsibilities:
RootCSS()→ vocabulary: design token declarations — brand, source tokens, scales.RenderCSS()→ logic: minimal reset + active-token bindings +@media (prefers-color-scheme).
The internal DSL ensures that every selector, declaration, and token reference is a Go expression, providing compile-time safety and eliminating hex-fallback drift.
To prevent design-system evasion, the low-level CSS properties and free-value constructors have been unexported from the public surface.
The public API consists solely of:
TokenandPairdesign tokens.- The design token catalog (e.g.,
ColorPrimary,ColorSurface,Space2,MixHover, etc.). Hover(t),Focus(t),Press(t)— interaction derivations viacolor-mix().Device,Mobile,Tablet,Desktop,Query(...)— typed viewport classes for media queries.StylesheetandNewStylesheetfor compilation.Theme,Set,SetThemefor rebranded app themes.
All component styling is expressed using the semantic visual intention API in github.com/tinywasm/widget/style, which compiles down to CSS rules.
Components do not write raw CSS or lower-level property functions. Instead, they express intent using high-level layouts (Stack, Row, Split), semantic surfaces (On(Page), On(Panel)), and scale exceptions (Fill(), Round(), Pad()):
package targetlist
import (
"github.com/tinywasm/widget"
"github.com/tinywasm/widget/style"
)
const (
nameTargetList = widget.Name("targetlist")
partRow = widget.Part("row")
)
func (l *TargetList) WidgetName() widget.Name { return nameTargetList }
func (l *TargetList) WidgetKind() widget.Kind { return widget.Listbox }
func (l *TargetList) Style() *style.Sheet {
return style.Of(nameTargetList).
Root(style.Stack(style.Space1), style.On(style.Sunken), style.Scrolls()).
Part(partRow, style.Row(style.Space2), style.On(style.Panel), style.Pad(style.Space2), style.Round(style.RadiusSm)).
When(widget.Selected, partRow, style.On(style.Selected))
}assetmin recognizes two CSS functions with strictly separate roles:
| Function | Slot | Replacement | Content |
|---|---|---|---|
RootCSS() *Stylesheet |
open |
Single-winner — app replaces framework | :root {} value declarations (vocabulary) |
RenderCSS() *Stylesheet |
middle |
Additive — every module's contribution is preserved | CSS rules that consume tokens via var() (logic) |
The split is the key to safe theming: vocabulary is replaceable so apps can rebrand; logic is additive so dark-mode switching cannot be deleted by accident.
To apply a theme or rebrand to an application, the root project exposes its own RootCSS(). Because assetmin treats the :root block as a single-winner slot, the app's RootCSS() completely replaces the library defaults.
// config/css.go in the application (!wasm)
import "github.com/tinywasm/css"
func RootCSS() *css.Stylesheet {
return css.Theme(
css.Set(css.ColorPrimary, "#FF6B35"),
css.SetTheme(css.ColorBackground, "#FAFAFA", "#121212"),
css.SetTheme(css.ColorSurface, "#F2F2F7", "#161B22"),
css.Set(css.MixHover, "22%"),
)
}Theme() returns a stylesheet with the token declarations that the app needs to overwrite, appended at the end of the default catalog block. This ensures that the app's branding overrides cascade correctly.
The app does not need to redeclare active layer bindings (--color-surface, etc.) or @media (prefers-color-scheme) logic; those reside in RenderCSS() and remain always active.
| Action | API |
|---|---|---|
| Rebrand brand color | css.Set(css.ColorPrimary, "#hex") |
| Change background (theme pair) | css.SetTheme(css.ColorBackground, "#light", "#dark") |
| Adjust interaction intensity | css.Set(css.MixHover, "22%") |
| Adjust global border-radius | css.Set(css.RadiusMd, "12px") |
| Adjust typographic scale | css.Set(css.TextBase, "1.1rem") |
Tokens are the single source of truth for all design decisions.
| Group | Purpose |
|---|---|
| Color — Brand | Fixed identity colors (e.g. ColorPrimary, ColorOnPrimary) |
| Color — Theme | Adaptive light/dark active layers (e.g. ColorBackground, ColorSurface) |
| Color — Pairs | Coupled background/foreground surface decisions (e.g. SurfacePanel, SurfaceSunken) |
| Color — Computed | Derived values via color-mix() (e.g. ColorSurfaceSunken, ColorSelection) |
| Interaction — Intensity | Mix-percentage scale for Hover/Focus/Press derivations (MixHover, MixFocus, MixPress) |
| Typography — Size | Font-size scale (Major Third ratio) |
| Typography — Extras | Line-height, weight, letter-spacing |
| Spacing | Margin/padding/gap scale (4px grid) |
| Border-radius | Consistent corner rounding |
| Elevation | Box-shadow scale |
| Motion | Animation timing + easing curves |
| Z-index | Stacking contract |
| Viewport classes | Typed media-query enum (Mobile, Tablet, Desktop) for responsive layout |
| Breakpoints | Viewport widths (BpSm/BpMd/BpLg/BpXl — container queries / JS only, not usable in @media) |
| Container widths | Max-width primitives |
| Rail widths | Sidebar/fixed-column scale (RailNarrow, RailWide) |
- Semantic names over values —
ColorOnSurfacenot#ffffff. Names describe intent; values can change. - Contrast safety by type — Background/foreground design decisions are coupled in
Pairstructures. Contrast ratio tests guarantee compliance with WCAG >= 4.5:1. - Scales over magic numbers — Typography and spacing follow mathematical ratios so all values are proportional and limited.
- Two-layer color pattern — Separates source values (per mode) from active tokens (used by components).
@media (prefers-color-scheme)switches modes without JS. - Single override point — Apps only need to change source-layer or scale variables; the rest cascades automatically.
- AGENTS.md — Constraints for anyone (human or agent) changing this library: the WASM boundary, the no-duplicated-value rule, and the checklist for adding a token.
- Technical Specifications — Detailed specifications of all active design tokens, browser-level light-dark handling, and the complete library contract.
- Migration Guide — Upgrade paths between releases.
- Theming Architecture — Detailed guide on RootCSS, single-winner slot, theme overrides, and type safety constraints.
- Go-Typed CSS DSL Justification — Deep-dive on design rationale, technical constraints, and comparison with alternatives.