Documentation
¶
Index ¶
- Variables
- func FadeStatic(t Token, transparentPct float64) string
- func Focus(t Token) string
- func FocusStatic(t Token) string
- func FontStack(family font.Family) string
- func Hover(t Token) string
- func HoverStatic(t Token) string
- func Press(t Token) string
- func PressStatic(t Token) string
- func Query(devices ...Device) string
- func Raw(css string) item
- type Capability
- type Device
- type NamedPair
- type Override
- type Pair
- type RawItem
- type Stylesheet
- type Token
- type ValueGetter
Constants ¶
This section is empty.
Variables ¶
var ( ColorPrimary = Token{Name: "--color-primary", Dark: "#1b5d8c"} ColorOnPrimary = Token{Name: "--color-on-primary", Dark: "#FFFFFF"} ColorSuccess = Token{Name: "--color-success", Dark: "#1e7a30"} ColorOnSuccess = Token{Name: "--color-on-success", Dark: "#FFFFFF"} ColorDanger = Token{Name: "--color-danger", Dark: "#ba2c0d"} ColorOnDanger = Token{Name: "--color-on-danger", Dark: "#FFFFFF"} ColorAccent = Token{Name: "--color-accent", Dark: "#e8a33d"} ColorOnAccent = Token{Name: "--color-on-accent", Dark: "#1C1C1E"} ColorBackground = Token{Name: "--color-background", Light: "#FFFFFF", Dark: "#0D1117"} ColorOnBackground = Token{Name: "--color-on-background", Light: "#1C1C1E", Dark: "#E6EDF3"} ColorSurface = Token{Name: "--color-surface", Light: "#F2F2F7", Dark: "#161B22"} ColorOnSurface = Token{Name: "--color-on-surface", Light: "#1C1C1E", Dark: "#E6EDF3"} ColorOutline = Token{Name: "--color-outline", Light: "#D1D1D6", Dark: "#30363D"} ColorMuted = Token{Name: "--color-muted", Light: "#6E6E73", Dark: "#8B949E"} // LightStatic: these three hold live var()/color-mix() expressions a // browser without color-mix() (Safari < 16.2) can't evaluate, so — unlike // every plain-hex token above, whose LightValue() derives automatically — // they need a precomputed static fallback. Computed once, from the // catalog's own default Light values: an app that overrides ColorSurface/ // ColorPrimary via Theme(Set(...)) does not retroactively change this // literal — an accepted gap for the legacy-browser tier only. // // Dark is built from the constituent tokens' NestedEnhanced(), NOT // EnhancedVar() and NOT a raw "var(--color-surface)" string: this Dark // string becomes an ARGUMENT inside the outer color-mix() here, and // NestedEnhanced() is the one guaranteed to contain no var() anywhere — // see its doc comment in tokens.go for why even a var() to an // always-safe property poisons an outer color-mix()/light-dark() that a // legacy browser can't parse. ColorSurfaceSunken = Token{Name: "--color-surface-sunken", Dark: "color-mix(in oklab, " + ColorSurface.NestedEnhanced() + ", " + ColorOnSurface.NestedEnhanced() + " 8%)", LightStatic: staticMix(ColorSurface.Light, ColorOnSurface.Light, 0.08)} ColorSelection = Token{Name: "--color-selection", Dark: "color-mix(in oklab, " + ColorPrimary.NestedEnhanced() + ", transparent 85%)", LightStatic: FadeStatic(ColorPrimary, 0.85)} ColorOnSelection = Token{Name: "--color-on-selection", Dark: ColorOnSurface.NestedEnhanced(), LightStatic: ColorOnSurface.Light} // ColorAccentWash is Accent faded toward transparency, the same // construction as ColorSelection but off ColorAccent instead of // ColorPrimary: a light amber tint for a hover/preview state that must // read as "leans toward the accent color" without claiming the solid // Accent fill, which is reserved for an actual committed state (e.g. // selection). ColorAccentWash = Token{Name: "--color-accent-wash", Dark: "color-mix(in oklab, " + ColorAccent.NestedEnhanced() + ", transparent 85%)", LightStatic: FadeStatic(ColorAccent, 0.85)} // ColorAccentHover is Accent faded only 30% toward transparency — a // visibly softer amber than the fully committed Accent fill, but close // enough in strength that ColorOnPrimary (white) still reads on it. A // pairing like ColorAccentWash (85% faded, nearly the page background // already) cannot carry a white icon at any faded strength that low; // this token exists for callers that need "clearly not yet committed" // AND "white icon" at once, which the 85% wash cannot deliver. ColorAccentHover = Token{Name: "--color-accent-hover", Dark: "color-mix(in oklab, " + ColorAccent.NestedEnhanced() + ", transparent 30%)", LightStatic: FadeStatic(ColorAccent, 0.30)} FontSans = Token{Name: "--font-sans", Dark: `"Roboto", system-ui, -apple-system, sans-serif`} TextXs = Token{Name: "--text-xs", Dark: "0.75rem"} TextSm = Token{Name: "--text-sm", Dark: "0.875rem"} TextBase = Token{Name: "--text-base", Dark: "1rem"} TextLg = Token{Name: "--text-lg", Dark: "1.25rem"} TextXl = Token{Name: "--text-xl", Dark: "1.5rem"} Text2xl = Token{Name: "--text-2xl", Dark: "2rem"} LeadingNormal = Token{Name: "--leading-normal", Dark: "1.5"} FontWeightRegular = Token{Name: "--font-weight-regular", Dark: "400"} FontWeightMedium = Token{Name: "--font-weight-medium", Dark: "500"} FontWeightBold = Token{Name: "--font-weight-bold", Dark: "700"} Space0 = Token{Name: "--space-0", Dark: "0"} Space1 = Token{Name: "--space-1", Dark: "0.25rem"} Space2 = Token{Name: "--space-2", Dark: "0.5rem"} Space3 = Token{Name: "--space-3", Dark: "0.75rem"} Space4 = Token{Name: "--space-4", Dark: "1rem"} Space6 = Token{Name: "--space-6", Dark: "1.5rem"} Space8 = Token{Name: "--space-8", Dark: "2rem"} Space12 = Token{Name: "--space-12", Dark: "3rem"} RadiusSm = Token{Name: "--radius-sm", Dark: "4px"} RadiusMd = Token{Name: "--radius-md", Dark: "8px"} RadiusLg = Token{Name: "--radius-lg", Dark: "16px"} RadiusFull = Token{Name: "--radius-full", Dark: "9999px"} ShadowSm = Token{Name: "--shadow-sm", Dark: "0 1px 2px rgba(0, 0, 0, 0.05)"} ShadowMd = Token{Name: "--shadow-md", Dark: "0 4px 6px rgba(0, 0, 0, 0.10)"} ShadowLg = Token{Name: "--shadow-lg", Dark: "0 10px 15px rgba(0, 0, 0, 0.10)"} DurationFast = Token{Name: "--duration-fast", Dark: "150ms"} DurationBase = Token{Name: "--duration-base", Dark: "250ms"} DurationSlow = Token{Name: "--duration-slow", Dark: "400ms"} EaseInOut = Token{Name: "--ease-in-out", Dark: "cubic-bezier(0.4, 0, 0.2, 1)"} ZBase = Token{Name: "--z-base", Dark: "0"} ZDropdown = Token{Name: "--z-dropdown", Dark: "100"} ZSticky = Token{Name: "--z-sticky", Dark: "200"} ZModal = Token{Name: "--z-modal", Dark: "300"} ZToast = Token{Name: "--z-toast", Dark: "400"} ZTooltip = Token{Name: "--z-tooltip", Dark: "500"} BpSm = Token{Name: "--bp-sm", Dark: "640px"} BpMd = Token{Name: "--bp-md", Dark: "768px"} BpLg = Token{Name: "--bp-lg", Dark: "1024px"} BpXl = Token{Name: "--bp-xl", Dark: "1280px"} MaxWReadable = Token{Name: "--max-w-readable", Dark: "65ch"} ColumnNarrow = Token{Name: "--column-narrow", Dark: "12rem"} ColumnMedium = Token{Name: "--column-medium", Dark: "20rem"} ColumnWide = Token{Name: "--column-wide", Dark: "30rem"} // How far a veil blurs what is behind it. A dimmed wash alone still lets // the page compete for attention; softening it is what makes a dialog read // as the only thing in focus. // 4px is the common step for this in modern UI — Tailwind's backdrop-blur-sm, // and roughly where design systems land when they blur a scrim at all. // Past ~8px the page stops reading as "behind" and starts reading as // "broken", and it compounds with the 60% wash the veil already applies. VeilBlur = Token{Name: "--veil-blur", Dark: "4px"} // The width every chip shares — a field's legend, a row's badge — so a // column of them lines up instead of each one hugging its own text. ChipWidth = Token{Name: "--chip-width", Dark: "7rem"} // The height every chip shares — a field's legend, a row's badge — so a // chip is a box of KNOWN size instead of an emergent one. Without this the // height comes from font-size × line-height and two chips only match by // accident; with it, OnEdge can mount a chip over a border line with real // margins instead of a transform, which is invisible to scroll-height // measurement and reserves no layout space. ChipHeight = Token{Name: "--chip-height", Dark: "1.25rem"} // The height every interactive row shares — a list row, a form field — // so the two read as the same rhythm instead of drifting apart. ControlHeight = Token{Name: "--control-height", Dark: "3.125rem"} // Rail widths — the fixed column of a Sidebar layout. RailNarrow = Token{Name: "--rail-narrow", Dark: "3.5rem"} RailWide = Token{Name: "--rail-wide", Dark: "12rem"} MixHover = Token{Name: "--mix-hover", Dark: "15%"} MixFocus = Token{Name: "--mix-focus", Dark: "30%"} MixPress = Token{Name: "--mix-press", Dark: "45%"} // Device geometry — insets reported by the device, and the viewport // height that shrinks with Safari iOS' collapsing URL bar. SafeTop = Token{Name: "--safe-top", Dark: "env(safe-area-inset-top, 0px)"} SafeRight = Token{Name: "--safe-right", Dark: "env(safe-area-inset-right, 0px)"} SafeBottom = Token{Name: "--safe-bottom", Dark: "env(safe-area-inset-bottom, 0px)"} SafeLeft = Token{Name: "--safe-left", Dark: "env(safe-area-inset-left, 0px)"} ViewportH = Token{Name: "--viewport-h", Dark: "100dvh"} )
var ( SurfacePrimary = Pair{ColorPrimary, ColorOnPrimary} SurfacePanel = Pair{ColorSurface, ColorOnSurface} SurfaceBackground = Pair{ColorBackground, ColorOnBackground} SurfaceSunken = Pair{ColorSurfaceSunken, ColorOnSurface} SurfaceSelected = Pair{ColorSelection, ColorOnSelection} SurfaceDanger = Pair{ColorDanger, ColorOnDanger} SurfaceAccent = Pair{ColorAccent, ColorOnAccent} SurfaceSuccess = Pair{ColorSuccess, ColorOnSuccess} )
Functions ¶
func FadeStatic ¶ added in v0.4.8
FadeStatic is the static counterpart of the color-mix(in <space>, TOKEN P%, transparent) pattern used for a token faded toward transparency (e.g. ColorSelection, or a veil/backdrop wash): t's LightValue faded toward transparent by transparentPct (0.0-1.0 — the weight transparent gets).
func FocusStatic ¶ added in v0.4.8
func FontStack ¶ added in v0.4.4
FontStack returns a CSS font family stack starting with the given font family.
func Hover ¶ added in v0.2.0
Hover, Focus and Press return the standard interaction-state derivation for any base token: the base mixed toward the theme's contrasting extreme. The mixer is light-dark(black, white) so a hover darkens on a light theme and lightens on a dark one. The intensity is a token, so an app can retune it with Theme(Set(MixHover, "22%")) without republishing this package.
func HoverStatic ¶ added in v0.4.8
HoverStatic, FocusStatic and PressStatic are the browser-safe counterparts of Hover/Focus/Press: t's LightValue mixed toward black by the same intensity, computed once in Go instead of once per paint in the browser. Callers emit this as the first of a double declaration — see tinywasm/widget/style — so a browser without color-mix() support (Safari < 16.2) keeps it, permanently in the light theme, instead of an invalid declaration.
func PressStatic ¶ added in v0.4.8
func Query ¶ added in v0.4.0
Query joins several device classes into one condition list. Callers that mean "tablet and desktop" pass both rather than emitting two blocks. Duplicate and unknown values are dropped; the result is ordered Mobile, Tablet, Desktop regardless of argument order, so emission is deterministic.
Types ¶
type Capability ¶ added in v0.4.7
type Capability uint8
Capability is a media condition that is not a viewport class. Unlike Device, it is not a partition of widths: a device satisfies it or not regardless of its size, and several capabilities can hold at once.
const ( // FinePointer is the hover capability: the primary input can hover. A // touch tap fires `:hover` and synthetic mouse events, so CSS hover // reveals must be gated on this query — `@media (hover: hover)` — or they // misfire on phones. FinePointer Capability = iota )
func (Capability) Query ¶ added in v0.4.7
func (c Capability) Query() string
Query returns the media-query condition for this capability, without the leading "@media ".
type Device ¶ added in v0.4.0
type Device uint8
Device is the closed set of viewport classes. It exists because a media query condition cannot read a custom property: the pixel thresholds must be baked into the query string, and this is the only file in the ecosystem allowed to hold them.
The three classes are mutually exclusive and jointly exhaustive: every viewport width matches exactly one. That property is asserted by TestDeviceClassesPartition.
type NamedPair ¶ added in v0.3.1
type NamedPair struct {
Name string
Bg ValueGetter
Fg ValueGetter
Min float64
}
NamedPair is a descriptor used for contrast compliance verification.
func AllPairs ¶ added in v0.3.1
func AllPairs() []NamedPair
AllPairs returns the 6 active functional design decision pairs for automated contrast auditing. SurfaceSunken and SurfaceSelected are excluded (remaining 2 of the 8 total pairs): their values are color-mix() expressions that resolveColor() cannot evaluate. Evaluating color-mix(in oklab, ...) is a known gap, pending resolution in github.com/tinywasm/color (see color/docs/PLAN.md §3.3).
type Override ¶ added in v0.1.3
type Override struct {
// contains filtered or unexported fields
}
Override is the customized override of a single Token's value.
type Pair ¶ added in v0.2.0
type Pair struct{ Bg, Fg Token }
Pair represents a complete surface decision: background and foreground colors coupled.
type Stylesheet ¶ added in v0.0.2
type Stylesheet struct {
// contains filtered or unexported fields
}
func FontFaces ¶ added in v0.4.5
func FontFaces(d font.Declaration, urlPrefix string) *Stylesheet
FontFaces returns the @font-face block for the four faces of the declared family, served from urlPrefix (e.g. "/assets"). The prefix is caller data: this package neither knows nor invents server paths.
Not part of RootCSS() or RenderCSS(): whoever serves the files decides when to inject it.
func NewStylesheet ¶ added in v0.1.0
func NewStylesheet(items ...item) *Stylesheet
func RenderCSS ¶ added in v0.0.2
func RenderCSS() *Stylesheet
RenderCSS is the base reset, emitted in the `tokens` layer — the lowest of the four the widget style DSL declares. Unlayered, `svg { display: block }` would beat `@layer widgets { .part { display: none } }` regardless of specificity, so any component hiding an icon by state could not.
func RootCSS ¶ added in v0.0.2
func RootCSS() *Stylesheet
func Theme ¶ added in v0.1.3
func Theme(overrides ...Override) *Stylesheet
Theme returns the entire RootCSS() catalog with custom overrides appended.
func (*Stylesheet) String ¶ added in v0.0.2
func (s *Stylesheet) String() string
type Token ¶ added in v0.0.2
type Token struct {
Name string
Light, Dark string // Dark solo = static fallback; ambos = light-dark pair
// LightStatic overrides LightValue() for a token whose Dark (or Light)
// holds a live expression — color-mix(...) or var(--other-token) — that a
// browser without color-mix()/light-dark() support cannot evaluate.
// Leave empty for every plain-hex token: LightValue() derives it
// automatically from Light (or Dark). Only the handful of composed color
// tokens in catalog.go need to set this, precomputed via color.Mix at
// package-init time.
LightStatic string
}
Token is a design token: a named visual decision with a fallback value. For static tokens, Dark holds the literal fallback and Light is empty. For theme tokens, Light and Dark hold the light/dark pair and Var/GetFallback generate light-dark(Light, Dark) automatically.
func (Token) DarkVarName ¶ added in v0.4.8
func (Token) EnhancedVar ¶ added in v0.4.8
EnhancedVar returns the value used as the SECOND half of a double declaration when the token is the declaration's ENTIRE top-level value (e.g. `background-color: <this>;`) — see NestedEnhanced for the alternative required when it is instead one ARGUMENT inside another light-dark()/color-mix() call.
For a theme token this must never be Var(): Var() wraps the value in var(t.Name, ...), and t.Name's OWN registered :root value (see declare() in dsl.go) is exactly the light-dark()/color-mix() expression a legacy browser cannot compute. Reaching it through var() makes the declaration invalid at COMPUTED-value time — which, per the CSS Custom Properties cascade, does NOT fall back to an earlier sibling declaration for the same property; it falls to the property's initial value instead (transparent, for a color), silently discarding whatever static declaration a caller put before it. So this is 100% literal — baked from the catalog's default Light/Dark at Go build time, with NO var() anywhere in it, so the unsupported function is the first thing a legacy browser's parser sees, invalid at PARSE time — which DOES correctly leave an earlier sibling declaration standing. A real, accepted cost: an app's Theme(Set(...))/SetTheme() therefore does not reach this specific declaration (it still reaches Var() and the -light/-dark properties above, used everywhere else in this package).
For a static token there is nothing to protect — Var() was never wrapped in light-dark()/color-mix(), so it stays live and override-able.
func (Token) GetFallback ¶ added in v0.3.1
GetFallback returns the default/fallback value of the token.
func (Token) LightValue ¶ added in v0.4.8
LightValue returns the token's browser-safe static value: no light-dark() or color-mix() involved, ever. A browser that can't evaluate those (Safari < 17.5 / < 16.2 — iOS 15-era Safari on older hardware, among others) gets this value permanently, regardless of system or app theme — the light theme, never dark. Callers emit it as the first of a double declaration, immediately followed by EnhancedVar(), never Var() — see EnhancedVar for why that distinction is load-bearing.
func (Token) LightVarName ¶ added in v0.4.8
LightVarName and DarkVarName are the custom-property names of a theme token's plain, browser-safe halves — declared separately from Name itself (see declareSplit in dsl.go). Not used by EnhancedVar/NestedEnhanced below (see their comments for why a var() reference, even to one of these always-valid halves, is not safe inside a light-dark()/color-mix() call); they exist as public custom properties for an app's OWN CSS to build the same double-declaration pattern against. Only meaningful for a theme token (Light != "") — a static token has no light/dark split to name.
func (Token) NestedEnhanced ¶ added in v0.4.8
NestedEnhanced is EnhancedVar's counterpart for a token used as one ARGUMENT inside another light-dark()/color-mix() call (see catalog.go's composed tokens, and css.go's mixToward) rather than as a declaration's own top-level value.
The distinction matters because the "any var() anywhere defers validity to computed-value time" rule (see EnhancedVar) applies to the OUTER declaration as a whole, not just to the specific sub-expression that contains it: a static token's Var() is completely safe used on its own (EnhancedVar returns it, correctly, for exactly that reason) but the moment that SAME var() call sits inside an outer color-mix()/light-dark() that a legacy browser cannot parse, its mere presence defers the ENTIRE containing declaration to computed-value time too — even though the nested var() would itself have resolved just fine. That silently turns the outer function's parse-time failure into the same falls-to-initial-value bug EnhancedVar exists to avoid. NestedEnhanced closes that gap by never using var(), for any token kind.
type ValueGetter ¶ added in v0.3.1
ValueGetter defines the interface for retrieving token metadata.