css

package module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 28, 2026 License: MIT Imports: 1 Imported by: 0

README

tinywasm/css

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.

Public API & Architecture

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:

  • Token and Pair design tokens.
  • The design token catalog (e.g., ColorPrimary, ColorSurface, Space2, etc.).
  • Class (type alias to widget.Class).
  • Stylesheet and NewStylesheet for compilation.
  • Theme for 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.

Component Styling Example

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))
}

SSR contract: RootCSS vs RenderCSS

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.

Theming an App (Rebrand)

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.Set(css.ColorSecondary, "#3F88BF"),
        css.Set(css.ColorBackgroundLight, "#FAFAFA"),
        css.Set(css.ColorBackgroundDark, "#121212"),
    )
}

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 light background css.Set(css.ColorBackgroundLight, "#hex")
Adjust global border-radius css.Set(css.RadiusMd, "12px")
Adjust typographic scale css.Set(css.TextBase, "1.1rem")

Design Tokens

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, SurfaceSelected)
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
Breakpoints Viewport widths (container queries / JS)
Container widths Max-width primitives

Design Philosophy

  • Semantic names over valuesColorOnSurface not #ffffff. Names describe intent; values can change.
  • Contrast safety by type — Background/foreground design decisions are coupled in Pair structures. 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.

See docs/ARCHITECTURE.md for more details on the theming system.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// Brand colors
	ColorPrimary     = Token{"--color-primary", "#1E6B9E"}
	ColorOnPrimary   = Token{"--color-on-primary", "#FFFFFF"}
	ColorSecondary   = Token{"--color-secondary", "#654FF0"}
	ColorOnSecondary = Token{"--color-on-secondary", "#FFFFFF"}
	ColorSuccess     = Token{"--color-success", "#1F7A31"}
	ColorOnSuccess   = Token{"--color-on-success", "#FFFFFF"}
	ColorError       = Token{"--color-error", "#D12200"}
	ColorOnError     = Token{"--color-on-error", "#FFFFFF"}

	// Theme — active layer (consumed by components)
	ColorBackground    = Token{"--color-background", "#FFFFFF"}
	ColorSurface       = Token{"--color-surface", "#F2F2F7"}
	ColorSurfaceSunken = Token{"--color-surface-sunken", "#E5E5EA"}
	ColorOnSurface     = Token{"--color-on-surface", "#1C1C1E"}
	ColorOutline       = Token{"--color-outline", "#D1D1D6"}
	ColorMuted         = Token{"--color-muted", "#6E6E73"}
	ColorHover         = Token{"--color-hover", "#B8860B"}
	ColorSelection     = Token{"--color-selection", "#f5a623"}    // selected/active highlight
	ColorOnSelection   = Token{"--color-on-selection", "#1C1C1E"} // text on a selected row
	ColorDisabled      = Token{"--color-disabled", "#E5E5EA"}
	ColorOnDisabled    = Token{"--color-on-disabled", "#666666"}

	// Theme — source layer (apps redeclare these for rebrand)
	ColorBackgroundLight    = Token{"--color-background-light", "#FFFFFF"}
	ColorBackgroundDark     = Token{"--color-background-dark", "#0D1117"}
	ColorSurfaceLight       = Token{"--color-surface-light", "#F2F2F7"}
	ColorSurfaceDark        = Token{"--color-surface-dark", "#161B22"}
	ColorSurfaceSunkenLight = Token{"--color-surface-sunken-light", "#E5E5EA"}
	ColorSurfaceSunkenDark  = Token{"--color-surface-sunken-dark", "#21262D"}
	ColorOnSurfaceLight     = Token{"--color-on-surface-light", "#1C1C1E"}
	ColorOnSurfaceDark      = Token{"--color-on-surface-dark", "#E6EDF3"}
	ColorOutlineLight       = Token{"--color-outline-light", "#D1D1D6"}
	ColorOutlineDark        = Token{"--color-outline-dark", "#30363D"}
	ColorMutedLight         = Token{"--color-muted-light", "#6E6E73"}
	ColorMutedDark          = Token{"--color-muted-dark", "#8B949E"}
	ColorHoverLight         = Token{"--color-hover-light", "#B8860B"}
	ColorHoverDark          = Token{"--color-hover-dark", "#F7DF1E"}
	ColorSelectionLight     = Token{"--color-selection-light", "#f5a623"}
	ColorSelectionDark      = Token{"--color-selection-dark", "#9e6a2e"}
	ColorOnSelectionLight   = Token{"--color-on-selection-light", "#1C1C1E"}
	ColorOnSelectionDark    = Token{"--color-on-selection-dark", "#FFFFFF"}
	ColorDisabledLight      = Token{"--color-disabled-light", "#E5E5EA"}
	ColorDisabledDark       = Token{"--color-disabled-dark", "#21262D"}
	ColorOnDisabledLight    = Token{"--color-on-disabled-light", "#666666"}
	ColorOnDisabledDark     = Token{"--color-on-disabled-dark", "#8B949E"}

	// Typography size scale
	TextXs   = Token{"--text-xs", "0.75rem"}
	TextSm   = Token{"--text-sm", "0.875rem"}
	TextBase = Token{"--text-base", "1rem"}
	TextLg   = Token{"--text-lg", "1.25rem"}
	TextXl   = Token{"--text-xl", "1.5rem"}
	Text2xl  = Token{"--text-2xl", "2rem"}

	// Line-height / weight / tracking
	LeadingTight      = Token{"--leading-tight", "1.25"}
	LeadingNormal     = Token{"--leading-normal", "1.5"}
	LeadingRelaxed    = Token{"--leading-relaxed", "1.75"}
	FontWeightRegular = Token{"--font-weight-regular", "400"}
	FontWeightMedium  = Token{"--font-weight-medium", "500"}
	FontWeightBold    = Token{"--font-weight-bold", "700"}
	TrackingTight     = Token{"--tracking-tight", "-0.02em"}
	TrackingNormal    = Token{"--tracking-normal", "0"}
	TrackingWide      = Token{"--tracking-wide", "0.05em"}

	// Spacing (4px grid)
	Space0  = Token{"--space-0", "0"}
	Space1  = Token{"--space-1", "0.25rem"}
	Space2  = Token{"--space-2", "0.5rem"}
	Space3  = Token{"--space-3", "0.75rem"}
	Space4  = Token{"--space-4", "1rem"}
	Space6  = Token{"--space-6", "1.5rem"}
	Space8  = Token{"--space-8", "2rem"}
	Space12 = Token{"--space-12", "3rem"}

	// Border radius
	RadiusSm   = Token{"--radius-sm", "4px"}
	RadiusMd   = Token{"--radius-md", "8px"}
	RadiusLg   = Token{"--radius-lg", "16px"}
	RadiusFull = Token{"--radius-full", "9999px"}

	// Elevation
	ShadowSm = Token{"--shadow-sm", "0 1px 2px rgba(0,0,0,0.05)"}
	ShadowMd = Token{"--shadow-md", "0 4px 6px rgba(0,0,0,0.1)"}
	ShadowLg = Token{"--shadow-lg", "0 10px 15px rgba(0,0,0,0.1)"}
	ShadowXl = Token{"--shadow-xl", "0 20px 25px rgba(0,0,0,0.15)"}

	// Motion
	DurationFast = Token{"--duration-fast", "150ms"}
	DurationBase = Token{"--duration-base", "250ms"}
	DurationSlow = Token{"--duration-slow", "400ms"}
	EaseIn       = Token{"--ease-in", "cubic-bezier(0.4,0,1,1)"}
	EaseOut      = Token{"--ease-out", "cubic-bezier(0,0,0.2,1)"}
	EaseInOut    = Token{"--ease-in-out", "cubic-bezier(0.4,0,0.2,1)"}

	// Z-index
	ZBase     = Token{"--z-base", "0"}
	ZDropdown = Token{"--z-dropdown", "100"}
	ZSticky   = Token{"--z-sticky", "200"}
	ZModal    = Token{"--z-modal", "300"}
	ZToast    = Token{"--z-toast", "400"}
	ZTooltip  = Token{"--z-tooltip", "500"}

	// Breakpoints
	BpSm = Token{"--bp-sm", "640px"}
	BpMd = Token{"--bp-md", "768px"}
	BpLg = Token{"--bp-lg", "1024px"}
	BpXl = Token{"--bp-xl", "1280px"}

	// Container widths
	MaxWProse   = Token{"--max-w-prose", "65ch"}
	MaxWContent = Token{"--max-w-content", "1200px"}
	MaxWScreen  = Token{"--max-w-screen", "1440px"}
)

