@pyreon/styler — API Reference
Generated from
styler'ssrc/manifest.ts— the same source that powersllms.txtand MCPget_api. Do not edit this page by hand; edit the manifest. For the conceptual guide, see styler.
Pyreon's CSS-in-JS engine. styled('div') is a tagged template that returns a ComponentFn injecting a generated class; css is a tagged template returning a LAZY CSSResult resolved on use (not a string); keyframes returns the generated animation-name string. Tagged-template interpolations receive the component's props (and the theme) so styles can be signal-driven — function interpolations flip the component onto the dynamic resolve path (isDynamic). A singleton StyleSheet with FNV-1a hashing dedupes and supports SSR; createSheet() makes an isolated instance. Theme is delivered through a REACTIVE context — useTheme() snapshots at call time, useThemeAccessor() returns the raw () => Theme accessor for tracking inside effects so whole-theme swaps re-resolve without remounting.
Features
styled('div')
.../ styled(Component).../ styled.div...(Proxy) — component factory withaspolymorphism + $-transient propscss
...— lazy CSSResult, resolved on use (NOT a string)keyframes
...— returns the generated @keyframes animation-name stringcreateGlobalStyle
...— returns a ComponentFn that injects global CSS when mounteduseCSS(template, props?, boost?) — resolve a CSSResult to a class name inside a component
Reactive theming — useTheme() snapshot vs useThemeAccessor() accessor; ThemeProvider merges nested
Singleton StyleSheet (FNV-1a dedup, SSR) + createSheet() for isolated instances
buildProps / filterProps — $-transient + shouldForwardProp DOM prop forwarding (descriptor-preserving)
Exports
| Symbol | Kind | Summary |
|---|---|---|
styled | function | Component factory. |
css | function | Tagged-template that returns a LAZY CSSResult — it is NOT a class name or a CSS string until resolved by styled(), ` |
keyframes | function | Tagged-template returning a KeyframesResult whose string form is the GENERATED, content-hashed @keyframes animation |
createGlobalStyle | function | Returns a ComponentFn that injects GLOBAL CSS (resets, :root tokens, body styles) when MOUNTED — it is not a side-ef |
useCSS | hook | Resolves a CSSResult (from the css tagged template) to an injected class-name string inside a component. |
useTheme | hook | Returns the current theme as a SNAPSHOT at call time. |
useThemeAccessor | hook | Returns the raw () => T theme accessor (not a snapshot). |
ThemeProvider | component | Provides a theme to the reactive ThemeContext. |
ThemeContext | constant | The reactive context backing the theme. |
createSheet | function | Creates an ISOLATED StyleSheet instance (its own FNV-1a dedup cache + rule registry) instead of the shared singleton ` |
StyleSheet | class | The CSS injection engine: FNV-1a content hashing, a dedup cache (identical CSS → one rule), and SSR support (collect rul |
sheet | constant | The process-wide singleton StyleSheet that styled() / css / keyframes / createGlobalStyle inject into by defau |
resolve | function | Low-level: resolve a tagged-template (strings + interpolations) against a props object into a final CSS string (functi |
normalizeCSS | function | Normalizes a raw CSS string (whitespace/format canonicalization) so identical-intent CSS hashes to the same FNV-1a key a |
resolveValue | function | Resolves a SINGLE interpolation against props: invokes function interpolations with props, flattens nested `CSSResul |
clearNormCache | function | Clears the normalizeCSS memo cache. |
buildProps | function | Builds the final prop object forwarded to the rendered element: merges the generated class, drops $-transient props, a |
filterProps | function | Returns a copy of props with $-transient and known non-DOM props removed — the DOM-safety filter buildProps applie |
isDynamic | function | True when an interpolation is a function (signal accessor / props reader) — i.e. |
hash / hashUpdate / hashFinalize / HASH_INIT | function | The FNV-1a non-cryptographic hash styler uses for compact, deduped class names + rule keys. |
setStyleExtraction | function | Internal dependency-injection seam for Custom-Property Style Extraction (CPSE). |
API
styled function
styled: ((tag: Tag, options?: StyledOptions) => TagTemplateFn) & { div: TagTemplateFn; span: TagTemplateFn; /* …all HTML tags via Proxy */ }Component factory. styled('div'), styled(MyComp), and styled.div (Proxy sugar) are all tagged templates returning a ComponentFn that injects a generated class. Tagged-template interpolations are called with the live props object (theme included), so a function interpolation reading p.theme.color / signal-driven values works and puts the component on the dynamic resolve path. Supports the polymorphic as prop and $-prefixed TRANSIENT props (consumed by styles, NOT forwarded to the DOM). Per-definition caching keys generated classes so repeat mounts skip re-resolution.
Example
import { styled } from "@pyreon/styler"
const Button = styled("button")`
background: ${(p) => p.theme.colors.primary};
padding: ${(p) => (p.$compact ? "4px" : "12px")};
`
// <Button $compact onClick={...}>Go</Button> — $compact not forwarded to <button>Common mistakes
Expecting
$-prefixed props to reach the DOM — they are transient by design (consumed by the template, stripped before forwarding). Use a non-$name if the attribute must land on the elementDestructuring
propsin the interpolation (${({ theme }) => …}) and being surprised it does not update on a whole-theme swap — readprops.themelazily; the theme context is reactive and the styled resolver re-runs on swapPassing a resolved value where a function interpolation is needed for reactivity —
${signal()}snapshots once at definition; use${() => signal()}(or${(p) => p.x}) to stay on the dynamic pathUsing
styled.divand expecting a different identity per call — the Proxy returns the same tag template fn shape; per-definition caches key on the template, not the call site
See also: css · useCSS · useTheme
css function
css(strings: TemplateStringsArray, ...values: Interpolation[]): CSSResultTagged-template that returns a LAZY CSSResult — it is NOT a class name or a CSS string until resolved by styled(), useCSS(), or composition into another template. Compose reusable fragments with it (assign a css result to const base, then interpolate base inside a styled template). Resolution is deferred so it can read the props/theme of the consuming component at use time.
Example
import { css, useCSS } from "@pyreon/styler"
const card = css`border: 1px solid #ddd; padding: 16px;`
function Card(props) {
const cls = useCSS(card)
return <div class={cls}>{props.children}</div>
}Common mistakes
Treating the
csstagged-template return value as a string / class name — it is a lazyCSSResult; interpolating it into text (e.g.class={card}) renders[object Object]. Resolve viauseCSSor embed in astyledtemplateReading props/theme at
csscall time — the template is resolved later; put dynamic bits in function interpolations so they read the LIVE props at use
See also: styled · useCSS · keyframes
keyframes function
keyframes(strings: TemplateStringsArray, ...values: Interpolation[]): KeyframesResultTagged-template returning a KeyframesResult whose string form is the GENERATED, content-hashed @keyframes animation NAME. Reference it inside a css / styled template as the animation-name value; the @keyframes rule is injected (deduped via FNV-1a) on first use.
Example
import { keyframes, styled } from "@pyreon/styler"
const spin = keyframes`from { transform: rotate(0) } to { transform: rotate(360deg) }`
const Spinner = styled("div")`animation: ${spin} 1s linear infinite;`Common mistakes
Expecting a CSS class —
keyframesyields an animation-NAME token, used as theanimation/animation-namevalue, not a class applied to an elementDefining
keyframesinside the render body per mount — define once at module scope so the hashed rule is injected once and reused
See also: css · styled
createGlobalStyle function
createGlobalStyle(strings: TemplateStringsArray, ...values: Interpolation[]): ComponentFnReturns a ComponentFn that injects GLOBAL CSS (resets, :root tokens, body styles) when MOUNTED — it is not a side-effecting call. Render the returned component once near the app root. The injected rule PERSISTS for the document's lifetime, deduped by content hash — like emotion's injectGlobal, and UNLIKE styled-components' createGlobalStyle, it is NOT removed on unmount (a global reset shouldn't vanish when the mounting component re-renders away). Function interpolations make the global block dynamic (re-resolves on prop/theme change).
Example
import { createGlobalStyle } from "@pyreon/styler"
const GlobalReset = createGlobalStyle`
*, *::before, *::after { box-sizing: border-box }
body { margin: 0; font-family: ${(p) => p.theme.fonts.body}; }
`
// render <GlobalReset /> once at the app rootCommon mistakes
Calling
createGlobalStyle(the tagged template) and expecting the CSS to inject — nothing happens until the returned component is RENDERED. Mount<GlobalReset />once near the rootExpecting the global CSS to be removed when the component unmounts — it persists (deduped by hash), matching emotion
injectGlobalnot styled-components. Toggle globals with a class/attribute on:root, not by mounting/unmounting the component
See also: styled · css
useCSS hook
useCSS(template: CSSResult, props?: Record<string, any>, boost?: boolean): stringResolves a CSSResult (from the css tagged template) to an injected class-name string inside a component. Pass props so function interpolations in the template read live values; boost opts into a faster cache path for hot, stable templates. The returned class is deduped/hashed by the active StyleSheet.
Example
import { css, useCSS } from "@pyreon/styler"
const box = css`color: ${(p) => p.danger ? "red" : "inherit"};`
function Box(props) {
return <div class={useCSS(box, props)}>{props.children}</div>
}Common mistakes
Forgetting to pass
propswhen the template has function interpolations — they then resolve against an empty object and the dynamic values are lostCalling
useCSSoutside a component setup — it depends on the active sheet/theme context like any hook
See also: css · styled
useTheme hook
useTheme<T extends object = Theme>(): TReturns the current theme as a SNAPSHOT at call time. ThemeContext is a REACTIVE context — useTheme() reads it once, so the returned object is static unless the read happens inside a reactive scope. For values that must track whole-theme swaps inside an effect / computed, use useThemeAccessor() instead.
Example
import { useTheme } from "@pyreon/styler"
function Badge() {
const t = useTheme()
return <span style={{ color: t.colors.primary }}>{/* … */}</span>
}Common mistakes
Destructuring
const { colors } = useTheme()and expecting it to update on a user-preference theme swap — the snapshot is captured once. UseuseThemeAccessor()and read inside the reactive scope, or rely onstyledtemplates (their resolver tracks the theme)Calling
useTheme()at module scope — it must run during component setup where the context is available
See also: useThemeAccessor · ThemeProvider · styled
useThemeAccessor hook
useThemeAccessor<T extends object = Theme>(): () => TReturns the raw () => T theme accessor (not a snapshot). Call it inside an effect / computed / JSX thunk so the read TRACKS the reactive theme context — whole-theme swaps (user-preference themes) then re-run the consumer without a remount. This is the escape hatch styled() itself uses internally.
Example
import { useThemeAccessor } from "@pyreon/styler"
import { effect } from "@pyreon/reactivity"
const theme = useThemeAccessor()
effect(() => applyChartPalette(theme().colors)) // re-runs on theme swapCommon mistakes
Calling the accessor once at setup and caching the result — that defeats the point; call it INSIDE the reactive scope every time so the dependency is tracked
Reaching for this when a
styledtemplate would do — the template resolver already tracks the theme; use the accessor only for imperative/non-CSS theme reads
See also: useTheme · ThemeProvider
ThemeProvider component
ThemeProvider(props: { theme: Theme | ((parent: Theme) => Theme); children?: VNodeChild }): VNodeChildProvides a theme to the reactive ThemeContext. Nested providers compose — a function theme receives the parent theme so subtrees can extend rather than replace. Because the context is reactive, swapping the theme prop re-resolves every styled / useCSS consumer below without remounting the tree. Marked nativeCompat so it works inside @pyreon/{react,preact,vue,solid}-compat apps.
Example
import { ThemeProvider } from "@pyreon/styler"
<ThemeProvider theme={{ colors: { primary: "#06f" } }}>
<App />
</ThemeProvider>Common mistakes
Replacing the whole theme in a nested provider when you meant to extend — pass
theme={(parent) => ({ ...parent, colors: { ...parent.colors, accent: "#0a0" } })}Expecting most apps to mount this directly —
<PyreonUI>wraps it; useThemeProviderstandalone only outside the@pyreon/ui-coreprovider
See also: useTheme · useThemeAccessor · ThemeContext
ThemeContext constant
ThemeContext: ReactiveContext<Theme>The reactive context backing the theme. Created via createReactiveContext<Theme> — useContext(ThemeContext) returns a () => Theme accessor (which is what useTheme() / useThemeAccessor() wrap). Exposed for advanced consumers building their own theme-aware primitives; prefer the hooks for app code.
Example
import { ThemeContext } from "@pyreon/styler"
import { useContext } from "@pyreon/core"
const themeAccessor = useContext(ThemeContext) // () => ThemeCommon mistakes
Treating
useContext(ThemeContext)as the theme object — it is the ACCESSOR() => Theme(reactive context). Call it to read
See also: useTheme · useThemeAccessor · ThemeProvider
createSheet function
createSheet(options?: StyleSheetOptions): StyleSheetCreates an ISOLATED StyleSheet instance (its own FNV-1a dedup cache + rule registry) instead of the shared singleton sheet. Use for shadow-DOM roots, multi-window/iframe rendering, per-request SSR isolation, or test isolation where one request/realm must not share the global dedup cache. Options: maxCacheSize, layer (wrap scoped rules in an @layer), and nonce (CSP — stamps the SSR <style> from getStyleTag() and the client <style> element with a nonce so a strict style-src 'nonce-…' policy admits the critical CSS). Most apps never need this — the singleton is correct for a single document.
Example
import { createSheet } from "@pyreon/styler"
const shadowSheet = createSheet({ /* StyleSheetOptions */ })Common mistakes
Creating a fresh sheet per render — defeats dedup; create once per realm/root and reuse
Mixing the singleton and an isolated sheet for the same DOM — classes from one will not be deduped against the other; pick one per document root
See also: StyleSheet · sheet
StyleSheet class
class StyleSheet { constructor(options?: StyleSheetOptions) }The CSS injection engine: FNV-1a content hashing, a dedup cache (identical CSS → one rule), and SSR support (collect rules to a string on the server, hydrate on the client). sheet is the process singleton; createSheet() wraps new StyleSheet(). Direct instantiation is for custom integrations (server frameworks collecting critical CSS, test harnesses).
Example
import { StyleSheet } from "@pyreon/styler"
const s = new StyleSheet({ /* options */ })Common mistakes
Instantiating
new StyleSheet()in app code — use the exportedsheetsingleton (orcreateSheet()for explicit isolation); a stray instance will not be wherestyled()injects
See also: createSheet · sheet
sheet constant
sheet: StyleSheetThe process-wide singleton StyleSheet that styled() / css / keyframes / createGlobalStyle inject into by default. Read it for SSR critical-CSS extraction or debugging the rule registry; do not mutate it directly.
Example
import { sheet } from "@pyreon/styler"
// SSR: render the app, then read the collected rules off `sheet` for the <head>See also: StyleSheet · createSheet
resolve function
resolve(strings: TemplateStringsArray, values: Interpolation[], props: Record<string, any>): stringLow-level: resolve a tagged-template (strings + interpolations) against a props object into a final CSS string (function interpolations invoked with props). The engine styled() / useCSS build on. Direct use is for custom CSS-in-JS layered on top of styler; app code should prefer styled / css.
Example
import { resolve } from "@pyreon/styler"
const cssText = resolve(strings, values, { theme, $compact: true })See also: normalizeCSS · resolveValue · styled
normalizeCSS function
normalizeCSS(css: string): stringNormalizes a raw CSS string (whitespace/format canonicalization) so identical-intent CSS hashes to the same FNV-1a key and dedupes. Memoized via an internal cache — call clearNormCache() to drop it (tests / long-lived processes).
Example
import { normalizeCSS } from "@pyreon/styler"
normalizeCSS("color: red ;") // canonical form, dedup-stableSee also: clearNormCache · resolve
resolveValue function
resolveValue(value: Interpolation, props: Record<string, any>): stringResolves a SINGLE interpolation against props: invokes function interpolations with props, flattens nested CSSResult / KeyframesResult, and stringifies the result. The per-interpolation primitive resolve() loops over.
Example
import { resolveValue } from "@pyreon/styler"
resolveValue((p) => p.theme.colors.primary, { theme })See also: resolve · isDynamic
clearNormCache function
clearNormCache(): voidClears the normalizeCSS memo cache. Needed in test suites that assert on injection counts / sheet contents across cases, and in long-lived processes that churn unique CSS and want to bound the cache. No effect on already-injected rules.
Example
import { clearNormCache } from "@pyreon/styler"
afterEach(() => clearNormCache())See also: normalizeCSS
buildProps function
buildProps(rawProps: Record<string, any>, generatedCls: string, isDOM: boolean, customFilter?: (prop: string) => boolean): Record<string, any>Builds the final prop object forwarded to the rendered element: merges the generated class, drops $-transient props, and (for DOM targets) filters non-DOM attributes — customFilter overrides per-component. Copies DESCRIPTORS, not values, so compiler-emitted reactive (_rp getter) props survive forwarding instead of collapsing to a static snapshot.
Example
import { buildProps } from "@pyreon/styler"
const forwarded = buildProps(rawProps, "sc-abc123", true)Common mistakes
Re-implementing prop forwarding with
result[key] = source[key]— that fires getters and freezes reactive props to a one-time value. styler uses descriptor copy specifically to preserve the_rpgetter contract; any custom forwarder must do the samePassing
isDOM: truefor a component target — DOM-attr filtering will strip props the wrapped component legitimately needs
See also: filterProps · styled
filterProps function
filterProps(props: Record<string, unknown>): Record<string, unknown>Returns a copy of props with $-transient and known non-DOM props removed — the DOM-safety filter buildProps applies for element targets. Exposed for consumers doing their own forwarding who still want the styler allowlist semantics. Descriptor-preserving, same reactive-prop rationale as buildProps.
Example
import { filterProps } from "@pyreon/styler"
const domSafe = filterProps(props)See also: buildProps
isDynamic function
isDynamic(v: Interpolation): booleanTrue when an interpolation is a function (signal accessor / props reader) — i.e. the styled component must take the DYNAMIC resolve path (re-resolve per prop/theme change) rather than the static cached path. Used internally to decide the resolver branch; exported for tooling that mirrors that decision.
Example
import { isDynamic } from "@pyreon/styler"
isDynamic((p) => p.color) // true → dynamic path
isDynamic("12px") // false → static, cachedSee also: resolve · styled
hash / hashUpdate / hashFinalize / HASH_INIT function
hash(str: string) => string — hashUpdate(state: number, str: string) => number — hashFinalize(state: number) => string — HASH_INIT: numberThe FNV-1a non-cryptographic hash styler uses for compact, deduped class names + rule keys. hash(str) is the one-shot form → a base-36 string. The streaming trio composes it: hashUpdate(HASH_INIT, "ab") folds bytes into a running 32-bit numeric state, hashFinalize(state) renders (state >>> 0).toString(36), and hashUpdate(hashUpdate(HASH_INIT, "ab"), "cd") === hash("abcd"). Exported for tooling/consumers that need the SAME class-name hash styler emits (e.g. precomputing a class name before injection). Low-level — most apps never call it.
Example
import { hash, hashUpdate, hashFinalize, HASH_INIT } from "@pyreon/styler"
hash("color:red") // e.g. "1a2b3c"
hashFinalize(hashUpdate(hashUpdate(HASH_INIT, "a"), "b")) === hash("ab")Common mistakes
Using it for anything security-sensitive — FNV-1a is NON-cryptographic (fast, collision-cheap for CSS keys, NOT collision-resistant against adversarial input).
Feeding the base-36 STRING from
hashFinalizeback intohashUpdate— the streaming state is the 32-bit NUMBER; keep folding numbers withhashUpdateand callhashFinalizeONCE at the end.
See also: createSheet · styled
setStyleExtraction function
setStyleExtraction(enabled: boolean, rewrite?: (cssText: string, varsOut: Record<string, string>) => string) => voidInternal dependency-injection seam for Custom-Property Style Extraction (CPSE). @pyreon/ui-core's init({ styleExtraction: true }) calls this to enable CPSE and inject the cpseRewrite function — which lives in @pyreon/unistyle (styler cannot import unistyle: dep direction), so it is threaded in at init time. When on, the static + SSR resolve path rewrites resolved CSS to hoist per-instance values into custom properties. Apps do NOT call this directly — enable CPSE via the @pyreon/ui-core init flag; it is exported only so ui-core can wire it.
Example
// Apps enable CPSE through ui-core, not this call:
import { init } from "@pyreon/ui-core"
init({ styleExtraction: true }) // ui-core calls setStyleExtraction under the hoodCommon mistakes
Calling
setStyleExtraction(true)directly to turn on CPSE — without therewritefrom@pyreon/unistyle(which@pyreon/ui-coresupplies) it enables the branch with no rewriter. Useinit({ styleExtraction: true })from@pyreon/ui-core.
See also: styled · createSheet
Package-level notes
css / keyframes return lazy values, not strings: The
csstagged template yields aCSSResult(resolved on use);keyframesstringifies to an animation NAME;createGlobalStylereturns aComponentFnthat must be MOUNTED. None of them inject CSS at call time — only on resolution/mount.
Theme context is reactive:
useTheme()is a snapshot;useThemeAccessor()is the tracking accessor.styled/useCSStemplates already track the theme through their resolver, so whole-theme swaps re-resolve CSS + swap class names WITHOUT remounting the VNode. DestructuringuseTheme()and reading outside a reactive scope freezes the value.
Prop forwarding copies descriptors:
buildProps/filterPropscopy property DESCRIPTORS (not values) so compiler-emitted_rpgetter props keep their reactive subscription end-to-end. Any custom prop-forwarding wrapper layered on styler MUST do the same — plainresult[k] = src[k]silently collapses signal-driven props to a one-time snapshot.
Singleton sheet by default: All injection goes through the
sheetsingleton (FNV-1a dedup, SSR).createSheet()/new StyleSheet()are only for isolated realms (shadow DOM, iframes, test isolation) — mixing sheets for one document breaks dedup.
CSP nonce for strict style-src: Under a strict
style-src 'nonce-…'policy (no'unsafe-inline'), the SSR-inlined critical<style>needs a nonce or the browser blocks it on first paint (client CSSOMinsertRuleis CSP-exempt regardless). Pass the per-request nonce tosheet.getStyleTag(nonce), or setcreateSheet({ nonce })as a default — both stamp the SSR<style>and the client<style>element.