@pyreon/unistyle — API Reference
Generated from
unistyle'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 unistyle.
Foundational responsive-style layer that powers every visual package above it (elements, rocketstyle, coolgrid, kinetic). enrichTheme() merges a partial user theme with the default breakpoints / spacing / unit utilities so the rest of the system has a complete theme to read. makeItResponsive() turns a value or per-breakpoint map into the right CSS for the current screen. createMediaQueries() builds breakpoint-keyed media queries; styles() generates CSS from a theme; alignContent() resolves alignment shorthand to flex / grid CSS. The package is the single source of truth for responsive prop semantics across the UI system.
Multiplatform
Tier: Web-only — the browser package; the native story is stated below
responsive breakpoints + CSS-variable theming over real CSS; native theming is compile-time tokens + the 2-bucket size-class model
See Multiplatform for the capability matrix and Multiplatform libraries for every package's tier.
Features
enrichTheme(theme) — merge a partial theme with default breakpoints / spacing / units
breakpoints() — default responsive breakpoint set
createMediaQueries(breakpoints) — build breakpoint-keyed media query strings
makeItResponsive() — resolve a value / array / breakpoint object to CSS for the current screen
styles(theme) — generate CSS from a theme
alignContent() — resolve alignX / alignY / direction to flex CSS
extendCss() — extend a CSS definition with overrides
stripUnit / value / values — unit-utility helpers
themeToCssVars(theme) — autogenerate CSS custom properties from a theme JSON; units baked at emission (px→rem via rootSize)
resolveCssVarReferences(value, registry) — inline var() references back to raw values for non-CSS consumers (document export, devtools)
Provider / context — React-style provider for the theme (used internally by PyreonUI)
Complete example
A full, end-to-end usage of the package:
import { enrichTheme, makeItResponsive, alignContent } from '@pyreon/unistyle'
import type { MakeItResponsiveStyles } from '@pyreon/unistyle'
import { config } from '@pyreon/ui-core'
const { css } = config
// 1. Enrich a partial user theme with defaults — required before passing to
// PyreonUI. enrichTheme also precomputes the breakpoint media-query helpers
// into theme.__PYREON__ for makeItResponsive to consume.
const theme = enrichTheme({
colors: { primary: '#3b82f6', secondary: '#6366f1' },
fonts: { body: 'Inter, sans-serif' },
})
// 2. makeItResponsive builds a styled-component interpolation from a `styles`
// callback. It reads the component's theme prop and emits the mobile-first
// @media cascade — drop the result into a styled template literal.
const boxStyles: MakeItResponsiveStyles<{ padding?: string }> = ({ theme: t, css: cssFn }) =>
cssFn`padding: ${t.padding};`
const responsivePadding = makeItResponsive({ key: '$box', css, styles: boxStyles, normalize: true })
// 3. alignContent maps direction / alignX / alignY shorthand to flex CSS
const flexCss = alignContent({ direction: 'rows', alignX: 'center', alignY: 'top' })
// → 'flex-direction: column; align-items: center; justify-content: flex-start;'
// 4. Autogenerate CSS custom properties from a theme JSON (units baked at emission)
import { themeToCssVars } from '@pyreon/unistyle'
const { vars } = themeToCssVars({ rootSize: 16, spacing: { small: 8 }, ratio: { medium: 1.5 } })
vars.spacing.small // 'var(--px-spacing-small)' (emitted as 0.5rem in the :root block)
const width = `calc(${vars.spacing.small} * ${vars.ratio.medium})` // proportional sizing, native CSSExports
| Symbol | Kind | Summary |
|---|---|---|
enrichTheme | function | Merge a partial theme with the full default theme (breakpoints, spacing, unit utilities, fallback colors). |
breakpoints | constant | The default breakpoint configuration — a constant { rootSize, breakpoints } object, NOT a function. |
createMediaQueries | function | Build a record of media-query tagged-templates keyed by breakpoint name from a { breakpoints, rootSize, css } options |
makeItResponsive | function | Build a styled-component interpolation from a styles callback. |
styles | function | Generate the CSS for a flat theme object — box-model, typography, spacing, border, and layout declarations resolved from |
alignContent | function | Resolve direction / alignX / alignY shorthand to the matching flex CSS (flex-direction, align-items, `justify- |
extendCss | function | Flatten a CSS definition to a string. |
stripUnit | function | Strip the unit suffix from a CSS value and return the numeric part ("16px" → 16, "1.5rem" → 1.5). |
value | function | Convert ONE numeric/string CSS value to its final unit string. |
themeToCssVars | function | Autogenerate CSS custom properties from a plain theme JSON. |
resolveCssVarReferences | function | Resolve var(--…) references in a string back to their raw emitted values using a themeToCssVars registry — for consu |
values | function | Companion to value() for a mobile-first FALLBACK CHAIN: picks the first non-nullish entry in inputs (left to right) |
cpseRewrite / cpseVarName / extractStyleVar | function | The Custom-Property Style Extraction (CPSE) primitives — the machinery behind styleExtraction: true / cpseStyled. |
cpseStyled | function | The complete, opt-in CPSE-backed styled primitive — pass a styles prop (static object, or () => object for signal-dr |
API
enrichTheme function
enrichTheme(theme: PartialTheme): ThemeMerge a partial theme with the full default theme (breakpoints, spacing, unit utilities, fallback colors). Always call this before passing a user theme to PyreonUI — raw theme objects miss the default breakpoints and spacing scale that the rest of the UI system reads from. Idempotent: enriching an already-enriched theme is a no-op.
Example
import { enrichTheme } from "@pyreon/unistyle"
const theme = enrichTheme({
colors: { primary: "#3b82f6", secondary: "#6366f1" },
fonts: { body: "Inter, sans-serif" },
})
// Merges user overrides with default breakpoints, spacing, and unitsCommon mistakes
Passing the raw partial theme to
<PyreonUI theme={...}>without enriching —theme.breakpointsis undefined and every responsive prop falls back to the desktop valueMutating the theme after passing it to
PyreonUI— the styler resolver caches off the theme identity; clone + re-enrich for whole-theme swaps
See also: breakpoints · createMediaQueries
breakpoints constant
const breakpoints: { rootSize: number; breakpoints: Record<string, number> }The default breakpoint configuration — a constant { rootSize, breakpoints } object, NOT a function. breakpoints.breakpoints is the min-width map keyed by name (xs 0, sm 576, md 768, lg 992, xl 1200, xxl 1440) and breakpoints.rootSize is 16. The same values are folded into enrichTheme() output, so most consumers read the enriched theme rather than this constant. Use it when you need the defaults outside a theme context (e.g. building a custom theme or seeding createMediaQueries).
Example
import { breakpoints } from '@pyreon/unistyle'
breakpoints.breakpoints // { xs: 0, sm: 576, md: 768, lg: 992, xl: 1200, xxl: 1440 }
breakpoints.rootSize // 16See also: enrichTheme · createMediaQueries
createMediaQueries function
createMediaQueries(options: { breakpoints: Record<string, number>; rootSize: number; css: CssFn }): Record<string, (strings: TemplateStringsArray, ...values: unknown[]) => string>Build a record of media-query tagged-templates keyed by breakpoint name from a { breakpoints, rootSize, css } options bag (NOT a bare breakpoints argument). Each value is a FUNCTION — a css tagged-template that wraps the interpolated CSS in that breakpoint @media (min-width) block (the 0 breakpoint passes through unwrapped). Widths convert to em via rootSize. Used internally by enrichTheme() (stored on theme.__PYREON__.media); call directly when composing custom CSS-in-JS rules outside the responsive-prop pipeline.
Example
import { createMediaQueries, breakpoints } from '@pyreon/unistyle'
import { config } from '@pyreon/ui-core'
const queries = createMediaQueries({
breakpoints: breakpoints.breakpoints,
rootSize: breakpoints.rootSize,
css: config.css,
})
// each value is a tagged-template that wraps CSS in that breakpoint @media block:
// queries.sm`color: red` → '@media only screen and (min-width: 36em) { color: red }'See also: breakpoints · makeItResponsive
makeItResponsive function
makeItResponsive(options: { css: CssFn; styles: MakeItResponsiveStyles; theme?: object; key?: string; normalize?: boolean }): (props) => CSSResult | stringBuild a styled-component interpolation from a styles callback. This is NOT a value resolver — it returns a FUNCTION that, given component props, reads the theme (via key or props.theme) and emits the mobile-first @media cascade. The styles callback (a MakeItResponsiveStyles) receives the resolved per-breakpoint { theme, css, rootSize } and returns the CSS for that breakpoint; when the theme carries responsive per-breakpoint values, makeItResponsive normalizes then transforms then optimizes them into @media (min-width) blocks (mobile-first, only deltas emitted). key scopes which prop bag holds the theme; normalize toggles the breakpoint normalization; pass the css tag from @pyreon/ui-core config. Drop the returned interpolation into a styled template literal.
Example
import { makeItResponsive } from '@pyreon/unistyle'
import type { MakeItResponsiveStyles } from '@pyreon/unistyle'
import { config } from '@pyreon/ui-core'
const { css } = config
// The `styles` callback receives the resolved per-breakpoint theme (typed via
// the generic). makeItResponsive returns a styled-component interpolation.
const styles: MakeItResponsiveStyles<{ padding?: string }> = ({ theme: t, css: cssFn }) =>
cssFn`padding: ${t.padding};`
const responsive = makeItResponsive({ key: '$box', css, styles, normalize: true })
// styled('div')`${responsive}` — reads the component theme prop, emits @media queriesCommon mistakes
Passing
{ value, property }— makeItResponsive is a styled-component interpolation factory, not a value resolver; provide astylescallback plus thecsstag from@pyreon/ui-coreconfigPassing CSS-spec property names (
borderTopWidth) inside the styles callback — unistyle uses property-first naming (borderWidthTop); the responsive transformer expects the unistyle conventionForgetting to pass an enriched theme — without
theme.__PYREON__(populated byenrichTheme), per-breakpoint values fall back to the base value at every breakpointExpecting a
nullslot in a mobile-first array to fill from the LAST element — anull/undefinedslot is a SKIP that inherits the PREVIOUS breakpoint (["red", null, "blue"]= xs red, sm inherits red, md blue), identical to a breakpoint object with a missing key
See also: createMediaQueries · styles
styles function
styles(options: { theme: InnerTheme; css: CssFn; rootSize?: number; globalTheme?: object }): CSSResultGenerate the CSS for a flat theme object — box-model, typography, spacing, border, and layout declarations resolved from a { theme, css } options bag (NOT a bare theme argument). Returns the css-tagged result. Used to produce the declarations that back every styled component. Most consumers do not call this directly; the PyreonUI provider invokes it internally on theme mount.
Example
import { styles } from '@pyreon/unistyle'
import { config } from '@pyreon/ui-core'
const { css } = config
const rules = styles({ theme: { padding: '8px', color: '#222' }, css })
// → the resolved CSS declarations for the given themeSee also: enrichTheme · extendCss
alignContent function
alignContent(options: { alignX?: AlignContentAlignXKeys; alignY?: AlignContentAlignYKeys; direction?: AlignContentDirectionKeys }): string | nullResolve direction / alignX / alignY shorthand to the matching flex CSS (flex-direction, align-items, justify-content). The Element / Row / Column primitives use this internally — it is exposed for custom layout components that want the same alignment semantics. direction is one of inline / reverseInline / rows / reverseRows (inline maps to row, rows to column; the inline variants swap which axis alignX / alignY drive). Returns null when any of the three inputs is missing.
Example
import { alignContent } from '@pyreon/unistyle'
alignContent({ direction: 'rows', alignX: 'center', alignY: 'top' })
// → 'flex-direction: column; align-items: center; justify-content: flex-start;'
alignContent({ direction: 'inline', alignX: 'spaceBetween', alignY: 'center' })
// → 'flex-direction: row; align-items: center; justify-content: space-between;'See also: makeItResponsive
extendCss function
extendCss(styles: ((css: CssFn) => string) | string | null | undefined): stringFlatten a CSS definition to a string. Takes a SINGLE argument that is either a css-callback (invoked with a simple css tag, its result returned), a raw CSS string (returned as-is), or null / undefined (returns an empty string). Used by rocketstyle dimension chains + the elements / coolgrid styled helpers to inline a component extraStyles / extendCss prop. NOT an object deep-merge — it takes ONE argument and never layers a base with an override.
Example
import { extendCss } from '@pyreon/unistyle'
extendCss('color: red;') // → 'color: red;' (string returned as-is)
extendCss((css) => css`color: ${'red'};`) // → 'color: red;' (callback invoked)
extendCss(undefined) // → '' (nullish → empty string)See also: styles
stripUnit function
stripUnit(value: string | number, unitReturn?: boolean): number | string | [number | string, string | undefined]Strip the unit suffix from a CSS value and return the numeric part ("16px" → 16, "1.5rem" → 1.5). A number passes through unchanged, and a string that is NOT a <number><unit> shape (e.g. "calc(…)", "auto") is returned verbatim. Pass unitReturn: true to get the [value, unit] tuple instead ("24px" → [24, "px"]; an unmatched string → [value, undefined]). Useful for arithmetic on theme values declared as strings without hand-parsing.
Example
import { stripUnit } from '@pyreon/unistyle'
stripUnit('16px') // → 16
stripUnit('1.5rem') // → 1.5
stripUnit(16) // → 16
stripUnit('24px', true) // → [24, 'px']
stripUnit('auto', true) // → ['auto', undefined]See also: value · values
value function
value(input: string | number | null | undefined, rootSize?: number, outputUnit?: 'rem' | 'px' | '%' | 'em' | 'vh' | 'vw' | string): string | number | nullConvert ONE numeric/string CSS value to its final unit string. A unitless number is divided by rootSize (default 16) and emitted in outputUnit (default rem): value(16) → "1rem", value(24) → "1.5rem", value(8) → "0.5rem". 0 always stays unitless (0). A px string is converted to rem (value("16px") → "1rem"); any other-unit string ("2em", "50%") and any non-numeric string ("calc(100% - 8px)", "var(--x)", "auto") pass through verbatim — this passthrough is what lets themeToCssVars var leaves flow untouched. null/undefined/"" return null. Pass outputUnit: "px" to keep a unitless number in pixels (value(16, 16, "px") → "16px"). The companion values() picks the first non-nullish item then applies the same conversion. NOTE: the second parameter is rootSize (the px→rem divisor), NOT a fallback.
Example
import { value } from '@pyreon/unistyle'
value(16) // → '1rem' (16 / rootSize 16)
value(24) // → '1.5rem'
value(0) // → 0 (always unitless)
value('16px') // → '1rem' (px → rem)
value('2em') // → '2em' (other units pass through)
value('calc(100% - 8px)') // → 'calc(100% - 8px)' (verbatim passthrough)
value(16, 16, 'px') // → '16px' (outputUnit override)See also: stripUnit · values
themeToCssVars function
themeToCssVars(theme: object, options?: { prefix?: string; exclude?: readonly string[]; units?: Record<string, CssVarsUnitPolicy>; rootSize?: number }): { vars, css, registry }Autogenerate CSS custom properties from a plain theme JSON. Returns vars (same-shape tree with every eligible leaf replaced by a var(--px-…) reference string — plain strings, so they flow through the entire unistyle value pipeline untouched), css (a ready-to-inject :root { … } block), and registry (varName → emitted value for consumers that cannot evaluate var(), e.g. document export). Units are baked at EMISSION using the same value() conversion the pipeline applies today: spacing.small: 8 emits --px-spacing-small: 0.5rem, so themes stay authored in pixels. Conventional length keys (spacing/fontSize/headingSize/elementSize/borderRadius → rem, borderWidth → px) convert by default; everything else emits verbatim so unitless scales (lineHeight, ratio, zIndex) keep working in calc() multiplication. Pure + WeakMap-cached per theme identity — repeated calls return the SAME result object.
Example
import { themeToCssVars } from '@pyreon/unistyle'
const theme = { rootSize: 16, spacing: { small: 8 }, ratio: { medium: 1.5 } }
const { vars, css, registry } = themeToCssVars(theme)
vars.spacing.small // 'var(--px-spacing-small)'
css // ':root {\n --px-spacing-small: 0.5rem;\n --px-ratio-medium: 1.5;\n}'
registry.get('--px-spacing-small') // '0.5rem'
// proportional sizing is native CSS — no extra machinery:
const width = `calc(${vars.spacing.small} * ${vars.ratio.medium})`
// custom scales opt into conversion per top-level key:
themeToCssVars(theme, { units: { mySizes: 'rem' } })Common mistakes
Doing JS arithmetic on a var leaf (
vars.spacing.small * 2→ NaN) — compose with native CSS calc instead:`calc(${vars.spacing.small} * 2)`Expecting
breakpoints/rootSizeto be tokenized — they are excluded by design (@mediaqueries cannot readvar()); JS consumes them at build/render timeUsing a var leaf for
backgroundImage— CSS forbidsvar()insideurl(…); keep image URLs as raw valuesForgetting to inject
css— the function is pure; nothing lands on the page until the:rootblock reaches a style sink (<style>tag,sheet.injectRules,createGlobalStyle)Re-creating the theme object per render — results are WeakMap-cached by theme IDENTITY; a fresh object every call re-walks the tree and defeats downstream identity-keyed caches
Assuming a custom top-level key converts to rem — only the conventional length keys convert by default; declare
units: { myScale: "rem" }for custom scales
See also: enrichTheme · value
resolveCssVarReferences function
resolveCssVarReferences<T>(input: T, registry: ReadonlyMap<string, string>): TResolve var(--…) references in a string back to their raw emitted values using a themeToCssVars registry — for consumers that cannot evaluate CSS custom properties (document export to PDF/DOCX/email, devtools, non-CSS render targets). Inline fallbacks (var(--x, 1rem)) apply when the name is unknown; unresolvable references stay verbatim; non-strings pass through untouched. calc() expressions are inlined, NOT evaluated.
Example
import { resolveCssVarReferences, themeToCssVars } from '@pyreon/unistyle'
const { registry } = themeToCssVars(theme)
resolveCssVarReferences('var(--px-spacing-small)', registry) // '0.5rem'
resolveCssVarReferences('calc(var(--px-spacing-small) * 2)', registry) // 'calc(0.5rem * 2)'
resolveCssVarReferences('var(--px-missing, 1rem)', registry) // '1rem'Common mistakes
Expecting calc() to be EVALUATED — only the var() references inside are inlined; a non-CSS target needing one number must evaluate the calc itself or avoid calc-composed values
Passing a registry from a DIFFERENT theme identity — registries are per themeToCssVars(theme) result; mixed registries resolve to wrong values
See also: themeToCssVars
values function
values(inputs: Array<string | number | null | undefined>, rootSize?: number, outputUnit?: string): string | number | nullCompanion to value() for a mobile-first FALLBACK CHAIN: picks the first non-nullish entry in inputs (left to right) and runs it through the same value() conversion. Built for a responsive-prop shape where a smaller breakpoint may be unset and should fall back to a larger one's already-resolved raw value, without the caller hand-writing the ?? chain.
Example
import { values } from '@pyreon/unistyle'
values([undefined, 24, 16]) // → '1.5rem' (first defined: 24 → converted)
values([null, null, 8]) // → '0.5rem'Common mistakes
Confusing this with
value()(singular) —valueconverts ONE input;valuesselects the first defined item from a LIST, then converts it
See also: value · stripUnit
cpseRewrite / cpseVarName / extractStyleVar function
extractStyleVar(property, rawValue, rootSize?) → { rule, varName, varValue } · cpseVarName(property, breakpoint?) → string · cpseRewrite(frag, varsOut, breakpoint?) → stringThe Custom-Property Style Extraction (CPSE) primitives — the machinery behind styleExtraction: true / cpseStyled. The thesis: decouple a style rule's IDENTITY from its VALUE. Instead of baking a value into the rule (gap: 2.25rem — a new rule + resolve per distinct value, cost O(distinct values)), emit a value-AGNOSTIC rule that reads a custom property (gap: var(--u-<hash>) — resolved ONCE per component definition) and deliver the value per-instance as an inline style="--u-<hash>: 2.25rem" — cost O(component definitions), and a signal-driven value updates for free (write the inline custom property, no re-resolve). extractStyleVar extracts ONE declaration; cpseVarName derives the stable hashed var name for a property (+ optional breakpoint suffix); cpseRewrite rewrites every FLAT prop: value; declaration in an already-resolved CSS fragment to its var form — a fragment with any structure (selectors, nesting, @media, url(...)) passes through UNCHANGED (conservative: correct-but-unextracted beats wrong).
Example
import { extractStyleVar, cpseVarName, cpseRewrite } from '@pyreon/unistyle'
extractStyleVar('gap', 36)
// → { rule: 'gap:var(--u-1n2k4)', varName: '--u-1n2k4', varValue: '2.25rem' }
cpseVarName('gap') // '--u-1n2k4' — stable, shared across instances
cpseVarName('gap', 'sm') // '--u-1n2k4-sm' — per-breakpoint suffix
const vars = {}
cpseRewrite('gap: 2.25rem; margin: 1rem 2rem;', vars)
// → 'gap:var(--u-1n2k4);margin:var(--u-8f3a1);'
// vars → { '--u-1n2k4': '2.25rem', '--u-8f3a1': '1rem 2rem' }Common mistakes
Calling these directly to style a component — reach for
cpseStyled(tag)(the complete, opt-in vehicle) or<PyreonUI>init({ styleExtraction: true })instead; these are the low-level primitives they're built onExpecting
cpseRewriteto extract a declaration inside a selector /@mediablock /extendCssfragment — anything with structure ({,},&,@,url() is returned VERBATIM by design, since a flat-declaration rewrite would corrupt itReusing a
varsOutobject across unrelated fragments without clearing it —cpseRewriteonly ADDS keys, so a stale entry from a previous call can silently linger
See also: cpseStyled · themeToCssVars
cpseStyled function
cpseStyled(tag: string): ComponentFn<{ styles?, rootSize?, breakpoints?, class?, ref?, children? }>The complete, opt-in CPSE-backed styled primitive — pass a styles prop (static object, or () => object for signal-driven dynamic values) instead of a template literal. Per-definition, styling cost is FLAT in style-VALUE cardinality: the emitted class depends only on the declaration SHAPE (which properties, at which breakpoints), so N instances with N distinct values share ONE class and pay ONE resolve — the values themselves travel as per-instance inline custom properties. Supports responsive values as a mobile-first array (padding={[8, 16]}) or a breakpoint object (padding={{ sm: 16 }}), each breakpoint emitting its own suffixed var wrapped in a @media block. A dynamic (function) styles prop updates the inline vars via a renderEffect — the class itself never re-resolves.
Example
import { cpseStyled } from '@pyreon/unistyle'
const Box = cpseStyled('div')
<Box styles={{ gap: 24, padding: [8, 16] }} />
// one shared class; gap/padding delivered as inline custom properties
<Box styles={() => ({ gap: signal() })} /> // dynamic — updates the inline var onlyCommon mistakes
Expecting
stylesto accept the same shorthand as astyledtemplate literal — it takes unistyle-convention property keys (borderWidthTop, notborderTopWidth) in a flat object, not arbitrary CSS textMounting many DIFFERENT shapes (different property SETS) expecting the flat-cost guarantee — the win is per-SHAPE; a component whose author varies which properties are set per instance still resolves once per distinct shape
See also: cpseRewrite / cpseVarName / extractStyleVar
Package-level notes
Single source for responsive semantics: Every visual package (
elements,rocketstyle,coolgrid,kinetic) reads breakpoints / spacing / unit conventions from this package. Override defaults viaenrichTheme()once at the app root rather than per-component.
CSS property naming: Unistyle uses property-first naming (
borderWidthTop,borderColorLeft) rather than CSS-spec order (borderTopWidth,borderLeftColor). Stick to the unistyle convention when authoring components — the responsive transformer expects it.
Deprecated internal Provider / context: The package exports a low-level
Provider(default export) +context(re-exported from@pyreon/ui-core) that<PyreonUI>no longer calls directly — it is@deprecated-tagged and dev-warns on mount. There is no reason to import it: use<PyreonUI theme={theme}>instead.
Responsive-pipeline internals (normalizeTheme / sortBreakpoints / transformTheme): These three are re-exported for advanced consumers but are the internal steps
makeItResponsive()already runs for you:sortBreakpointsorders breakpoint keys by their min-width,normalizeThemeexpands a mobile-first array/breakpoint-object value into a per-breakpoint map (carrying forward the "null slot inherits the previous breakpoint" rule),transformThememaps a normalized per-breakpoint theme through astyles-shaped callback. Reach formakeItResponsive()first; use these directly only when building a custom responsive pipeline outside the styled-component path.