Token catalog — every token from the legacy theme.css.

View Source
var (
	SurfacePrimary  = Pair{ColorPrimary, ColorOnPrimary}
	SurfacePanel    = Pair{ColorSurface, ColorOnSurface}
	SurfaceSunken   = Pair{ColorSurfaceSunken, ColorOnSurface}
	SurfaceSelected = Pair{ColorSelection, ColorOnSelection}
	SurfaceDanger   = Pair{ColorError, ColorOnError}
	SurfaceSuccess  = Pair{ColorSuccess, ColorOnSuccess}
	SurfaceDisabled = Pair{ColorDisabled, ColorOnDisabled}
)

Functions

func Disabled added in v0.2.0

func Disabled(c Class) selector

func Focus added in v0.2.0

func Focus(c Class) selector

func Hover added in v0.2.0

func Hover(c Class) selector

Pseudo-class helpers

func Raw added in v0.0.2

func Raw(css string) item

Types

type Class added in v0.0.2

type Class string

Class is a CSS class name. Shared by HTML emission (WASM) and CSS emission (SSR). Only the string identity crosses the WASM boundary; pseudo-class helpers (Hover/Focus/Disabled) live in dsl.go (!wasm) because they only feed Rule().

css owns this type: it is the shared vocabulary for a CSS class identifier, and any package that builds names from a stricter contract (e.g. deriving them from a widget Name+Part) defines its own constructor and stores the result here — it does not own the identifier type itself.

func (Class) AsAttr added in v0.0.4

func (c Class) AsAttr() fmt.KeyValue

func (Class) String added in v0.0.2

func (c Class) String() string

type Override added in v0.1.3

type Override struct {
	// contains filtered or unexported fields
}

Override es el cambio de valor de UN token. Campos no exportados: solo Set lo construye.

func Set added in v0.1.3

func Set(t Token, value string) Override

Set declara el override de un token del catálogo. Token tipado (no un nombre libre); value es el borde de I/O.

type Pair added in v0.2.0

type Pair struct{ Bg, Fg Token }

Pair represents a complete surface decision: background and foreground colors coupled. A background is never declared without its foreground, preventing the type of bug where a text color is accidentally used as a panel background.

type RawItem added in v0.0.2

type RawItem string

type Stylesheet added in v0.0.2

type Stylesheet struct {
	// contains filtered or unexported fields
}

func NewStylesheet added in v0.1.0

func NewStylesheet(items ...item) *Stylesheet

func RenderCSS added in v0.0.2

func RenderCSS() *Stylesheet

func RootCSS added in v0.0.2

func RootCSS() *Stylesheet

func Theme added in v0.1.3

func Theme(overrides ...Override) *Stylesheet

Theme devuelve el catálogo :root COMPLETO (como RootCSS) con los overrides al final. Pensado como el RootCSS() del proyecto raíz — assetmin REEMPLAZA el :root de css por el de la app, por eso trae el catálogo entero, no solo los overrides.

func (*Stylesheet) String added in v0.0.2

func (s *Stylesheet) String() string

type Token added in v0.0.2

type Token struct{ Name, Fallback string }

Token is a design token: a named visual decision with a fallback value. Industry-standard term (W3C Design Tokens CG, Material, Carbon, Primer, Spectrum).

func (Token) Var added in v0.0.2

func (t Token) Var() string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL