diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 6656c27..712ebcb 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -24,3 +24,12 @@ jobs:
- name: Run tests
run: yarn test
+
+ - name: Smoke source-condition package imports
+ run: |
+ node -C cssx-ts --input-type=module -e "import { cssx, useCssxLayer } from 'cssxjs'; if (typeof cssx !== 'function' || typeof useCssxLayer !== 'function') throw new Error('cssxjs source-condition import failed')"
+
+ - name: Smoke built package imports
+ run: |
+ yarn workspace @cssxjs/css-to-rn build
+ node --input-type=module -e "import { compileCss, resolveCssx } from '@cssxjs/css-to-rn'; const sheet = compileCss('.root { color: red; }'); const result = resolveCssx({ styleName: 'root', layers: sheet }); if (result.props.style.color !== 'red') throw new Error('built css-to-rn import failed')"
diff --git a/.gitignore b/.gitignore
index ab51119..b2be2c2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,7 @@
# Node dependencies
node_modules
+packages/*/dist
# npm-debug log
npm-debug.*
diff --git a/AGENTS.md b/AGENTS.md
index bea3b03..0598666 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -2,7 +2,7 @@
Read this first, then use `architecture.md` for the detailed system map.
-CSSX is a monorepo for a CSS-in-JS toolchain. Users write `styl`, `css`, or optional `pug` templates plus `styleName` and `part` props. Babel compiles that authoring syntax into style objects and runtime calls. The runtime matches class selectors, applies CSS variables/media queries, supports component parts, and can memoize with teamplay.
+CSSX is a monorepo for a CSS-in-JS toolchain. Users write `css`, `styl`, or optional `pug` templates plus `styleName` and `part` props. Babel compiles that authoring syntax into compiled CSS sheet IR and runtime calls. The unified `@cssxjs/css-to-rn` package owns CSS parsing, CSS value resolution, React Native/web style transformation, runtime caching, variables, media/dimension tracking, and React subscriptions.
## Start Here
@@ -12,11 +12,11 @@ CSSX is a monorepo for a CSS-in-JS toolchain. Users write `styl`, `css`, or opti
## Package Map
-- `packages/cssxjs/`: public `cssxjs` facade, CLI, wrappers, package exports.
-- `packages/runtime/`: `process()`, `matcher()`, variables, dimensions, platform helpers, teamplay caching.
-- `packages/loaders/`: Stylus/CSS loaders and direct compiler wrappers.
-- `packages/babel-plugin-rn-stylename-inline/`: compiles inline `css` and `styl` templates.
-- `packages/babel-plugin-rn-stylename-to-style/`: rewrites JSX `styleName`, `part`, old `*StyleName`, and helper calls.
+- `packages/css-to-rn/`: unified compiler/runtime engine. Start here for CSS parsing, selector IR, value resolution, property transforms, caching, `cssx()`, `useRuntimeCss()`, variables, and dimensions.
+- `packages/cssxjs/`: public `cssxjs` facade, CLI, package exports, runtime compatibility wrappers, Babel preset wrapper, loader wrappers, and Metro wrappers.
+- `packages/loaders/`: Stylus/CSS loaders and direct compiler wrappers. CSS compilation delegates to `@cssxjs/css-to-rn`.
+- `packages/babel-plugin-rn-stylename-inline/`: compiles inline `css` and `styl` templates, including local template interpolation lowering.
+- `packages/babel-plugin-rn-stylename-to-style/`: rewrites JSX `styleName`, `part`, old `*StyleName`, and helper calls into runtime calls.
- `packages/babel-preset-cssxjs/`: transform ordering and public Babel options.
- `packages/bundler/`: Metro hot-reload path for separate style files.
- `packages/eslint-plugin-cssxjs/`: wrapper around React Pug ESLint processor.
@@ -26,13 +26,17 @@ CSSX is a monorepo for a CSS-in-JS toolchain. Users write `styl`, `css`, or opti
## Core Contracts
- `__CSS_GLOBAL__` and `__CSS_LOCAL__` connect the inline Babel plugin to the JSX/runtime plugin.
-- Compiled style metadata `__hash__`, `__vars`, and `__hasMedia` connects loaders to cached and uncached runtime processing.
-- Runtime calls have this shape: `runtime(styleName, fileStyles, globalStyles, localStyles, inlineStyleProps)`.
-- Style priority is file styles, then global templates, then local templates, then inline props.
-- Selector specificity is approximated by class count only.
+- Runtime calls generated by Babel keep the compatibility shape `runtime(styleName, fileStyles, globalStyles, localStyles, inlineStyleProps)`.
+- `cssxjs/runtime/*` wrappers adapt that call shape to `@cssxjs/css-to-rn` platform entrypoints.
+- Style priority is file/imported sheets, then global templates, then local templates, then inline props.
+- Compiled sheets are JSON-serializable IR. Runtime cache/tracking state must stay outside the sheet.
- `part='root'` maps to `style`; other parts map to `{partName}Style`.
-- `css`/`styl` template interpolation is intentionally unsupported.
-- Cached runtime is selected by `cache: 'teamplay'` or by importing `observer` from `teamplay` or `startupjs`.
+- `:hover` and `:active` compile to `hoverStyle` and `activeStyle`.
+- Provider/global `:root` custom properties compile to `sheet.rootVariables` and resolve as scoped defaults below runtime `variables` and above `defaultVariables`.
+- Component tag selectors such as `Button` and `Button:part(text)` apply only inside components wrapped with `themed('Button', Component)` or explicit resolver tag options.
+- `variables` and `defaultVariables` are validating proxies. Direct assignment/deletion works for valid `--name` keys; bulk updates use `.assign()`, `.set()`, and `.clear()`.
+- Local JS template interpolation is lowered to synthetic `var(--__cssx_dynamic_N)` slots and passed as `values`.
+- `cache: 'teamplay'` remains accepted as a Babel option for compatibility, but runtime caching is owned by `@cssxjs/css-to-rn`, not Teamplay.
## Commands
@@ -51,7 +55,7 @@ yarn test
Run targeted tests:
```sh
-cd packages/runtime && yarn test
+cd packages/css-to-rn && npm test
cd packages/babel-plugin-rn-stylename-inline && yarn test
cd packages/babel-plugin-rn-stylename-to-style && yarn test
```
@@ -70,9 +74,10 @@ yarn start
## Change Guidance
-- For runtime matching changes, update `packages/runtime/test/matcher.mjs` and `packages/runtime/test/process.mjs`.
-- For Babel changes, update the relevant Jest snapshots.
-- For public API or behavior changes, update `docs/` and `architecture.md`.
+- For CSS parsing, selector, value, transform, cache, variable, media, or React tracking behavior, update `packages/css-to-rn/test/engine/**` or `packages/css-to-rn/test/react/**`.
+- For inline template or interpolation compilation, update `packages/babel-plugin-rn-stylename-inline` snapshots.
+- For JSX `styleName`/`part` behavior, update `packages/babel-plugin-rn-stylename-to-style` snapshots.
+- For public API or behavior changes, update `docs/`, `architecture.md`, and this guide.
- For Pug, type checking, or ESLint behavior, check whether the implementation lives in `@react-pug/*`; this repo often only wraps it.
- For separate style files, check both Babel `compileCssImports` behavior and Metro transformer behavior.
- Prefer current source code and `docs/` over older package READMEs when they conflict.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dad8e16..d936c84 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,68 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [0.4.0-alpha.6](https://github.com/startupjs/cssx/compare/v0.4.0-alpha.5...v0.4.0-alpha.6) (2026-06-25)
+
+
+### Bug Fixes
+
+* make auto theme opt-in ([7132a31](https://github.com/startupjs/cssx/commit/7132a3189c49facdce8aa8bc47de63135313e82b))
+
+
+
+
+
+# [0.4.0-alpha.5](https://github.com/startupjs/cssx/compare/v0.4.0-alpha.4...v0.4.0-alpha.5) (2026-06-25)
+
+**Note:** Version bump only for package cssx
+
+
+
+
+
+# [0.4.0-alpha.4](https://github.com/startupjs/cssx/compare/v0.4.0-alpha.3...v0.4.0-alpha.4) (2026-06-25)
+
+**Note:** Version bump only for package cssx
+
+
+
+
+
+# [0.4.0-alpha.3](https://github.com/startupjs/cssx/compare/v0.4.0-alpha.2...v0.4.0-alpha.3) (2026-06-24)
+
+**Note:** Version bump only for package cssx
+
+
+
+
+
+# [0.4.0-alpha.2](https://github.com/startupjs/cssx/compare/v0.4.0-alpha.1...v0.4.0-alpha.2) (2026-06-24)
+
+**Note:** Version bump only for package cssx
+
+
+
+
+
+# [0.4.0-alpha.1](https://github.com/startupjs/cssx/compare/v0.4.0-alpha.0...v0.4.0-alpha.1) (2026-06-24)
+
+**Note:** Version bump only for package cssx
+
+
+
+
+
+# [0.4.0-alpha.0](https://github.com/startupjs/cssx/compare/v0.3.0...v0.4.0-alpha.0) (2026-06-24)
+
+
+### Features
+
+* Unify CSS-to-RN compiler and runtime pipeline ([#5](https://github.com/startupjs/cssx/issues/5)) ([cd205cf](https://github.com/startupjs/cssx/commit/cd205cfcf0e7772f79263a47d6ca5c7b802edc31)), closes [startupjs/startupjs#1327](https://github.com/startupjs/startupjs/issues/1327) [startupjs/startupjs-ui#41](https://github.com/startupjs/startupjs-ui/issues/41)
+
+
+
+
+
# [0.3.0](https://github.com/startupjs/cssx/compare/v0.2.33...v0.3.0) (2026-05-03)
diff --git a/README.md b/README.md
index c2d384a..7601c5f 100644
--- a/README.md
+++ b/README.md
@@ -23,6 +23,18 @@ Install the following extension for full CSSX support with Pug and CSS/Stylus in
[`vscode-react-pug-tsx`](https://marketplace.visualstudio.com/items?itemName=startupjs.vscode-react-pug-tsx)
+## Credits
+
+CSSX's unified CSS-to-React-Native compiler/runtime was inspired by and replaces
+the separate roles previously handled by:
+
+- [`css-to-react-native`](https://github.com/styled-components/css-to-react-native)
+- [`css-to-react-native-transform`](https://github.com/kristerkari/css-to-react-native-transform)
+
+The runtime and API design also benefited from studying:
+
+- [`cssta`](https://github.com/jacobp100/cssta)
+
## License
MIT
diff --git a/architecture.md b/architecture.md
index bc38792..0f2102a 100644
--- a/architecture.md
+++ b/architecture.md
@@ -1,44 +1,43 @@
# CSSX Architecture
-CSSX is a CSS-in-JS system for React Native, react-native-web, and pure React web targets. Its public API lets users write `styl`, `css`, and optional `pug` tagged template literals, apply styles with `styleName`, expose child component override points with `part`, and update CSS variables at runtime.
+CSSX is a CSS-in-JS system for React Native, react-native-web, and pure React web targets. Users write `css`, `styl`, and optional `pug` templates, apply styles with `styleName`, expose child component override points with `part`, and update CSS variables at runtime.
-Most work happens at build time. Babel compiles template literals and `.cssx.*` imports into plain style objects, then rewrites JSX so elements receive a spread of runtime-generated style props. The runtime is deliberately small: it matches class names to compiled selectors, applies CSS variables and media queries, handles `:part()` style props, and optionally memoizes results with teamplay.
+The current architecture centers on `@cssxjs/css-to-rn`. That package owns the unified CSS-to-style pipeline: CSS parsing, canonical sheet IR, selector matching, CSS variable/interpolation resolution, React Native/web property transformation, runtime caching, dimensions/media tracking, and React subscription helpers. The older separate runtime package has been removed from the active dependency graph.
## Repository Map
-- `docs/`: public documentation served by Rspress. Start here for expected user-facing behavior.
-- `packages/cssxjs/`: umbrella package published as `cssxjs`. It exposes the public entrypoints, CLI, runtime wrappers, Babel preset wrapper, loader wrappers, and Metro wrappers.
-- `packages/runtime/`: style matching, CSS variable state, media-query dimension state, platform helper injection, and cached/non-cached runtime entrypoints.
-- `packages/loaders/`: webpack-compatible style loaders plus direct compiler helpers used by Babel.
-- `packages/babel-plugin-rn-stylename-inline/`: compiles inline `css` and `styl` template literals into module/function-scoped style objects.
-- `packages/babel-plugin-rn-stylename-to-style/`: rewrites `styleName`, `part`, `*StyleName`, and `styl(...)`/`css(...)` function calls into runtime calls.
-- `packages/babel-preset-cssxjs/`: composes syntax plugins, React Pug transform, inline style compilation, and `styleName`/`part` transform.
+- `docs/`: public documentation served by Rspress.
+- `packages/css-to-rn/`: unified compiler and runtime engine.
+- `packages/cssxjs/`: umbrella package published as `cssxjs`; exports public APIs, runtime compatibility wrappers, Babel/Metro wrappers, CLI, and loader wrappers.
+- `packages/loaders/`: webpack-compatible loaders plus compiler helpers used by Babel and Metro. Stylus still compiles to CSS here; CSS compilation delegates to `@cssxjs/css-to-rn`.
+- `packages/babel-plugin-rn-stylename-inline/`: compiles inline `css` and `styl` tagged templates.
+- `packages/babel-plugin-rn-stylename-to-style/`: rewrites JSX `styleName`, `part`, old `*StyleName`, and helper calls into runtime calls.
+- `packages/babel-preset-cssxjs/`: composes syntax plugins, React Pug, inline style compilation, and `styleName`/`part` rewriting.
- `packages/bundler/`: Metro config and transformer support for separate `.cssx.styl` and `.cssx.css` files.
- `packages/eslint-plugin-cssxjs/`: facade over `@react-pug/eslint-plugin-react-pug`.
- `example/`: simple web example using Babel plus esbuild directly.
-- `docs-theme/` and `rspress.config.ts`: documentation theme and syntax highlighting configuration.
-The repository uses Yarn workspaces and Lerna. Root `package.json` requires Node `>=22` and defines the main scripts.
+The repository uses Yarn workspaces and Lerna. Root `package.json` requires Node `>=22`.
## Public API Surface
-The published `cssxjs` package exposes:
+The `cssxjs` package exposes:
-- `styl` and `css`: template tags processed away by Babel, and function forms used as `styl(styleName, inlineStyleProps)` / `css(...)` after Babel rewrites them.
+- `css` and `styl`: Babel-processed template tags, plus helper-call forms after Babel rewriting.
- `pug`: template tag processed by `@react-pug/babel-plugin-react-pug`.
-- `variables`: observable runtime CSS variable overrides.
-- `setDefaultVariables` and `defaultVariables`: default CSS variable registry.
-- `dimensions`: observable screen width state for media-query invalidation.
-- `matcher`: advanced/internal class selector matcher.
-- `cssxjs/babel`: Babel preset wrapper.
-- `cssxjs/metro-config` and `cssxjs/metro-babel-transformer`: Metro integration wrappers.
+- `cssx`: runtime helper from `@cssxjs/css-to-rn/react`.
+- `variables`, `defaultVariables`, `setDefaultVariables`: runtime CSS variable registries.
+- `useRuntimeCss`, `useCssxSheet`, `useCssxTemplate`: React helpers for runtime-generated CSS and local template values.
+- `CssxProvider`, `configureCssx`, `useCssxConfig`: optional runtime configuration.
+- `cssxjs/runtime`, `cssxjs/runtime/web`, `cssxjs/runtime/react-native`, and `teamplay` compatibility runtime paths used by Babel-generated code.
+- `cssxjs/babel`, `cssxjs/metro-config`, and `cssxjs/metro-babel-transformer`.
- `cssxjs check`: CLI bridge to `@react-pug/check-types`.
-`packages/cssxjs/index.js` intentionally makes `css`, `styl`, and `pug` throw at runtime. If a user sees those errors, their file did not go through the Babel pipeline.
+`packages/cssxjs/index.js` intentionally makes direct unprocessed `css`, `styl`, and `pug` calls throw. Seeing those errors means the file did not go through the Babel pipeline.
-## End-to-End Build Flow
+## End-To-End Flow
-### 1. Authoring
+### Authoring
Users write components like:
@@ -63,7 +62,7 @@ function Button ({ variant, children }) {
}
```
-Parent components can target the exposed parts from outside:
+Parent components can target exposed parts from outside:
```jsx
function Toolbar () {
@@ -78,92 +77,68 @@ function Toolbar () {
}
```
-The core authoring constructs are:
+Parts are only addressable from outside the component exposing them. Inside a component, style the inner element directly with its own class selector.
-- class-like `styleName` values: strings, arrays, and object flags.
-- `part` attributes with compile-time-static names.
-- `:part(name)` selectors in CSS/Stylus, used by parent/outside styles to target child component parts.
-- runtime CSS variables through `var(--name, fallback)`.
-- media queries and viewport units.
-- optional Pug templates and embedded terminal `style` blocks.
+### Babel Preset
-### 2. Babel Preset
+`packages/babel-preset-cssxjs/index.js` configures transforms in this order:
-`packages/babel-preset-cssxjs/index.js` configures the transform stack:
-
-1. Syntax support for JSX, TypeScript, and TSX depending on filename.
+1. JSX/TypeScript syntax plugins.
2. `@react-pug/babel-plugin-react-pug` when `transformPug !== false`.
3. `@cssxjs/babel-plugin-rn-stylename-inline` when `transformCss !== false`.
4. `@cssxjs/babel-plugin-rn-stylename-to-style` when `transformCss !== false`.
-This order matters. Pug must become JSX before CSSX rewrites JSX attributes. Inline CSS/Stylus templates must compile before `styleName` references are converted into runtime calls.
+This order matters. Pug must become JSX before CSSX rewrites JSX attributes, and inline CSS/Stylus templates must compile before `styleName` references are converted into runtime calls.
-Preset options:
+Important options:
- `platform`: passed to style compilers. Defaults to `web` or Babel caller platform.
-- `reactType`: chooses runtime target, currently `web` or `react-native`.
-- `cache`: chooses cached runtime, currently only `teamplay`.
-- `transformPug`: disables Pug transformation when false.
-- `transformCss`: disables CSS/Stylus and `styleName` transformation when false.
+- `reactType`: chooses runtime target, `web` or `react-native`.
+- `cache`: accepts `teamplay` for compatibility. It still affects generated import paths, but runtime caching is now internal to `@cssxjs/css-to-rn`.
+- `transformPug` and `transformCss`: disable the corresponding transforms when false.
-### 3. Pug Transform
+### Inline Template Compilation
-Pug support is provided by external `@react-pug/*` packages. CSSX wraps those packages through:
+`packages/babel-plugin-rn-stylename-inline/index.js` handles `css` and `styl` tagged templates imported from magic imports. Defaults are `cssxjs` and `startupjs`.
-- `cssxjs/babel/plugin-react-pug`
-- `cssxjs check`
-- `eslint-plugin-cssxjs`
+Behavior:
-Current CSSX docs recommend terminal embedded style blocks inside Pug templates:
+- Imported aliases are supported.
+- Module-level templates become `const __CSS_GLOBAL__ = compiledSheet`.
+- Function-level templates become a top-level compiled sheet plus function-local `const __CSS_LOCAL__ = compiledSheet`.
+- Local JS template interpolation is supported. Expressions are lowered to synthetic declaration-value variables:
```jsx
-return pug`
- View.card
- Text.title= title
-
- style(lang='styl')
- .card
- padding 2u
+css`
+ .root {
+ color: ${color};
+ padding: ${pad} 2u;
+ }
`
```
-The React Pug Babel plugin turns this into JSX plus local `styl` or `css` templates, which are then handled by CSSX's inline style plugin.
-
-### 4. Inline Style Compilation
-
-`packages/babel-plugin-rn-stylename-inline/index.js` processes `css` and `styl` tagged template literals imported from magic imports. The default magic imports are `cssxjs` and `startupjs`.
-
-Important behavior:
+compiles as a sheet containing `var(--__cssx_dynamic_0)` and `var(--__cssx_dynamic_1)`, while the function receives:
-- Only imported `css`/`styl` identifiers are processed. Aliases are supported.
-- Template interpolation is rejected. Dynamic values should use CSS variables or inline `style`.
-- Module-level templates become a top-level `const __CSS_GLOBAL__ = ...`.
-- Function-level templates become a top-level compiled object plus a function-local `const __CSS_LOCAL__ = ...`.
-- The plugin removes processed template expressions.
-- Compilation is delegated to `@cssxjs/loaders/compilers`.
-
-The generated names come from `packages/runtime/constants.cjs`:
-
-- `GLOBAL_NAME`: `__CSS_GLOBAL__`
-- `LOCAL_NAME`: `__CSS_LOCAL__`
-
-Those names are part of the transform/runtime contract.
-
-### 5. Style File Imports and JSX Rewriting
+```js
+const __CSS_LOCAL__ = {
+ sheet: _localCssInstance,
+ values: [color, pad]
+}
+```
-`packages/babel-plugin-rn-stylename-to-style/index.js` is the main JSX transform. It has three jobs.
+Interpolations are allowed only in function-scoped local `css`/`styl` templates and only in declaration values. Selectors, property names, media queries, exports, and module-level templates remain static.
-First, it handles style file imports. Default extensions are `cssx.css` and `cssx.styl`, so imports such as `import './Button.cssx.styl'` are style imports. In tests the plugin is often configured with `extensions: ['styl', 'css']`.
+### JSX Rewriting
-When `compileCssImports` is true, Babel reads and compiles the file itself and replaces the import with a compiled `const`. This is convenient but means changes to the separate style file may require restarting or clearing Babel cache. When false, the import stays in place and the bundler must compile it.
+`packages/babel-plugin-rn-stylename-to-style/index.js` handles JSX styling attributes and helper calls.
-Second, it rewrites JSX styling attributes. A JSX opening element with `styleName`, `style`, or part style props becomes a spread call:
+A JSX opening element with `styleName`, `style`, or part style props becomes a spread call:
```jsx
```
-becomes conceptually:
+conceptually becomes:
```jsx
```
-The runtime call returns an object containing `style` and any `{part}Style` props.
+The runtime call returns an object containing `style` and any `{part}Style`, `hoverStyle`, or `activeStyle` props.
-Third, it rewrites function calls to imported `styl`/`css` identifiers. This supports the public spread helper form:
+The same runtime call shape is used for public helper forms like:
```jsx
```
-The helper call is replaced with the same runtime call shape used for JSX attributes.
-
-Runtime import paths are chosen from plugin options and imports:
+Runtime import paths are selected by plugin options:
- default: `cssxjs/runtime`
- `reactType: 'web'`: `cssxjs/runtime/web`
@@ -195,166 +168,196 @@ Runtime import paths are chosen from plugin options and imports:
- `cache: 'teamplay'`: `cssxjs/runtime/teamplay`
- both `reactType` and `cache`: `cssxjs/runtime/web-teamplay` or `cssxjs/runtime/react-native-teamplay`
-If the file imports an `observer` named import from `teamplay` or `startupjs`, the plugin auto-selects `cache: 'teamplay'`.
+The `teamplay` paths are compatibility wrappers around the same new runtime.
-## Style Compilation
+## `@cssxjs/css-to-rn`
-### Loader Chain
+This package is TypeScript-first ESM and uses Node's strip-only TS support for tests via the custom export condition `cssx-ts`.
-The style compiler path is:
+### Exports
-1. Stylus input goes through `stylusToCssLoader` to become CSS.
-2. CSS input goes through `cssToReactNativeLoader`.
-3. `cssToReactNativeLoader` calls `@startupjs/css-to-react-native-transform` to produce React Native style objects.
+- Root `@cssxjs/css-to-rn`: isomorphic compiler/resolver APIs.
+- `@cssxjs/css-to-rn/react`: React runtime helpers with conditional web/native behavior.
+- `@cssxjs/css-to-rn/web`: web-targeted helpers.
+- `@cssxjs/css-to-rn/react-native`: React Native-targeted helpers.
-`packages/loaders/compilers/*` wrap the loaders for synchronous direct use from Babel and strip the generated `module.exports =` prefix.
+`react` and `react-native` are optional peer dependencies.
-### Stylus Loader
+### Canonical Sheet IR
-`packages/loaders/stylusToCssLoader.js`:
+`compileCss()` and `compileCssTemplate()` return JSON-serializable sheets:
-- creates a Stylus compiler for the source.
-- sets `filename` for error reporting/import resolution.
-- defines `$PLATFORM` and `__WEB__`, `__IOS__`, `__ANDROID__`, etc. when a platform is provided.
-- auto-imports `@startupjs/ui/styles/index.styl` and `@startupjs-ui/core/styles/index.styl` if those packages are installed.
-- auto-imports `styles/index.styl` from `process.cwd()` if present.
-- applies `patchStylusAddUnit()` once.
+```ts
+interface CompiledCssSheet {
+ version: 1
+ id: string
+ sourceId?: string
+ contentHash: string
+ rules: CssxRule[]
+ keyframes: Record
+ exports?: Record
+ metadata: CssxMetadata
+ diagnostics: CssxDiagnostic[]
+ error?: CssxDiagnostic
+}
+```
-`patchStylusAddUnit()` monkey-patches Stylus units so `1u` is converted to `8px` during Stylus compilation.
+Rules preserve:
-### CSS-to-RN Loader
+- selector text.
+- class list.
+- logical part target, or `null` for root.
+- class-count specificity.
+- source order.
+- optional media condition.
+- declaration order and source locations.
-`packages/loaders/cssToReactNativeLoader.js`:
+The sheet must remain serializable. Cache state, subscriptions, and runtime trackers live outside the sheet.
-- calls `@startupjs/css-to-react-native-transform` with media queries, part selectors, and keyframes enabled.
-- supports `:export { ... }` values and converts exported Stylus values into JS values.
-- adds `__hash__` to the compiled object for memoization keys.
-- adds `__vars` with sorted CSS variable names when `var(...)` is present.
-- adds `__hasMedia` when top-level `@media` rules exist.
-- returns JS source in the shape `module.exports = { ... }`.
+### Compiler
-The metadata fields are consumed by `packages/runtime/process.js` and `packages/runtime/processCached.js`; changing them requires coordinated runtime updates.
+`src/compiler.ts` parses CSS with the lightweight `css` parser. Runtime mode returns an empty diagnostic sheet on syntax errors. Build mode throws for errors that should fail Babel/loader builds.
-## Runtime
+Build mode validates static declaration values through the shared value resolver
+and property transformer. Unsupported static constructs such as
+layout-dependent `calc()` expressions, unsupported transform functions, and
+unsupported background images fail during Babel/loader compilation.
+Declarations containing `var()` or template slots are deferred to runtime
+validation because their final value is not knowable at build time.
-Runtime entrypoints live in `packages/runtime/entrypoints/*`. Each entrypoint:
+Supported selectors:
-1. injects platform helpers through `setPlatformHelpers()`.
-2. initializes the dimensions updater.
-3. exports either the normal `process` function or the teamplay-cached `process` function.
+- `.root`
+- `.root.active`
+- `.root:part(label)`
+- `.root::part(label)`
+- `.root.active:part(icon)`
+- `.root:hover`
+- `.root:active`
+- `Button`
+- `Button.primary`
+- `Button:part(text)`
+- `Button::part(text)`
+- `Button.primary:part(text)`
+- `:export`
+- bare `:root` custom-property declarations
-The facade package re-exports these entrypoints from `packages/cssxjs/runtime/*` and provides both default and named `runtime` exports, because the Babel plugin imports `{ runtime as _runtime }`.
+`:hover` maps to `hoverStyle`; `:active` maps to `activeStyle`. Tag selectors apply only when the current component tag is provided by `themed(tagName, Component)` or explicit resolver options. Unsupported selectors are ignored with diagnostics in runtime mode.
-### Platform Helpers
+Bare `:root` custom-property declarations are compiled into `sheet.rootVariables` and become scoped defaults when the sheet is supplied through `CssxProvider style` or another layer. Declaration-level custom properties outside `:root` are ignored with diagnostics.
-`packages/runtime/platformHelpers/index.js` stores the active helper implementation. Helpers provide:
+### Value Resolution
-- `getDimensions()`
-- `getPlatform()`
-- `isPureReact()`
-- `initDimensionsUpdater()`
+`src/values.ts` resolves declaration value strings before property transformation:
-`platformHelpers/web.js` uses `window.innerWidth`/`innerHeight`, falls back to `1024x768` without `window`, reports platform `web`, and marks pure React mode true.
+1. Replace interpolation slots from `values`.
+2. Recursively resolve nested `var()`.
+3. Resolve `u`, viewport units, and supported `calc()`.
+4. Normalize supported modern color functions (`oklch()`, `oklab()`, `color-mix()`) to `rgba(...)`.
+5. Return dependencies for variables and dimensions.
-`platformHelpers/react-native.js` uses React Native `Dimensions` and `Platform`, reports pure React mode false, and listens for dimension changes.
+Variable priority is:
-The runtime logs and throws if helpers are missing, which usually means Babel imported the wrong runtime entrypoint.
+1. template interpolation values.
+2. runtime `variables['--name']`.
+3. nearest scoped provider/sheet `:root` variable.
+4. outer scoped provider/sheet `:root` variables.
+5. `defaultVariables['--name']`.
+6. inline fallback `var(--name, fallback)`.
-### Variables and Dimensions
+Unresolved variables, cycles, depth limits, invalid interpolations, and unsupported `calc()` invalidate only the containing declaration. Earlier fallback declarations in the same rule still apply.
-`packages/runtime/variables.js` exports:
+`1u = 8px`. Viewport units resolve from current dimensions. `calc()` supports arithmetic that can reduce to a concrete numeric or pixel value; layout-dependent percentages are unsupported.
-- default observable `variables` object.
-- mutable `defaultVariables`.
-- `setDefaultVariables()`.
+### Property Transformation
-Resolution order is:
+`src/transform/index.ts` turns final CSS declaration values into React Native/web style props. It supports:
-1. runtime `variables['--name']`
-2. `defaultVariables['--name']`
-3. inline fallback from `var(--name, fallback)`
+- raw camelCase property pass-through.
+- margin/padding/border/border-radius/border-width/border-color shorthands.
+- transform arrays.
+- text-shadow.
+- box-shadow string pass-through.
+- `filter` string pass-through.
+- animation and transition shorthands/longhands.
+- keyframe object inlining for Reanimated v4 style props.
+- `background-image` and limited `background` shorthand.
-`packages/runtime/dimensions.js` exports an observable `{ width: 0 }` singleton plus an initialization flag.
+For React Native, `background-image` becomes `experimental_backgroundImage`. Only `linear-gradient()` and `radial-gradient()` are emitted; image URLs and other image functions are diagnosed and dropped.
-Both observables come from `@nx-js/observer-util`. The uncached runtime reads these observables while processing styles; the cached runtime reads them in its `forceUpdateWhenChanged` hook.
+### Resolver And Caching
-### `process()`
+`src/resolve.ts` composes the compiler, value resolver, property transformer, cascade, and caching.
-`packages/runtime/process.js` is the main runtime function:
+Public functions:
-```js
-process(styleName, fileStyles, globalStyles, localStyles, inlineStyleProps)
-```
-
-It:
+- `resolveCssx(options)`
+- `cssx(styleName, layers, inlineStyleProps?, options?)`
+- `createCssxCache()`
-1. transforms each style object:
- - replaces CSS variables when `__vars` exists.
- - listens to dimensions when `__hasMedia` exists.
- - applies media queries and viewport units through vendored processors.
-2. calls `matcher()`.
-3. flattens nested specificity arrays into single style objects.
-4. adjusts pure React values such as numeric `lineHeight` to `px` strings.
-5. applies runtime `u` unit replacement for string values that still contain `u`.
+Resolver order:
-### `matcher()`
+1. Normalize `styleName` with classcat-like semantics.
+2. Normalize one or more sheet layers.
+3. Match selectors by component tag and class set.
+4. Filter inactive media rules.
+5. Group by output prop: `style`, `{part}Style`, `hoverStyle`, `activeStyle`.
+6. Apply cascade by layer, specificity, and source order.
+7. Resolve dynamic values declaration-by-declaration.
+8. Transform final declarations.
+9. Inline only keyframes referenced by active animation declarations.
+10. Merge inline style props last.
-`packages/runtime/matcher.js` is intentionally simple and class-only.
+Runtime caches are bounded. Static cache keys include sheet identity, style names, and a JSON/hash of inline style props. Dynamic signatures include only used variables, used media matches, and dimensions when actually used. Interpolated local templates keep one effective cache entry per tracked sheet call shape; changing values replaces the same cache slot instead of growing historical variants.
-Input `styleName` is normalized through an embedded classcat-style function. Supported shapes are strings, arrays, and object flags.
+`JSON.stringify()` is intentionally used for inline style value hashing. Cyclic inline style objects are treated as uncacheable.
-For each selector in each style object:
+### React Runtime
-- `:part(name)` or `::part(name)` targets prop `nameStyle`.
-- no part selector targets root prop `style`.
-- `part(root)` is handled by Babel as root `style`, not by the matcher.
-- selectors are matched by checking whether every class in the selector exists in the normalized `styleName`.
-- selector specificity is approximated by number of classes.
+`src/react/**` adds React integration without making `cssx()` a hook.
-Application order is:
+Key pieces:
-1. file styles
-2. global inline templates
-3. local inline templates
-4. inline style props
+- `store.ts`: `variables`, `defaultVariables`, `setDefaultVariables()`, variable bulk methods, dimensions/media state, microtask-batched notifications.
+- `tracker.ts`: `TrackedCssxSheet`, committed dependency snapshots, per-tracker cache.
+- `cssx.ts`: ergonomic `cssx()` wrapper that delegates to `resolveCssx()` and records dependencies into tracked sheets during render.
+- `hooks.ts`: `useCssxSheet()`, `useRuntimeCss()`, `useCssxTemplate()`, `useCssxLayer()`, `useCssVariable()`, `useCssVariableRaw()`, `getCssVariable()`, and `getCssVariableRaw()`.
+- `config.ts`: optional `CssxProvider`, `configureCssx()`, `useCssxConfig()`, and `themed()`.
-Because `process()` flattens and `Object.assign`s in that order, later layers override earlier layers. Within each layer, selectors with more classes override selectors with fewer classes.
+`useCssxSheet()` starts a render-local dependency collection before render and commits it in a layout/effect phase. If a render is aborted, for example because a component throws a promise into Suspense, the pending dependencies are not committed and do not leak global subscriptions.
-There is also a legacy matcher mode when `inlineStyleProps` is omitted. It returns only root style arrays and exists for older `*StyleName` conversion behavior.
+`CssxProvider style` accepts raw CSS strings, compiled sheets, tracked sheets, layer objects, arrays, and falsey values. Provider layers are appended after parent provider layers and before component-local layers. Nested providers append additional `:root` variable scopes, with inner scopes winning over outer scopes. `themed()` adds the current component tag and a render-local dependency tracker so provider/global styles that read variables can update themed components even when they have no local sheet.
-### Cached Runtime
+Variable writes and deletes notify subscribers once per microtask. Subscribers only rerender when a variable they actually used changes. Viewport-unit subscribers are tied to dimension changes. Media-query dependencies store the match value observed during the committed render; dimension changes and platform media adapter changes only rerender subscribers whose committed media result changed. Browser `matchMedia` is used on web when available, and tests can install a media-query adapter for non-DOM media features such as `prefers-color-scheme`, `hover`, and `pointer`. Web resize uses leading plus trailing debounced updates.
-`packages/runtime/processCached.js` wraps `process()` with `teamplay/cache` `singletonMemoize`.
+## Loaders And Separate Files
-The cache normalizer hashes:
+Stylus remains separate from CSS-to-RN transformation:
-- `styleName`
-- each style object's `__hash__` or full object
-- `inlineStyleProps`
+1. `stylusToCssLoader` compiles Stylus to CSS and preserves current project/UI auto-import behavior.
+2. `cssToReactNativeLoader` calls `compileCss()` or `compileCssTemplate()` from `@cssxjs/css-to-rn`.
+3. The loader emits `module.exports = `.
-The cache invalidation hook watches:
+`cssToReactNativeLoader` still handles `:export` compatibility by exposing exports as top-level properties on the emitted object. It also adds `__hash__` for old generated-code compatibility, but the new runtime uses sheet IDs and its own cache.
-- `dimensions.width` when any style object has `__hasMedia`.
-- specific variables listed in `__vars`.
+The loader is CommonJS because Babel and webpack loader APIs are synchronous CommonJS. In normal Node >=22 usage it can require the ESM package directly. Jest's CommonJS runtime cannot, so plugin tests use the Teamplay-style TS/Jest setup and a test-only child-process fallback when Jest intercepts ESM loading.
-The cached runtime depends on `teamplay` being installed. It is selected explicitly with `cache: 'teamplay'` or implicitly by importing `observer` from `teamplay` or `startupjs`.
+Metro separate-file support lives in `packages/bundler`. Inline templates do not need Metro loader setup.
## Component Parts
-Parts are a two-sided compile-time and runtime protocol.
-
-Parts are only addressable from the outside. A component styles its own elements with its own class selectors, such as `.text`; parent components use `:part(text)` against the child's exposed `part='text'` element.
+Parts are a compile-time/runtime protocol.
-On the parent side, a selector like:
+On the parent side:
```stylus
.card:part(title)
color red
```
-is compiled as a selector that `matcher()` returns under `titleStyle` when the parent element has styleName `card`.
+resolves under `titleStyle` when the parent element has `styleName='card'`.
-On the child side, JSX like:
+On the child side:
```jsx
function Card ({ title }) {
@@ -362,9 +365,9 @@ function Card ({ title }) {
}
```
-is rewritten so the closest likely React component accepts `titleStyle` and appends it to the element's root `style` prop. If props are destructured, the Babel plugin injects missing part style variables into the destructuring pattern. If no props parameter exists, it creates one.
+is rewritten so the closest likely React component accepts `titleStyle` and appends it to that element's `style` prop. If props are destructured, the Babel plugin injects missing part style variables into the destructuring pattern. If no props parameter exists, it creates one.
-`part='root'` is special. It maps to `style`, so parent styles for a component's own class can reach the component's root element without a `rootStyle` prop.
+`part='root'` maps to the normal `style` prop.
Part names must be statically knowable. Supported `part` values are:
@@ -372,33 +375,9 @@ Part names must be statically knowable. Supported `part` values are:
- arrays of string literals and object expressions.
- object expressions with static keys and dynamic truthy/falsy values.
-Unsupported dynamic part names intentionally throw at build time.
-
-## CSS Semantics and Limits
-
-Supported features are constrained by React Native style capabilities and `@startupjs/css-to-react-native-transform`.
+Unsupported dynamic part names throw at build time.
-Supported in current code and docs:
-
-- class selectors and compound class selectors.
-- `&` parent selector in Stylus.
-- `:part(name)` and `::part(name)`.
-- CSS variables in full or compound values.
-- media queries.
-- viewport units through the vendored dynamic style processor.
-- keyframes, animation, and transition output from the CSS-to-RN transformer.
-- `u` unit, where `1u = 8px`.
-- `:export` blocks in style files.
-
-Not supported by design:
-
-- expression interpolation inside `css` or `styl` template literals.
-- descendant selectors.
-- attribute selectors.
-- web pseudo-classes such as `:hover`, `:focus`, and `:active`.
-- pseudo-elements such as `::before` and `::after`.
-
-## Pug, Type Checking, and Linting
+## Pug, Type Checking, And Linting
CSSX does not implement the Pug parser itself. It wraps React Pug tooling:
@@ -414,79 +393,44 @@ npx cssxjs check [files...] [--project ]
and delegates to `packages/cssxjs/check.js`, which re-exports `@react-pug/check-types`.
-`eslint-plugin-cssxjs` is a package-name facade over `@react-pug/eslint-plugin-react-pug`, so changes to lint behavior usually belong upstream unless the wrapper API changes.
-
-## Metro and Separate Style Files
-
-Inline `css`/`styl` templates are handled by Babel and do not require Metro configuration.
-
-Separate `.cssx.styl` files need bundler support for hot reloading. `packages/bundler/metro-config.js`:
-
-- starts from Expo, React Native 0.73+, or older Metro default config.
-- sets `babelTransformerPath` to CSSX's Metro transformer.
-- adds `css` and `styl` to `resolver.sourceExts`.
-- enables package exports.
-- disables Expo's CSS support when using Expo defaults.
-
-`packages/bundler/metro-babel-transformer.js`:
-
-- compiles `.styl` through Stylus then CSS-to-RN.
-- compiles `.css` through CSS-to-RN.
-- passes resulting JS source to the upstream Metro Babel transformer.
-
-This path is primarily for imported style files and hot reloading. The preferred component-local path remains inline templates or Pug embedded style blocks.
-
-## Example App
-
-`example/` is a pure web demonstration:
-
-- `example/server.js` starts an HTTP server on port 3000.
-- `example/_serveClient.js` runs Babel with `cssxjs/babel`, then bundles with esbuild from memory.
-- `example/client.tsx` demonstrates Pug, embedded Stylus, `styleName`, `part`, media queries, and external `.cssx.styl` import.
-
-Run it with:
-
-```sh
-yarn start
-```
-
-from the repository root.
-
## Testing
-Root script:
+Run everything:
```sh
yarn test
```
-This loops over every `packages/*` directory and runs each package's `yarn test`.
-
Useful targeted tests:
```sh
-cd packages/runtime && yarn test
+cd packages/css-to-rn && npm test
cd packages/babel-plugin-rn-stylename-inline && yarn test
cd packages/babel-plugin-rn-stylename-to-style && yarn test
```
-Runtime tests live in `packages/runtime/test/*.mjs`.
+`@cssxjs/css-to-rn` tests:
+
+- `test/engine/**`: parser IR, value resolution, property transforms, resolver cascade, cache behavior.
+- `test/react/**`: variable batching, dependency tracking, media adapter invalidation, aborted-render safety, tracked cache references, React 19 hook/Suspense behavior.
Babel plugin tests use `babel-plugin-tester` and Jest snapshots in:
- `packages/babel-plugin-rn-stylename-inline/__tests__/`
- `packages/babel-plugin-rn-stylename-to-style/__tests__/`
-Many packages currently have placeholder tests that print `No tests yet`.
+The inline plugin test package uses a small TypeScript Jest transformer modeled after Teamplay because Jest cannot otherwise load TS/ESM workspace sources through custom export conditions.
## Maintenance Constraints
-- Treat `__CSS_GLOBAL__`, `__CSS_LOCAL__`, `__hash__`, `__vars`, and `__hasMedia` as cross-package contracts.
-- Keep Babel transform order intact unless the replacement order is tested.
-- Keep runtime import wrappers in `packages/cssxjs/runtime/*` compatible with the named `runtime` import used by the Babel plugin.
-- If selector matching changes, update `matcher` tests and process integration tests together.
-- If CSS variable metadata changes, update both cached and uncached runtime paths.
-- If media-query metadata changes, update dimensions invalidation in cached and uncached runtime paths.
-- If part injection changes, update tests for destructured props, named props, nested render functions, `root`, and dynamic parts.
-- If default style file extensions change, update docs, Babel plugin defaults, Metro expectations, and tests together.
-- Be careful with old package READMEs. Some historical README text still references StartupJS-era names or older defaults; prefer current code and `docs/` for public behavior.
+- Keep `__CSS_GLOBAL__`, `__CSS_LOCAL__`, and the Babel runtime call shape compatible unless both Babel plugins and runtime wrappers change together.
+- Keep compiled sheet IR JSON-serializable.
+- Keep `@cssxjs/css-to-rn` as the single owner of selector matching, value resolution, property transformation, caching, variables, and dimension/media dependency tracking.
+- Do not reintroduce Teamplay or `@nx-js/observer-util` as runtime cache/subscription requirements.
+- Keep Stylus-to-CSS separate from CSS-to-style transformation.
+- For selector or cascade changes, update resolver tests and Babel snapshots as needed.
+- For value syntax changes, update value resolver and transform tests together.
+- For interpolation changes, update inline Babel snapshots and resolver cache tests.
+- For part injection changes, update tests for destructured props, named props, nested render functions, `root`, and dynamic parts.
+- For public API changes, update `docs/`, `AGENTS.md`, and this file.
+- Be careful with historical READMEs and changelogs. Prefer current code, current docs, and this architecture document when they conflict.
diff --git a/docs/api/babel.md b/docs/api/babel.md
index 4c2c979..30dd1c7 100644
--- a/docs/api/babel.md
+++ b/docs/api/babel.md
@@ -2,6 +2,9 @@
CSSX uses a Babel preset to transform styles at build time.
+For CSS strings that are generated in the client at runtime, use the
+[Runtime Compilation API](/api/runtime) instead.
+
## cssxjs/babel
The Babel preset that transforms CSSX syntax.
@@ -21,7 +24,6 @@ module.exports = {
|--------|------|---------|-------------|
| `platform` | `'web'` \| `'ios'` \| `'android'` | `'web'` | Target platform |
| `reactType` | `'react-native'` \| `'web'` | auto | React target type |
-| `cache` | `'teamplay'` | auto | Caching library |
| `transformPug` | `boolean` | `true` | Enable Pug transformation |
| `transformCss` | `boolean` | `true` | Enable CSS transformation |
@@ -32,8 +34,7 @@ module.exports = {
module.exports = {
presets: [
['cssxjs/babel', {
- transformPug: false, // Disable pug if not using it
- cache: 'teamplay' // Force teamplay caching
+ transformPug: false // Disable pug if not using it
}]
]
}
@@ -61,7 +62,7 @@ You can also set platform-specific variables in your Stylus code:
## Caching
-When `cache: 'teamplay'` is set (or auto-detected), the Babel transform generates code that integrates with [teamplay](https://github.com/startupjs/teamplay) for optimized style memoization.
+CSSX uses the built-in resolver cache by default.
See the [Caching guide](/guide/caching) for more details.
@@ -103,6 +104,7 @@ The Babel preset converts this into optimized runtime code that:
- Compiles Stylus to style objects at build time
- Connects `styleName` to the compiled styles
- Injects part style props automatically
+- Re-renders only when used CSS variables or matching media queries change
## TypeScript
diff --git a/docs/api/css.md b/docs/api/css.md
index 3182fdb..290cdad 100644
--- a/docs/api/css.md
+++ b/docs/api/css.md
@@ -122,6 +122,60 @@ The custom `u` unit works in `css` too:
}
```
+Variables can appear anywhere CSS allows `var()`: whole values, parts of
+shorthands, comma-separated value chunks, and nested fallbacks.
+
+```css
+.card {
+ box-shadow: var(--shadow, 0 4px 12px rgba(0, 0, 0, 0.16));
+ border: var(--border-width, 1px) solid var(--border-color, #ddd);
+}
+```
+
+Provider/global CSS can define subtree-scoped variables with `:root`:
+
+```css
+:root {
+ --primary-color: oklch(62% 0.18 250);
+}
+```
+
+Those variables are scoped by `CssxProvider`, not stored as global defaults.
+
+### Modern Color Functions
+
+CSSX resolves `oklch()`, `oklab()`, and `color-mix()` to legacy `rgba(...)`
+strings so the same CSS works on React Native:
+
+```css
+.button {
+ background-color: color-mix(in oklch, var(--brand), white 20%);
+}
+```
+
+### JavaScript Interpolation
+
+Function-scoped `css` templates support JavaScript interpolation in CSS value
+positions:
+
+```jsx
+function Badge({ color, size }) {
+ return
+
+ css`
+ .badge {
+ background-color: ${color};
+ padding: ${size}px 12px;
+ }
+ `
+}
+```
+
+Interpolation is an alternative to `var()`. It is only supported in the same
+places a CSS value can use `var()`, and only inside function-scoped JS tagged
+templates. Module-level templates, imported CSS files, and runtime CSS strings
+must use plain CSS text.
+
### Part Selectors
```css
@@ -129,11 +183,70 @@ The custom `u` unit works in `css` too:
color: red;
}
-.button:part(text) {
+.button::part(text) {
font-weight: bold;
}
```
+Both `:part()` and `::part()` are supported.
+
+### Component Tag Selectors
+
+Provider/global CSS can target components wrapped with `themed()` by tag:
+
+```css
+Button {
+ background: var(--button-bg);
+}
+
+Button.primary:part(text) {
+ color: white;
+}
+```
+
+Tag selectors are intended for global component overrides. Class selectors still
+work as utility classes everywhere.
+
+### Hover and Active Styles
+
+CSSX maps `:hover` and `:active` to the same output as `:part(hover)` and
+`:part(active)`. Components can receive those props as `hoverStyle` and
+`activeStyle`.
+
+```css
+.button:hover {
+ background-color: #0056b3;
+}
+
+.button:active {
+ transform: scale(0.97);
+}
+```
+
+### Filters and Background Images
+
+React Native supports `filter` and experimental background gradients in current
+versions. CSSX passes `filter` through and maps `background-image` to
+`experimental_backgroundImage` on React Native.
+
+```css
+.hero {
+ filter: blur(8px) brightness(0.8);
+ background-image:
+ linear-gradient(0deg, white, rgba(238, 64, 53, 0.8), rgba(238, 64, 53, 0) 70%),
+ radial-gradient(circle, rgba(0, 0, 0, 0.2), transparent 70%);
+}
+```
+
+Only `linear-gradient()` and `radial-gradient()` background images are emitted
+for React Native. Other image values are ignored with a diagnostic.
+
+### Runtime CSS Strings
+
+For CSS text that is generated at runtime, use the
+[Runtime Compilation API](/api/runtime). Runtime strings must be plain CSS text
+and use `var()` for dynamic values.
+
## Limitations
The `css` template does **not** support:
@@ -141,6 +254,7 @@ The `css` template does **not** support:
- Stylus variables (`$var`)
- Stylus mixins
- Global `styles/index.styl` imports
+- JavaScript interpolation in module-level templates or runtime CSS strings
For these features, use the [styl template](/api/styl) instead.
@@ -154,9 +268,12 @@ For these features, use the [styl template](/api/styl) instead.
| Global imports | `styles/index.styl` | Not supported |
| `u` unit | Yes | Yes |
| CSS variables | Yes | Yes |
+| Function-scoped JS interpolation | Yes | Yes |
| Part selectors | Yes | Yes |
+| Runtime CSS strings | No | [Runtime API](/api/runtime) |
## See Also
- [styl Template](/api/styl) — Stylus syntax with variables and mixins
- [styleName Prop](/api/jsx-props) — Connect elements to styles
+- [Runtime Compilation](/api/runtime) — Compile generated CSS strings
diff --git a/docs/api/index.md b/docs/api/index.md
index 2502ccf..e2a8366 100644
--- a/docs/api/index.md
+++ b/docs/api/index.md
@@ -12,8 +12,15 @@ import {
variables,
setDefaultVariables,
defaultVariables,
- dimensions,
- matcher
+ cssx,
+ useRuntimeCss,
+ useCssVariable,
+ useCssVariableRaw,
+ useCssxSheet,
+ useCssxTemplate,
+ CssxProvider,
+ configureCssx,
+ themed
} from 'cssxjs'
```
@@ -27,7 +34,10 @@ import {
**Styling:**
- [styl() Function](/api/styl-function) — Apply styles via spread
- [JSX Props](/api/jsx-props) — `styleName`, `part`
+- [Theming](/guide/theming) — Provider style layers, themes, component tags, and theme assets
- [CSS Variables](/api/variables) — Runtime theming
+- [Runtime Compilation](/api/runtime) — Compile generated CSS strings at runtime
+- [Caching](/guide/caching) — Built-in resolver cache behavior
**Configuration:**
- [Babel Config](/api/babel) — Preset options
@@ -40,8 +50,15 @@ import {
| `styl` | Template literal / Function | Write styles in Stylus syntax, or apply styles via spread |
| `css` | Template literal | Write styles in plain CSS syntax |
| `pug` | Template literal | Write JSX in Pug syntax, with TypeScript expressions and embedded `style` blocks |
-| `variables` | Observable object | Set CSS variable values at runtime |
-| `setDefaultVariables` | Function | Set default CSS variable values |
-| `defaultVariables` | Object | Read-only default variable values |
-| `dimensions` | Observable object | Current screen width for media queries |
-| `matcher` | Function | Internal style matching (advanced) |
+| `variables` | Reactive object | Set CSS variable values at runtime; supports `.assign()`, `.set()`, `.clear()` |
+| `setDefaultVariables` | Function | Replace default CSS variable values |
+| `defaultVariables` | Reactive object | Default variable values; supports `.assign()`, `.set()`, `.clear()` |
+| `cssx` | Function | Resolve a runtime sheet and `styleName` to props |
+| `useRuntimeCss` | Hook | Compile runtime CSS text into a tracked sheet |
+| `useCssVariable` | Hook | Read a CSS variable as an RN-friendly value and subscribe to it |
+| `useCssVariableRaw` | Hook | Read a CSS variable as raw resolved CSS text |
+| `useCssxSheet` | Hook | Track an already compiled sheet |
+| `useCssxTemplate` | Hook | Track a compiled sheet with interpolation values |
+| `CssxProvider` | Component | Provide runtime options and global/scoped CSS to a subtree |
+| `themed` | Function | Give a component a CSS tag for provider/global component overrides |
+| `configureCssx` | Function | Configure global runtime defaults |
diff --git a/docs/api/jsx-props.md b/docs/api/jsx-props.md
index b234ca5..9debcde 100644
--- a/docs/api/jsx-props.md
+++ b/docs/api/jsx-props.md
@@ -66,26 +66,30 @@ The pattern:
### Dynamic Styles
-For truly dynamic values, combine `styleName` with the `style` prop:
+For CSS values that come from props, prefer function-scoped template
+interpolation:
```jsx
import { View, Text } from 'react-native'
-function ProgressBar({ progress }) {
+function ProgressBar({ progress, color }) {
return (
-
+ {progress}%
)
styl`
.bar
+ width ${progress}%
height 20px
- background-color #4caf50
+ background-color ${color}
`
}
```
+For ad hoc overrides, combine `styleName` with the regular `style` prop.
+
---
## part
@@ -128,26 +132,24 @@ See the [Component Parts guide](/guide/component-parts) for detailed examples.
---
-## matcher
+## cssx()
-The internal function that matches `styleName` values against compiled styles. Advanced use only.
+The low-level runtime helper that resolves a compiled or runtime sheet and
+returns props to spread onto a component. Most components should use
+`styleName`; use `cssx()` when CSS arrives as a runtime string or when a custom
+component cannot use the Babel transform.
**Signature:**
```ts
-function matcher(
- styleName: string,
- fileStyles: object,
- globalStyles: object,
- localStyles: object,
- inlineStyleProps: object
+function cssx(
+ styleName: string | array | object,
+ sheet: string | CompiledCssSheet | TrackedCssxSheet,
+ inlineStyleProps?: object
): object
```
-**Parameters:**
-- `styleName` - Space-separated class names (supports classnames-like syntax)
-- `fileStyles` - Styles from the imported CSS file
-- `globalStyles` - Module-level `styl` styles
-- `localStyles` - Function-level `styl` styles
-- `inlineStyleProps` - Inline style overrides
+`cssx()` returns an object with `style` and any part style props such as
+`titleStyle`, `hoverStyle`, or `activeStyle`.
-**Returns:** An object with style props, including `style` and any `{part}Style` props.
+See [Runtime Compilation](/api/runtime) for generated CSS strings, diagnostics,
+tracking, and caching behavior.
diff --git a/docs/api/runtime.md b/docs/api/runtime.md
new file mode 100644
index 0000000..0fd1997
--- /dev/null
+++ b/docs/api/runtime.md
@@ -0,0 +1,265 @@
+# Runtime Compilation
+
+Runtime compilation is for CSS text that is not known during Babel compilation,
+for example CSS generated by an AI system, loaded from a CMS, or edited inside a
+client-side builder.
+
+Most app code should still use `styleName` with `css` or `styl` templates. Use
+the runtime API when the CSS source is a string at render time.
+
+## Basic Usage
+
+```jsx
+import { cssx, useRuntimeCss } from 'cssxjs'
+
+function Button({ generatedCss, disabled, label }) {
+ const sheet = useRuntimeCss(generatedCss)
+
+ return (
+
+ {label}
+
+ )
+}
+```
+
+`useRuntimeCss()` compiles the string into a tracked sheet. `cssx()` resolves a
+`styleName` against that sheet and returns props such as `style`, `labelStyle`,
+`hoverStyle`, and `activeStyle`.
+
+## CSS Input
+
+Runtime input must be plain CSS text:
+
+```css
+.root {
+ padding: 12px 16px;
+ background: var(--button-bg, #1677ff);
+}
+
+.root.disabled {
+ opacity: 0.5;
+}
+
+.label {
+ color: var(--label-color, white);
+}
+```
+
+Runtime strings do not support Stylus syntax or JavaScript template
+interpolation. Use `var()` for dynamic values in generated CSS.
+
+## API
+
+```ts
+useRuntimeCss(cssText, options?)
+cssx(styleName, sheet, inlineStyleProps?, options?)
+```
+
+`styleName` accepts the same shapes as the JSX prop:
+
+```jsx
+cssx('card', sheet)
+cssx(['card', variant, { selected, disabled }], sheet)
+```
+
+`sheet` can be:
+
+- the `TrackedCssxSheet` returned by `useRuntimeCss()`
+- an already compiled sheet passed through `useCssxSheet()`
+- an array of sheets, ordered from lowest to highest priority
+
+`inlineStyleProps` uses the same prop names that components receive:
+
+```jsx
+
+```
+
+Inline styles have the highest priority.
+
+## Diagnostics
+
+Runtime compilation is graceful by default. Invalid CSS does not throw during
+render. The returned sheet contains diagnostics and any rules that could still
+be compiled.
+
+```jsx
+const sheet = useRuntimeCss(generatedCss)
+
+if (sheet.getSheet().diagnostics.length > 0) {
+ reportCssErrors(sheet.getSheet().diagnostics)
+}
+```
+
+Diagnostics include a severity, code, message, and line/column when available.
+This makes runtime compilation suitable for AI-generated CSS because the app can
+show or feed back errors without crashing.
+
+Build-time template compilation is stricter where Babel needs the module to be
+compiled correctly.
+
+## Variables And Updates
+
+Runtime CSS supports `var()` in the same places as build-time CSSX styles:
+whole values, parts of shorthands, comma-separated chunks, nested fallbacks, and
+complex values such as shadows and gradients.
+
+```css
+.card {
+ border: var(--border-width, 1px) solid var(--border-color, #ddd);
+ box-shadow: var(--shadow, 0 4px 12px rgba(0, 0, 0, 0.16));
+}
+```
+
+Only variables used by the resolved element are tracked. If `--border-color`
+changes, elements that used it update. If an unrelated variable changes, they do
+not.
+
+## Provider Styles
+
+`CssxProvider` can provide global CSS to a subtree through its `style` prop.
+Provider styles can define utility classes, component tag overrides, and scoped
+`:root` variables:
+
+```jsx
+import { CssxProvider, themed } from 'cssxjs'
+
+const Button = themed('Button', function Button({ children }) {
+ return (
+
+ {children}
+
+ )
+})
+
+function App() {
+ return (
+
+
+
+ )
+}
+```
+
+Nested providers override outer `:root` variables for their subtree. Runtime
+`variables['--name']` still has higher priority than provider `:root` values.
+Compiled provider sheets may also use template interpolation inside `:root`
+custom property values, so a precompiled provider layer can pass dynamic theme
+tokens through `{ sheet, values }`.
+
+Use `themed(tagName, Component)` for components that should be addressable by
+tag selectors in provider/global CSS. Class selectors remain global utilities
+and do not require a tag.
+
+## Reading Variables In JS
+
+Use `useCssVariable()` when component logic needs the resolved value:
+
+```jsx
+import { useCssVariable } from 'cssxjs'
+
+function Avatar() {
+ const size = useCssVariable('--avatar-size', '4u') // 32
+ return
+}
+```
+
+`useCssVariable()` returns an RN-friendly value: `2u` and `16px` become numbers,
+percentages stay strings, and modern color functions are normalized. Use
+`useCssVariableRaw()` when you need the raw resolved CSS string.
+
+## Media Queries
+
+Runtime CSS can use media queries:
+
+```css
+.layout {
+ padding: 24px;
+}
+
+@media (max-width: 640px) {
+ .layout {
+ padding: 12px;
+ }
+}
+```
+
+CSSX subscribes only to media queries used by committed renders. Dimension and
+media updates invalidate only affected elements.
+
+## Caching
+
+`useRuntimeCss()` recompiles only when the CSS string or target changes.
+`cssx()` caches the resolved props for the current inputs:
+
+- sheet identity and content hash
+- normalized `styleName`
+- runtime variable and media dependencies actually used
+- interpolation values for compiled templates
+- `JSON.stringify()` hash of inline style props
+
+When those inputs are unchanged, CSSX returns the same object references. When
+inputs change, it recalculates and replaces the previous cached entry instead of
+keeping unbounded variants.
+
+## Other Runtime Hooks
+
+Use these helpers for lower-level integrations:
+
+```ts
+useCssxSheet(compiledSheet, options?)
+useCssxTemplate(compiledSheet, values, options?)
+useCssxLayer(input, options?)
+CssxProvider
+configureCssx(options)
+themed(tagName, Component)
+useCssVariable(name, fallback?)
+useCssVariableRaw(name, fallback?)
+```
+
+`useCssxSheet()` tracks an already compiled sheet. `useCssxTemplate()` is used by
+compiled local templates with JavaScript interpolation values. `useCssxLayer()`
+accepts strings, compiled sheets, tracked sheets, or layer objects and returns
+the tracked equivalent.
+
+`CssxProvider` and `configureCssx()` configure runtime defaults such as target
+and dimension debounce behavior. `CssxProvider` also accepts a `style` prop for
+subtree-scoped CSS.
+
+## Platform Resolution
+
+Import from `cssxjs` in application code:
+
+```js
+import { cssx, useRuntimeCss } from 'cssxjs'
+```
+
+CSSX resolves the correct web or React Native runtime through package export
+conditions. Expo and React Native use the React Native target; other bundlers
+use the web target by default.
+
+## When Not To Use It
+
+Use build-time `css` or `styl` templates when the CSS is authored in source
+files. Babel can then precompile the sheet, lower JavaScript interpolation, and
+connect `styleName` automatically.
+
+Runtime compilation is best reserved for CSS that truly arrives as data.
+
+## See Also
+
+- [Babel Config](/api/babel) - Build-time compilation
+- [css Template](/api/css) - Plain CSS templates
+- [JSX Props](/api/jsx-props) - `styleName`, `part`, and `cssx()`
+- [Caching](/guide/caching) - Resolver cache behavior
+- [CSS Variables](/api/variables) - Runtime theming
diff --git a/docs/api/styl.md b/docs/api/styl.md
index 5a3974a..96f2b40 100644
--- a/docs/api/styl.md
+++ b/docs/api/styl.md
@@ -208,6 +208,28 @@ CSSX adds a custom `u` unit where `1u = 8px` (Material Design grid):
See [CSS Variables](/api/variables) for runtime variable updates.
+### JavaScript Interpolation
+
+Function-scoped `styl` templates support JavaScript interpolation in CSS value
+positions:
+
+```jsx
+function Button({ color, spacing }) {
+ return
+
+ styl`
+ .button
+ background ${color}
+ padding ${spacing}px 12px
+ `
+}
+```
+
+Interpolation is lowered through the same runtime value path as `var()`, so it
+can be used for whole values, parts of shorthands, and values nested inside
+functions. It is not supported in module-level templates because there is no
+render-time value array there.
+
## Selectors
| Selector | Description |
@@ -216,10 +238,12 @@ See [CSS Variables](/api/variables) for runtime variable updates.
| `.class1.class2` | Multiple classes (same element) |
| `&.modifier` | Modifier class (used within parent) |
| `:part(name)` | Part selector |
+| `:hover` | Emits `hoverStyle`, same as `:part(hover)` |
+| `:active` | Emits `activeStyle`, same as `:part(active)` |
> **Note:** Descendant selectors (`.parent .child`) are not supported. Apply modifiers directly to each element that needs styling.
-> **Note:** Pseudo-classes (`:hover`, `:focus`, `:active`, etc.) and pseudo-elements (`::before`, `::after`) are not supported. Use state-based modifiers instead (e.g., `&.focused`, `&.active`).
+> **Note:** `:focus`, other pseudo-classes, and pseudo-elements (`::before`, `::after`) are not supported. Use state-based modifiers for those cases.
### Part Selector
@@ -248,12 +272,13 @@ When the same property is defined in multiple places (highest to lowest):
## Limitations
-- No expression interpolations: `` styl`color ${color}` `` is not allowed
-- Must be a plain template literal
-- For dynamic values, use CSS variables or the `style` prop
+- JavaScript interpolation is local-only: module-level `styl` templates must be plain template literals
+- Interpolation is value-only, not selector or property-name interpolation
+- For runtime-generated plain CSS strings, use the [Runtime Compilation API](/api/runtime)
## See Also
- [css Template](/api/css) — Plain CSS alternative
- [styl() Function](/api/styl-function) — Apply styles via spread
- [styleName Prop](/api/jsx-props) — Connect elements to styles
+- [Runtime Compilation](/api/runtime) — Compile generated CSS strings
diff --git a/docs/api/variables.md b/docs/api/variables.md
index d3a25b8..7787f82 100644
--- a/docs/api/variables.md
+++ b/docs/api/variables.md
@@ -4,42 +4,54 @@ CSSX provides a reactive system for CSS variables that works at runtime.
## variables
-A reactive object for setting CSS variable values at runtime. Assigning values triggers automatic re-renders in components using those variables.
+A reactive object for setting CSS variable values at runtime. Assigning values
+triggers automatic re-renders in components using those variables.
-**Type:** `Observable>`
+**Type:** `CssxVariableStore`
```jsx
import { variables } from 'cssxjs'
// Set a variable
-variables['--primary-color'] = '#007bff'
+variables['--toast-offset'] = '1rem'
// Read a variable
-console.log(variables['--primary-color'])
+console.log(variables['--toast-offset'])
-// Set multiple variables
-Object.assign(variables, {
- '--primary-color': '#007bff',
- '--text-color': '#333'
+// Merge multiple variables
+variables.assign({
+ '--toast-offset': '1.5rem',
+ '--toast-color': 'var(--color-primary)'
})
+
+// Replace the whole runtime variable set
+variables.set({
+ '--toast-offset': '1rem'
+})
+
+// Clear all runtime variables
+variables.clear()
```
+Only valid CSS custom property names can be assigned. Names must start with
+`--`; invalid names throw.
+
**Reactivity:**
-When you assign to `variables`, all components using those CSS variables automatically re-render with the new values.
+When you assign to `variables`, components that used those specific variables in
+their resolved styles automatically re-render with the new values.
```jsx
import { Pressable, Text } from 'react-native'
-function ThemeToggle() {
- const toggleDark = () => {
- variables['--bg-color'] = '#1a1a1a'
- variables['--text-color'] = '#ffffff'
- // All components using these variables re-render
+function ToastOffsetButton() {
+ const moveToast = () => {
+ variables['--toast-offset'] = '2rem'
+ // Components using this variable re-render
}
return (
-
- Dark Mode
+
+ Move toast
)
}
@@ -49,7 +61,12 @@ function ThemeToggle() {
## setDefaultVariables
-Sets default values for CSS variables at app startup. These values are used as fallbacks when runtime values aren't set.
+Sets global default values for CSS variables. These values are used as
+fallbacks when runtime values and provider `:root` values are not set.
+
+For app themes, prefer `CssxProvider style` with `:root` variables. Use
+`setDefaultVariables()` for compatibility, low-level defaults, or non-React
+integrations.
**Signature:**
```ts
@@ -64,16 +81,9 @@ function setDefaultVariables(vars: Record): void
```jsx
import { setDefaultVariables } from 'cssxjs'
-// Call early in app initialization (e.g., App.jsx or index.js)
setDefaultVariables({
- '--primary-color': '#007bff',
- '--secondary-color': '#6c757d',
- '--text-color': '#333',
- '--background-color': '#fff',
- '--border-radius': '8px',
- '--spacing-sm': '8px',
- '--spacing-md': '16px',
- '--spacing-lg': '24px'
+ '--legacy-radius': '8px',
+ '--legacy-gap': '16px'
})
```
@@ -81,48 +91,64 @@ setDefaultVariables({
## defaultVariables
-A read-only object containing the default variable values set by `setDefaultVariables`.
+A reactive object containing default variable values. It supports the same
+`.assign()`, `.set()`, and `.clear()` methods as `variables`.
-**Type:** `Record`
+**Type:** `CssxVariableStore`
```jsx
import { defaultVariables } from 'cssxjs'
-console.log(defaultVariables['--primary-color']) // '#007bff'
+console.log(defaultVariables['--legacy-radius']) // '8px'
```
----
-
-## dimensions
+`setDefaultVariables(vars)` is an alias for `defaultVariables.set(vars)`.
-A reactive object containing the current screen width. Used internally for media query support.
+## Reading Variables In Components
-**Type:** `Observable<{ width: number }>`
+Use `useCssVariable()` when JavaScript needs the resolved value:
```jsx
-import { dimensions } from 'cssxjs'
+import { useCssVariable } from 'cssxjs'
-console.log(dimensions.width) // e.g., 375
+function Box() {
+ const gap = useCssVariable('--gap', '1rem') // 16
+ return
+}
```
-The `width` property automatically updates when the screen size changes, triggering re-renders in components using media queries.
+`useCssVariable()` subscribes only to the variables it resolves, including nested
+`var()` references. It returns RN-friendly values: `1rem` and `16px` become
+numbers, percentages remain strings, and modern color functions are normalized.
----
+Use `useCssVariableRaw()` to read raw resolved CSS text. Outside React, use
+`getCssVariable()` and `getCssVariableRaw()` for global variables only.
## Variable Resolution Order
CSS variables resolve in this priority (highest first):
-1. **Runtime:** `variables['--name']`
-2. **Default:** `setDefaultVariables({ '--name': value })`
-3. **Inline fallback:** `var(--name, fallback)`
+1. **Template interpolation values**
+2. **Runtime:** `variables['--name']`
+3. **Nearest provider `:root` variable**
+4. **Outer provider `:root` variables**
+5. **Default:** `defaultVariables['--name']`
+6. **Inline fallback:** `var(--name, fallback)`
```jsx
-setDefaultVariables({ '--color': 'blue' }) // Priority 2
-variables['--color'] = 'red' // Priority 1 (wins)
+setDefaultVariables({ '--color': 'blue' })
+variables['--color'] = 'red' // wins over provider and defaults
styl`
.box
- color var(--color, green) // Will be 'red'
+ color var(--color, green) // Will be 'red'
`
```
+
+`var()` supports nested fallbacks and complex CSS values:
+
+```stylus
+.card
+ box-shadow var(--card-shadow, 0 4px 12px rgba(0, 0, 0, 0.16))
+ border var(--border-width, 1px) solid var(--border-color, #ddd)
+```
diff --git a/docs/examples/theme.md b/docs/examples/theme.md
index fde255f..29610e1 100644
--- a/docs/examples/theme.md
+++ b/docs/examples/theme.md
@@ -1,142 +1,161 @@
# Theme System
-Complete dark/light theme implementation using CSS variables.
+Complete light/dark theme implementation using provider CSS variables and
+`useTheme()`.
-## Theme Configuration
+## Theme CSS
```jsx
// theme.js
-import { setDefaultVariables, variables } from 'cssxjs'
-
-const themes = {
- light: {
- '--bg-primary': '#ffffff',
- '--bg-secondary': '#f5f5f5',
- '--bg-tertiary': '#e0e0e0',
- '--text-primary': '#333333',
- '--text-secondary': '#666666',
- '--text-muted': '#999999',
- '--accent': '#007bff',
- '--accent-hover': '#0056b3',
- '--border': '#e0e0e0',
- '--shadow': 'rgba(0,0,0,0.1)'
- },
- dark: {
- '--bg-primary': '#1a1a1a',
- '--bg-secondary': '#2d2d2d',
- '--bg-tertiary': '#404040',
- '--text-primary': '#ffffff',
- '--text-secondary': '#b0b0b0',
- '--text-muted': '#808080',
- '--accent': '#bb86fc',
- '--accent-hover': '#9a67ea',
- '--border': '#404040',
- '--shadow': 'rgba(0,0,0,0.3)'
+import { css } from 'cssxjs'
+
+export const appTheme = css`
+ :root {
+ --background: oklch(1 0 0);
+ --foreground: oklch(0.145 0 0);
+ --card: oklch(0.98 0 0);
+ --card-foreground: var(--foreground);
+ --primary: oklch(0.58 0.22 260);
+ --primary-foreground: oklch(0.98 0.02 260);
+ --border: oklch(0.9 0 0);
+
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+ --color-card: var(--card);
+ --color-card-foreground: var(--card-foreground);
+ --color-primary: var(--primary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-border: var(--border);
}
-}
-// Initialize with saved preference or default to light
-import AsyncStorage from '@react-native-async-storage/async-storage'
-import { Appearance } from 'react-native'
+ :root.dark {
+ --background: oklch(0.145 0 0);
+ --foreground: oklch(0.985 0 0);
+ --card: oklch(0.205 0 0);
+ --card-foreground: var(--foreground);
+ --primary: oklch(0.72 0.16 260);
+ --primary-foreground: oklch(0.145 0 0);
+ --border: oklch(0.28 0 0);
+ }
+`
+```
-const systemDark = Appearance.getColorScheme() === 'dark'
-const initialTheme = systemDark ? 'dark' : 'light'
+## Theme Toggle
-setDefaultVariables(themes[initialTheme])
+```jsx
+// ThemeToggle.jsx
+import { css, useTheme } from 'cssxjs'
+import { Pressable, Text } from 'react-native'
-export async function setTheme(themeName) {
- Object.assign(variables, themes[themeName])
- await AsyncStorage.setItem('theme', themeName)
-}
+export default function ThemeToggle () {
+ const [theme, setTheme] = useTheme()
+ const dark = theme === 'dark'
-export function toggleTheme() {
- const current = variables['--bg-primary'] === themes.dark['--bg-primary']
- ? 'dark' : 'light'
- setTheme(current === 'dark' ? 'light' : 'dark')
+ return (
+ setTheme(dark ? 'light' : 'dark')}>
+
+ {dark ? 'Light mode' : 'Dark mode'}
+
+
+ )
+
+ css`
+ .themeToggle {
+ padding: 0.5rem 1rem;
+ background-color: var(--color-primary);
+ border-radius: 0.5rem;
+ }
+
+ .themeToggleText {
+ color: var(--color-primary-foreground);
+ font-weight: 600;
+ }
+ `
}
```
-## Themed App Component
+## Themed App
```jsx
// App.jsx
-import { styl } from 'cssxjs'
-import { View, Text, Pressable, ScrollView } from 'react-native'
-import { toggleTheme } from './theme'
+import { CssxProvider, css } from 'cssxjs'
+import { View, Text, ScrollView } from 'react-native'
+import { appTheme } from './theme'
+import ThemeToggle from './ThemeToggle'
-function App() {
+export default function App () {
return (
-
-
- My App
-
- Toggle Theme
-
-
-
-
-
- Welcome
-
- This card automatically updates when the theme changes.
-
+
+
+
+ My App
+
-
-
+
+
+
+ Welcome
+
+ This card automatically updates when the theme changes.
+
+
+
+
+
)
- styl`
- .app
- flex 1
- background var(--bg-primary)
-
- .header
- flex-direction row
- justify-content space-between
- align-items center
- padding 16px 24px
- background var(--bg-secondary)
- border-bottom-width 1px
- border-bottom-color var(--border)
-
- .logo
- font-size 24px
- font-weight 600
- color var(--text-primary)
-
- .theme-toggle
- padding 8px 16px
- background var(--accent)
- border-radius 6px
-
- .toggle-text
- color white
-
- .main
- padding 24px
-
- .card
- background var(--bg-secondary)
- border-radius 12px
- padding 24px
-
- .card-title
- margin-bottom 12px
- color var(--text-primary)
- font-size 18px
- font-weight 600
-
- .card-text
- color var(--text-secondary)
- line-height 24px
+ css`
+ .app {
+ flex: 1;
+ background-color: var(--color-background);
+ }
+
+ .header {
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ padding: 1rem 1.5rem;
+ background-color: var(--color-card);
+ border-bottom-width: 1px;
+ border-bottom-color: var(--color-border);
+ }
+
+ .logo {
+ font-size: 1.5rem;
+ font-weight: 600;
+ color: var(--color-foreground);
+ }
+
+ .main {
+ padding: 1.5rem;
+ }
+
+ .card {
+ background-color: var(--color-card);
+ border-radius: 0.75rem;
+ padding: 1.5rem;
+ }
+
+ .cardTitle {
+ margin-bottom: 0.75rem;
+ color: var(--color-card-foreground);
+ font-size: 1.125rem;
+ font-weight: 600;
+ }
+
+ .cardText {
+ color: var(--color-card-foreground);
+ line-height: 1.5rem;
+ }
`
}
```
## Key Concepts
-- **`setDefaultVariables`** for initial theme values
-- **`variables` object** for runtime theme switching
-- **Automatic re-renders** when variables change
-- **System preference detection** with `Appearance.getColorScheme()`
-- **Persistence** with AsyncStorage
+- **Provider CSS** with `:root` and `:root.dark` defines theme variables.
+- **`useTheme()`** returns `[theme, setTheme]` for toggles and settings UI.
+- **Persistence** is automatic: `localStorage` on web and AsyncStorage on React Native.
+- **Default startup** uses the `default` theme unless a user preference was saved.
+- **`theme='auto'`** follows the OS color scheme when a `dark` theme exists and no user preference overrides it.
+- **Controlled providers** can still force a subtree with `theme='dark'`, `theme='light'`, `theme='default'`, or a custom theme name.
diff --git a/docs/guide/animations.md b/docs/guide/animations.md
index b64ce5f..bd3755f 100644
--- a/docs/guide/animations.md
+++ b/docs/guide/animations.md
@@ -323,8 +323,14 @@ CSSX compiles animations in a way Reanimated v4 expects:
This means you write standard CSS and get native-compatible animations automatically.
+Animation and transition declarations use the same value resolver as other CSSX
+styles, so values may use `var()` and local template interpolation wherever CSS
+values are supported. Animation names, keyframe names, and the `@keyframes`
+block structure must remain statically knowable so CSSX can inline the matching
+keyframes for Reanimated.
+
## Next Steps
-- [Caching](/guide/caching) — Performance optimization with teamplay
+- [Caching](/guide/caching) — Built-in style caching
- [Examples](/examples/) — More code examples
- [styl Template](/api/styl) — Full syntax reference
diff --git a/docs/guide/caching.md b/docs/guide/caching.md
index a372bf2..adbe8c3 100644
--- a/docs/guide/caching.md
+++ b/docs/guide/caching.md
@@ -1,59 +1,36 @@
-# Caching with teamplay
+# Caching
-CSSX can cache style computations to improve rendering performance. This is particularly useful when components re-render frequently but their styles don't change.
-
-> **Note:** Caching currently requires the [teamplay](https://github.com/startupjs/teamplay) library. In future versions, CSSX may include built-in caching that works independently.
+CSSX caches resolved style props by default and tracks the runtime dependencies
+used by each element.
## How It Works
-Without caching, CSSX computes styles on every render:
+For Babel-compiled styles, generated code calls the CSSX runtime with the
+compiled sheet and the current `styleName` value. For runtime CSS strings,
+`useRuntimeCss()` compiles the string and wraps the compiled sheet in a tracked
+runtime object.
-```jsx
-import { View, Text } from 'react-native'
+The resolver caches the final props object for the current inputs:
-function Card({ title }) {
- // Style computation runs on EVERY render
- return (
-
- {title}
-
- )
+- the compiled sheet identity and content hash
+- the normalized `styleName`
+- local interpolation values
+- the JSON hash of inline style props
+- only the CSS variables and media queries that were actually used
- styl`
- .card
- padding 16px
- background white
- `
-}
-```
-
-With caching enabled, CSSX memoizes the results:
+When those inputs are unchanged, CSSX returns the same object references for
+`style`, `textStyle`, `hoverStyle`, `activeStyle`, and other part style props.
+That keeps React and React Native from seeing new style objects on every render.
-1. First render: computes and caches the style object
-2. Subsequent renders: returns the cached result instantly
-3. Cache invalidates automatically when:
- - CSS variable values change
- - Screen dimensions change (for media queries)
- - The `styleName` value changes
-
-## Setup
-
-### Step 1: Install teamplay
-
-```bash
-npm install teamplay
-```
+## No Setup Required
-### Step 2: Wrap Components with observer
-
-For caching to work, components using `styleName` must be wrapped with `observer`:
+Use `styleName` normally:
```jsx
-import { observer } from 'teamplay'
import { styl } from 'cssxjs'
import { View, Text } from 'react-native'
-const Card = observer(function Card({ title, children }) {
+function Card({ title, children }) {
return (
{title}
@@ -72,192 +49,90 @@ const Card = observer(function Card({ title, children }) {
.content
color #666
`
-})
-```
-
-That's it! The Babel transform automatically detects `observer` and enables the cached runtime.
-
-## Automatic Detection
-
-CSSX automatically enables caching when it detects `observer` imported from:
-- `teamplay`
-- `startupjs`
-
-No additional configuration is needed.
-
-## Manual Configuration
-
-You can force caching behavior in your Babel config:
-
-```js
-// babel.config.js
-module.exports = {
- presets: [
- ['cssxjs/babel', {
- cache: 'teamplay' // Always use teamplay caching
- }]
- ]
}
```
-## What Gets Cached
-
-The caching system stores:
-- Computed style objects for each unique `styleName` combination
-- Results of CSS variable substitutions
-- Media query evaluations
-
-### Cache Key Components
+The Babel preset inserts the runtime calls for you.
-Each cache entry is keyed by:
-1. The `styleName` value
-2. Current CSS variable values (if styles use `var()`)
-3. Current screen dimensions (if styles use media queries)
-4. Any inline style props
+## Dependency-Aware Updates
-### Automatic Invalidation
-
-The cache invalidates when reactive dependencies change:
+CSSX tracks the specific runtime dependencies used by each resolved element.
+Changing an unrelated variable does not invalidate that element.
```jsx
-import { variables } from 'cssxjs'
-import { observer } from 'teamplay'
-import { View, Text } from 'react-native'
+import { variables, styl } from 'cssxjs'
+import { View } from 'react-native'
-const ThemedCard = observer(function ThemedCard() {
- // Cache invalidates when --card-bg changes
- return (
-
- Themed content
-
- )
+function ThemedCard() {
+ return
styl`
.card
background var(--card-bg, white)
`
-})
+}
-// Later: changing this automatically re-renders affected components
-variables['--card-bg'] = '#f0f0f0'
+variables['--card-bg'] = '#f0f0f0' // ThemedCard updates
+variables['--text-color'] = 'red' // ThemedCard does not update
```
-## Performance Impact
-
-Caching is most beneficial when:
-- Components re-render frequently (lists, animations, form inputs)
-- Styles are complex (many classes, nested selectors)
-- Multiple components share the same styles
-
-Example with a list:
+Variable notifications are batched in a microtask. Media query updates use the
+runtime dimension store and browser media listeners when available, so CSSX only
+rerenders components whose committed media result changed. Web resize handling
+can be configured globally through `configureCssx()`.
```jsx
-import { observer } from 'teamplay'
-import { styl } from 'cssxjs'
-import { View, Text } from 'react-native'
-
-const ListItem = observer(function ListItem({ item, isSelected }) {
- return (
-
- {item.name}
- {item.price}
-
- )
+import { configureCssx } from 'cssxjs'
- styl`
- .item
- flex-direction row
- justify-content space-between
- padding 12px 16px
- border-bottom-width 1px
- border-bottom-color #eee
- &.selected
- background #e3f2fd
- .name
- font-weight 500
- .price
- color #666
- `
+configureCssx({
+ dimensionsDebounceMs: 50
})
-
-// Rendering 1000 items benefits significantly from caching
-function ProductList({ products, selectedId }) {
- return (
-
- {products.map(item => (
-
- ))}
-
- )
-}
```
-## Using with startupjs
+## Runtime CSS Strings
-If you're using the [startupjs](https://github.com/startupjs/startupjs) framework, caching is automatically configured. Just import `observer` from `startupjs`:
+For client-generated CSS, use `useRuntimeCss()` and `cssx()`. Runtime
+compilation has its own API reference covering diagnostics, subscriptions, and
+platform behavior: [Runtime Compilation](/api/runtime).
-```jsx
-import { observer, styl } from 'startupjs'
-import { View, Text } from 'react-native'
+## Inline Style Hashing
-export default observer(function MyComponent() {
- return (
-
- Content
-
- )
+Inline styles are deep-hashed with `JSON.stringify()`. This means callers can
+write natural inline objects without manually memoizing every object:
- styl`
- .box
- padding 16px
- `
-})
+```jsx
+
```
-## Best Practices
+If the inline style values serialize to the same JSON string, the cache can
+reuse the previous result.
-### Wrap All Styled Components
+## Template Interpolation Cache
-For consistent behavior, wrap any component that uses `styleName`:
+Function-scoped `css` and `styl` templates can use JavaScript interpolation in
+CSS value positions:
```jsx
-import { Pressable, Text } from 'react-native'
+function Button({ color }) {
+ return
-// Good: observer wrapper enables caching
-const Button = observer(function Button({ children }) {
- return (
-
- {children}
-
- )
- styl`.button { padding 12px 24px } .text { color white }`
-})
-
-// Without observer: no caching, styles compute every render
-function Button({ children }) {
- return (
-
- {children}
-
- )
- styl`.button { padding 12px 24px } .text { color white }`
+ css`
+ .button {
+ background-color: ${color};
+ }
+ `
}
```
-## Debugging
-
-To verify caching is working, you can check if components are using the teamplay runtime. In development, the imported runtime path will be one of:
-
-- `cssxjs/runtime/react-native-teamplay` (React Native with caching)
-- `cssxjs/runtime/web-teamplay` (Web with caching)
-- `cssxjs/runtime/react-native` (React Native without caching)
-- `cssxjs/runtime/web` (Web without caching)
+Each compiled template has one cache slot for its latest interpolation values.
+If `color` changes, CSSX recalculates the sheet result and replaces the previous
+cached variant instead of keeping every historical value combination.
## Next Steps
-- [Examples](/examples/) - Complete component examples
-- [API Reference](/api/) - Complete API documentation
+- [CSS Variables](/guide/variables) - Runtime theming
+- [Runtime Compilation](/api/runtime) - Generated CSS strings
+- [css Template](/api/css) - Plain CSS templates and interpolation
+- [Animations](/guide/animations) - Reanimated v4 output
diff --git a/docs/guide/index.md b/docs/guide/index.md
index 413f7e8..fa53dcd 100644
--- a/docs/guide/index.md
+++ b/docs/guide/index.md
@@ -113,31 +113,68 @@ styl`
`
```
-### Dynamic CSS Variables
+### CSS Variables
-Use CSS `var()` syntax with runtime updates:
+Use CSS `var()` syntax with provider-scoped theme variables:
```jsx
-import { variables } from 'cssxjs'
+import { CssxProvider, css } from 'cssxjs'
-// Change theme at runtime
-variables['--primary-color'] = isDarkMode ? '#fff' : '#000'
+const theme = css`
+ :root {
+ --primary: oklch(0.58 0.22 260);
+ --color-primary: var(--primary);
+ }
+`
+
+
+
+
+```
+
+### Provider Theming
+
+Use `CssxProvider style` for scoped theme variables, theme selection, global
+utility classes, and component tag overrides:
+
+```jsx
+import { CssxProvider, css } from 'cssxjs'
+
+const theme = css`
+ :root {
+ --primary: oklch(0.58 0.22 260);
+ --color-primary: var(--primary);
+ }
+
+ Button {
+ border-radius: var(--radius-md);
+ }
+`
+
+
+
+
```
-### Material Design Grid
+### Standard CSS Units
-Built-in `u` unit (1u = 8px) for consistent spacing:
+Use `rem`, CSS variables, and `calc()` for spacing:
```css
-.card
- padding 2u /* 16px */
- margin 1u /* 8px */
- gap 0.5u /* 4px */
+.card {
+ padding: 1rem;
+ gap: calc(var(--spacing) * 2);
+}
```
+The legacy `u` unit still compiles for migration (`1u = 8px`), but new styles
+should prefer standard CSS units.
+
### Performance Optimized
-Automatic style caching prevents unnecessary re-renders. With the optional teamplay integration, styles are memoized and only recalculated when dependencies change.
+Automatic style caching prevents unnecessary re-renders. Styles are memoized by
+sheet, `styleName`, inline styles, interpolation values, and only the variables
+or media queries that were actually used.
## How It Works
@@ -208,5 +245,6 @@ function App() {
- [Installation](/guide/installation) - Set up CSSX in your project
- [TypeScript Support](/guide/typescript) - Type-check Pug templates
- [Basic Usage](/guide/usage) - Learn the core concepts
+- [Theming](/guide/theming) - Provider styles, theme assets, and component tags
- [Component Parts](/guide/component-parts) - Style component internals
- [CSS Variables](/guide/variables) - Dynamic theming
diff --git a/docs/guide/installation.mdx b/docs/guide/installation.mdx
index fe8968b..5ff1d63 100644
--- a/docs/guide/installation.mdx
+++ b/docs/guide/installation.mdx
@@ -6,6 +6,18 @@ import { PackageManagerTabs } from '@theme'
+If you use CSSX theme persistence in React Native, also install AsyncStorage:
+
+```sh
+npm install @react-native-async-storage/async-storage
+# or
+yarn add @react-native-async-storage/async-storage
+# or
+pnpm add @react-native-async-storage/async-storage
+```
+
+Web theme persistence uses `localStorage` automatically.
+
## Configure Babel
Add the CSSX preset to your `babel.config.js`:
diff --git a/docs/guide/theming.md b/docs/guide/theming.md
new file mode 100644
index 0000000..9f9ee63
--- /dev/null
+++ b/docs/guide/theming.md
@@ -0,0 +1,368 @@
+# Theming
+
+CSSX theming is built from normal CSS:
+
+- `:root` defines scoped CSS variables for a provider subtree.
+- `:root.dark` and other `:root.` blocks define named theme overrides.
+- `CssxProvider style` supplies global/provider CSS.
+- `CssxProvider theme` selects the active theme.
+- Component tags and parts let provider CSS customize reusable components.
+
+Most apps should prefer provider CSS variables over imperative runtime variable
+mutation. Runtime variables still exist for escape hatches, but provider styles
+make the theme visible, scoped, and easy to override.
+
+## Provider Styles
+
+Use `CssxProvider` when using CSSX directly:
+
+```jsx
+import { CssxProvider, css } from 'cssxjs'
+
+const appStyle = css`
+ :root {
+ --primary: oklch(0.58 0.22 260);
+ --primary-foreground: oklch(0.98 0.02 260);
+ --color-primary: var(--primary);
+ --color-primary-foreground: var(--primary-foreground);
+ }
+
+ Button {
+ border-radius: var(--radius-md);
+ }
+
+ Button:part(text) {
+ font-weight: var(--font-weight-semibold);
+ }
+`
+
+export default function App () {
+ return (
+
+
+
+ )
+}
+```
+
+`style` accepts a compiled `css` sheet, a runtime CSS string, a tracked runtime
+sheet, or an array of those values. Later entries override earlier entries at
+the same priority layer.
+
+Nested providers are scoped. Inner provider variables override outer provider
+variables only for the inner subtree.
+
+## Theme Selection
+
+`theme` controls which `:root.` block is active:
+
+```jsx
+
+
+
+```
+
+Supported values:
+
+| Value | Behavior |
+| --- | --- |
+| `default` | Uses only `:root`. This is the initial root preference when there is no saved user preference. |
+| `auto` | Uses the OS color scheme and selects `dark` when the provider style defines `:root.dark`. |
+| `light` | Alias for `default` unless `:root.light` exists. |
+| `dark` | Uses `:root` plus `:root.dark`. |
+| custom name | Uses `:root` plus `:root.`. |
+
+When the root `CssxProvider` does not receive a `theme` prop, CSSX uses the
+persisted global preference. Without a saved preference, the root provider
+starts in the `default` theme so host UI such as React Navigation does not
+unexpectedly switch to dark mode. Use `useTheme()` to read and update the
+preference:
+
+```jsx
+import { useTheme } from 'cssxjs'
+import { Pressable, Text } from 'react-native'
+
+function ThemeToggle () {
+ const [theme, setTheme] = useTheme()
+ const dark = theme === 'dark'
+
+ return (
+ setTheme(dark ? 'light' : 'dark')}>
+ {dark ? 'Light mode' : 'Dark mode'}
+
+ )
+}
+```
+
+CSSX stores this preference in `localStorage` on web and in
+`@react-native-async-storage/async-storage` on React Native. If a provider
+receives an explicit non-`auto` `theme` prop, that prop forces the theme for its
+subtree; `useTheme().setTheme()` still updates the saved preference, but the
+forced subtree keeps using the prop value until it changes or is removed. At
+the root, `theme='auto'` uses the system color scheme as the initial preference,
+then a saved or user-selected preference takes priority.
+
+Define themes with variable-only root blocks:
+
+```css
+:root {
+ --background: oklch(1 0 0);
+ --foreground: oklch(0.145 0 0);
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+}
+
+:root.dark {
+ --background: oklch(0.145 0 0);
+ --foreground: oklch(0.985 0 0);
+}
+```
+
+Only custom properties are valid inside `:root` and `:root.` blocks.
+Normal style declarations inside those blocks are ignored and reported as
+diagnostics.
+
+## Theme Assets
+
+CSSX ships theme token assets:
+
+```jsx
+import tailwindTheme from 'cssxjs/themes/tailwind'
+import shadcnTheme from 'cssxjs/themes/shadcn'
+```
+
+These are normal CSSX sheets that can be passed to `CssxProvider`:
+
+```jsx
+import { CssxProvider, css } from 'cssxjs'
+import tailwindTheme from 'cssxjs/themes/tailwind'
+import shadcnTheme from 'cssxjs/themes/shadcn'
+
+const overrides = css`
+ :root {
+ --primary: oklch(0.56 0.22 255);
+ --color-primary: var(--primary);
+ }
+`
+
+function App () {
+ return (
+
+
+
+ )
+}
+```
+
+The public API is the entrypoint import. The source files are useful as variable
+references:
+
+- `packages/cssxjs/themes/tailwind.cssx.css`
+- `packages/cssxjs/themes/shadcn.cssx.css`
+
+Bare CSSX does not automatically install either theme. Frameworks or component
+libraries can choose to layer them for their own users.
+
+## Token Pattern
+
+The recommended token structure is:
+
+1. Raw scale tokens, such as Tailwind palette, spacing, font, radius, and
+ breakpoint variables.
+2. Semantic shadcn-style variables, such as `--primary`, `--background`, and
+ `--border`.
+3. Tailwind-compatible consumption variables, such as `--color-primary` and
+ `--color-background`.
+4. Component-specific variables, such as `--Button-radius`.
+
+Example:
+
+```css
+:root {
+ --primary: oklch(0.58 0.22 260);
+ --primary-foreground: oklch(0.98 0.02 260);
+ --color-primary: var(--primary);
+ --color-primary-foreground: var(--primary-foreground);
+
+ --Button-radius: var(--radius-md);
+}
+```
+
+Component styles should usually consume `--color-*` variables. App themes should
+usually override semantic variables first, then map them to `--color-*`.
+
+## Theme-Specific Styles
+
+Use built-in theme media aliases for normal styles that should only apply in a
+specific theme:
+
+```css
+Card {
+ box-shadow: var(--shadow-sm);
+}
+
+@media (--theme-dark) {
+ Card {
+ box-shadow: none;
+ border-color: var(--color-border);
+ }
+}
+```
+
+`--theme-dark`, `--theme-light`, `--theme-default`, and `--theme-` are
+reserved by CSSX. They match the active provider theme and can compose with
+ordinary media queries or custom media aliases.
+
+## Custom Media
+
+Provider styles can define standard `@custom-media` aliases:
+
+```css
+:root {
+ --tablet: 48rem;
+ --desktop: 64rem;
+}
+
+@custom-media --breakpoint-tablet (width >= var(--tablet));
+@custom-media --breakpoint-desktop (width >= var(--desktop));
+```
+
+Use the aliases from component styles or provider overrides:
+
+```css
+@media (--breakpoint-tablet) {
+ Button {
+ min-width: 12rem;
+ }
+}
+```
+
+CSSX provides fallback aliases for `mobile`, `tablet`, `desktop`, and `wide`
+when a provider does not define them.
+
+## Component Tags
+
+Use `themed(tagName, Component)` for components that should be globally
+customizable by provider CSS:
+
+```jsx
+import { themed } from 'cssxjs'
+import { Pressable, Text } from 'react-native'
+
+function Button ({ children }) {
+ return (
+
+ {children}
+
+ )
+
+ css`
+ .root {
+ background-color: var(--color-primary);
+ }
+
+ .text {
+ color: var(--color-primary-foreground);
+ }
+ `
+}
+
+export default themed('Button', Button)
+```
+
+Provider CSS can then target every themed button:
+
+```css
+Button {
+ min-height: 2.5rem;
+}
+
+Button:part(text) {
+ text-transform: uppercase;
+}
+```
+
+Class selectors remain utility classes. Component tags are for reusable
+components that explicitly opt in through `themed()`.
+
+## Parts
+
+`part` exposes semantic inner elements to parent/provider CSS:
+
+```jsx
+
+
+
+```
+
+Mappings:
+
+| Part | Prop |
+| --- | --- |
+| `root` | `style` |
+| `label` | `labelStyle` |
+| `icon` | `iconStyle` |
+
+Use `:part()` or `::part()` in provider or parent CSS:
+
+```css
+Button:part(icon) {
+ opacity: 0.8;
+}
+```
+
+Use direct `style` and `*Style` props as per-instance escape hatches.
+
+## Reading Theme Values In JS
+
+Prefer CSS for visual styling. When JavaScript needs a resolved token, use the
+CSSX hooks:
+
+```jsx
+import { useCssColor, useCssVariable, useMedia } from 'cssxjs'
+
+function Avatar () {
+ const online = useCssColor('success')
+ const size = useCssVariable('--Avatar-size', '2.5rem')
+ const media = useMedia()
+
+ return (
+
+ )
+}
+```
+
+`useCssColor('primary')` resolves `var(--color-primary)`. Passing
+`useCssColor('primary', 0.15)` mixes the color with transparent by 15 percent.
+Pass `var(--custom)` when you need an exact variable expression.
+
+Outside React, `getCssColor()` and `getCssVariable()` read global/default
+variables only. They are escape hatches and are not provider-scoped in this
+batch.
+
+## Runtime Variables
+
+The `variables` and `defaultVariables` stores remain available for imperative
+updates:
+
+```jsx
+import { variables } from 'cssxjs'
+
+variables.assign({
+ '--toast-offset': '1rem',
+ '--toast-color': 'var(--color-primary)'
+})
+```
+
+Use them when the value truly lives outside the provider tree or must be changed
+imperatively. For app themes, prefer `CssxProvider style`.
+
+## StartupJS
+
+When CSSX is used through StartupJS, `StartupjsProvider style` and
+`StartupjsProvider theme` forward to CSSX. Bare StartupJS does not include any
+theme assets by default. Component libraries such as startupjs-ui can add their
+own provider layer and still let app `StartupjsProvider style` override it.
diff --git a/docs/guide/usage.md b/docs/guide/usage.md
index a10b061..b7fcee9 100644
--- a/docs/guide/usage.md
+++ b/docs/guide/usage.md
@@ -36,21 +36,8 @@ function Button({ children, variant, disabled }) {
CSSX provides two template literals:
-- **`styl`** — [Stylus](https://stylus-lang.com/) syntax (recommended)
-- **`css`** — Plain CSS syntax
-
-### styl (Stylus)
-
-Clean, indentation-based syntax without braces or semicolons:
-
-```jsx
-styl`
- .card
- padding 16px
- background white
- border-radius 8px
-`
-```
+- **`css`** — Plain CSS syntax, recommended for new code and shared themes
+- **`styl`** — [Stylus](https://stylus-lang.com/) syntax for existing code or projects that still want Stylus preprocessing
### css (Plain CSS)
@@ -66,6 +53,19 @@ css`
`
```
+### styl (Stylus)
+
+Clean, indentation-based syntax without braces or semicolons:
+
+```jsx
+styl`
+ .card
+ padding 16px
+ background white
+ border-radius 8px
+`
+```
+
> See [styl Template](/api/styl) and [css Template](/api/css) for full syntax reference.
## Applying Styles with styleName
@@ -167,7 +167,26 @@ function Card({ variant, highlighted, compact, children }) {
## Dynamic Values
-For truly dynamic values, combine `styleName` with the `style` prop:
+For component props that should feed CSS values, use JavaScript interpolation in
+function-scoped `css` or `styl` templates:
+
+```jsx
+import { View } from 'react-native'
+
+function ProgressBar({ progress, color }) {
+ return
+
+ styl`
+ .bar
+ height 20px
+ width ${progress}%
+ background ${color}
+ `
+}
+```
+
+Interpolation is supported only in CSS value positions. For ad hoc overrides,
+combine `styleName` with the `style` prop:
```jsx
import { View } from 'react-native'
@@ -185,7 +204,8 @@ function ProgressBar({ progress }) {
}
```
-Or use [CSS Variables](/guide/variables) for runtime theming.
+Use [Theming](/guide/theming) for app-wide provider themes and shared tokens.
+Use [CSS Variables](/guide/variables) for imperative runtime variable updates.
## Style Placement
@@ -223,9 +243,9 @@ function ButtonA() {
}
```
-## The `u` Unit
+## The legacy `u` unit
-CSSX includes a custom unit where `1u = 8px` (Material Design grid):
+CSSX still supports the old custom unit where `1u = 8px`:
```stylus
.card
@@ -234,6 +254,8 @@ CSSX includes a custom unit where `1u = 8px` (Material Design grid):
gap 0.5u // 4px
```
+For new code, prefer `rem`, CSS variables, and `calc()`.
+
## CSS Support
CSSX runs on React Native, so not all CSS features are available.
@@ -246,11 +268,15 @@ CSSX runs on React Native, so not all CSS features are available.
| Compound selectors | `.card.featured` | Same element |
| Parent reference `&` | `&.active`, `&.disabled` | `styl` only |
| Part selectors | `:part(icon)`, `:part(text)` | |
+| Hover and active aliases | `:hover`, `:active` | Emits `hoverStyle` and `activeStyle` |
| CSS variables | `var(--color)`, `var(--size, 16px)` | |
+| JavaScript interpolation | ``color ${value}`` | Function-scoped templates only |
| Animations | `animation fadeIn 0.3s ease` | Reanimated v4 components only |
| Keyframes | `@keyframes fadeIn` | Reanimated v4 components only |
| Transitions | `transition background 0.2s` | Reanimated v4 components only |
| Media queries | `@media (min-width: 768px)` | |
+| Filters | `filter blur(8px)` | Current React Native versions |
+| Background gradients | `background-image linear-gradient(...)` | RN emits `experimental_backgroundImage` |
| Most CSS properties | `padding`, `margin`, `flex`, `color`, etc. | |
| Custom `u` unit | `padding 2u` | 1u = 8px |
@@ -260,44 +286,57 @@ CSSX runs on React Native, so not all CSS features are available.
| Feature | Alternative |
|---------|-------------|
-| `:hover` | Use `onPressIn`/`onPressOut` with `&.pressed` modifier |
| `:focus` | Use `onFocus`/`onBlur` with `&.focused` modifier |
-| `:active` | Use state with `&.active` modifier |
| `::before`, `::after` | Use a real element with its own styles |
| Descendant selectors | `.parent .child` — add modifier to child directly |
| Attribute selectors | `[type="text"]` — use class modifiers instead |
| `:first-child`, `:nth-child` | Handle in JS when rendering |
-| `linear-gradient`, `radial-gradient` | Use solid colors or images |
+| URL background images | Use platform image components |
-### Example: Replacing :hover
+### Hover and Active Props
-Instead of `:hover`, track state and use a modifier:
+CSSX emits `hoverStyle` and `activeStyle` for `:hover` and `:active`. Components
+can choose how to apply those props:
```jsx
import { useState } from 'react'
import { Pressable, Text } from 'react-native'
-function Button({ children, onPress }) {
+function InteractiveBox({ style, hoverStyle, activeStyle, children, onPress }) {
+ const [hovered, setHovered] = useState(false)
const [pressed, setPressed] = useState(false)
return (
setHovered(true)}
+ onHoverOut={() => setHovered(false)}
onPressIn={() => setPressed(true)}
onPressOut={() => setPressed(false)}
onPress={onPress}
>
- {children}
+ {children}
)
+}
+
+function Button({ children, onPress }) {
+ return (
+
+ {children}
+
+ )
styl`
.button
background #007bff
- &.pressed
+ &:hover
background #0056b3
+ &:active
+ transform scale(0.97)
+
.text
color white
`
@@ -338,5 +377,6 @@ function Button({ children, onPress }) {
## Next Steps
- [Component Parts](/guide/component-parts) — Style child component internals
+- [Theming](/guide/theming) — Provider styles, theme assets, and component tags
- [CSS Variables](/guide/variables) — Dynamic theming
- [styl Template API](/api/styl) — Full syntax reference including variables, mixins, selectors
diff --git a/docs/guide/variables.md b/docs/guide/variables.md
index b56eb35..3d63a4d 100644
--- a/docs/guide/variables.md
+++ b/docs/guide/variables.md
@@ -1,306 +1,206 @@
# CSS Variables
-CSSX supports CSS custom properties (`var()`) with a twist: you can change variable values at runtime, and your components will automatically re-render with the new values.
+CSSX supports standard CSS custom properties with runtime-aware resolution. Use
+`var(--name)` in CSS values, provider `:root` blocks for scoped theme defaults,
+and the imperative variable stores only for lower-level global overrides.
+
+For app-wide themes, start with [Theming](/guide/theming). This page focuses on
+`var()` usage, variable priority, and the imperative APIs.
## Basic Usage
-Use standard CSS `var()` syntax in your styles:
+Use normal CSS `var()` syntax:
```jsx
-import { styl } from 'cssxjs'
+import { css } from 'cssxjs'
import { Pressable, Text } from 'react-native'
-function ThemedButton({ children }) {
+function ThemedButton ({ children }) {
return (
-
- {children}
+
+ {children}
)
- styl`
- .button
- background-color var(--primary-color, #007bff)
- padding var(--button-padding, 12px 24px)
- border-radius var(--border-radius, 8px)
- .text
- color var(--text-color, white)
+ css`
+ .button {
+ background-color: var(--color-primary, #007bff);
+ padding: var(--Button-padding, 0.75rem 1rem);
+ border-radius: var(--radius-md, 0.5rem);
+ }
+
+ .text {
+ color: var(--color-primary-foreground, white);
+ }
`
}
```
-The second argument in `var()` is the fallback value used when the variable is not set.
-
-## Setting Default Variables
-
-Use `setDefaultVariables` to define your theme at app startup:
-
-```jsx
-// App.jsx or theme.js
-import { setDefaultVariables } from 'cssxjs'
-
-// Call this early in your app initialization
-setDefaultVariables({
- '--primary-color': '#007bff',
- '--secondary-color': '#6c757d',
- '--success-color': '#28a745',
- '--danger-color': '#dc3545',
- '--text-color': '#333',
- '--background-color': '#fff',
- '--border-radius': '8px',
- '--spacing-sm': '8px',
- '--spacing-md': '16px',
- '--spacing-lg': '24px'
-})
-```
-
-These values take precedence over inline fallbacks in `var()`.
+The second argument in `var()` is the fallback used when the variable cannot be
+resolved.
-## Dynamic Variables (Runtime Updates)
+## Provider Variables
-Import `variables` to change values at runtime:
+Provider styles define scoped variables with `:root`:
```jsx
-import { useState } from 'react'
-import { variables } from 'cssxjs'
-import { Pressable, Text } from 'react-native'
+import { CssxProvider, css } from 'cssxjs'
+
+const appTheme = css`
+ :root {
+ --primary: oklch(0.58 0.22 260);
+ --primary-foreground: oklch(0.98 0.02 260);
+ --color-primary: var(--primary);
+ --color-primary-foreground: var(--primary-foreground);
+ --Button-padding: 0.75rem 1rem;
+ }
-function ThemeToggle() {
- const [isDark, setIsDark] = useState(false)
-
- const toggleTheme = () => {
- const newIsDark = !isDark
- setIsDark(newIsDark)
-
- if (newIsDark) {
- variables['--primary-color'] = '#bb86fc'
- variables['--background-color'] = '#121212'
- variables['--text-color'] = '#ffffff'
- } else {
- variables['--primary-color'] = '#007bff'
- variables['--background-color'] = '#ffffff'
- variables['--text-color'] = '#333333'
- }
+ :root.dark {
+ --primary: oklch(0.72 0.16 260);
+ --primary-foreground: oklch(0.145 0 0);
}
+`
+export default function App () {
return (
-
- {isDark ? 'Light Mode' : 'Dark Mode'}
-
+
+
+
)
}
```
-When you assign to `variables`, all components using those variables automatically re-render.
-
-## Variable Priority
+Nested providers can override outer provider variables for their subtree.
+Root/theme blocks accept only CSS custom property declarations.
-Variables are resolved in this order (highest priority first):
+## Imperative Variables
-1. **Runtime variables** (`variables['--name']`)
-2. **Default variables** (`setDefaultVariables()`)
-3. **Inline fallback** (`var(--name, fallback)`)
+Use `variables` when a global value is controlled outside the provider tree or
+must be mutated imperatively:
```jsx
-setDefaultVariables({ '--color': 'blue' }) // Priority 2
-variables['--color'] = 'red' // Priority 1 (wins)
+import { variables } from 'cssxjs'
-styl`
- .box
- color var(--color, green) // Will be 'red'
-`
+variables['--toast-offset'] = '1rem'
+
+variables.assign({
+ '--toast-offset': '1.5rem',
+ '--toast-color': 'var(--color-primary)'
+})
+
+variables.clear(['--toast-offset'])
```
-## Using Variables in Complex Values
+When you assign to `variables`, components that used those exact variables in
+their resolved styles automatically re-render. Unrelated variable changes do
+not invalidate the component.
-Variables work within compound CSS values:
+`defaultVariables` is a lower-priority fallback store:
```jsx
-styl`
- .card
- box-shadow var(--shadow-x, 0) var(--shadow-y, 4px) var(--shadow-blur, 8px) var(--shadow-color, rgba(0,0,0,0.1))
+import { defaultVariables, setDefaultVariables } from 'cssxjs'
- border var(--border-width, 1px) solid var(--border-color, #ddd)
+defaultVariables.assign({
+ '--legacy-radius': '0.5rem'
+})
- transform translateX(var(--translate-x, 0)) scale(var(--scale, 1))
-`
+setDefaultVariables({
+ '--legacy-gap': '1rem'
+})
```
-## Practical Example: Theme System
+`setDefaultVariables(vars)` is a compatibility alias for
+`defaultVariables.set(vars)`. Prefer provider `:root` variables for app themes.
-Here's a complete theming implementation:
-
-```jsx
-// theme.js
-import { setDefaultVariables, variables } from 'cssxjs'
-
-const lightTheme = {
- '--bg-primary': '#ffffff',
- '--bg-secondary': '#f5f5f5',
- '--text-primary': '#333333',
- '--text-secondary': '#666666',
- '--accent': '#007bff',
- '--border': '#e0e0e0'
-}
+## Variable Priority
-const darkTheme = {
- '--bg-primary': '#1a1a1a',
- '--bg-secondary': '#2d2d2d',
- '--text-primary': '#ffffff',
- '--text-secondary': '#b0b0b0',
- '--accent': '#bb86fc',
- '--border': '#404040'
-}
+Variables resolve in this order, from highest to lowest priority:
-// Initialize with light theme
-setDefaultVariables(lightTheme)
+1. Template interpolation values used by the current style layer.
+2. Runtime variables (`variables['--name']`).
+3. Nearest provider `:root` variable.
+4. Outer provider `:root` variables.
+5. Default variables (`defaultVariables['--name']`).
+6. Inline fallback (`var(--name, fallback)`).
-export function setTheme(theme) {
- const values = theme === 'dark' ? darkTheme : lightTheme
- Object.assign(variables, values)
-}
+```jsx
+setDefaultVariables({ '--color': 'blue' })
+variables['--color'] = 'red'
+```
-export function getTheme() {
- return variables['--bg-primary'] === darkTheme['--bg-primary']
- ? 'dark'
- : 'light'
+```css
+.box {
+ color: var(--color, green); /* resolves to red */
}
```
-```jsx
-// App.jsx
-import { styl } from 'cssxjs'
-import { View, Text, Pressable } from 'react-native'
-import { setTheme } from './theme'
-
-function App() {
- return (
-
-
- My App
- setTheme('dark')}>
- Dark
-
- setTheme('light')}>
- Light
-
-
-
- Content here
-
-
- )
-
- styl`
- .app
- flex 1
- background var(--bg-primary)
-
- .header
- background var(--bg-secondary)
- padding 16px
- border-bottom-width 1px
- border-bottom-color var(--border)
+## Complex Values
- .title
- font-size 20px
- color var(--text-primary)
+Variables work inside shorthands, nested fallbacks, comma-separated chunks, and
+supported CSS functions:
- .content
- padding 24px
-
- .text
- color var(--text-primary)
- `
+```css
+.card {
+ border: var(--border-width, 1px) solid var(--color-border, #ddd);
+ box-shadow: var(--shadow-x, 0) var(--shadow-y, 0.25rem) var(--shadow-blur, 0.75rem) var(--shadow-color, rgba(0, 0, 0, 0.16));
+ background-image: var(--hero-gradient, linear-gradient(0deg, white, transparent));
}
```
-## Variables with `u` Units
+Nested fallbacks are supported:
-Combine CSS variables with the `u` unit system:
-
-```jsx
-setDefaultVariables({
- '--card-padding': '2u', // 16px
- '--button-height': '5u', // 40px
- '--spacing': '1u' // 8px
-})
+```css
+.text {
+ color: var(--Button-text-color, var(--color-primary-foreground, white));
+}
```
-## Tips and Best Practices
+Cycles and unresolved variables invalidate only the containing declaration.
-### Naming Convention
+## Reading Variables In JS
-Use a consistent naming scheme:
+Use `useCssVariable()` when component logic needs a resolved value:
```jsx
-setDefaultVariables({
- // Colors
- '--color-primary': '#007bff',
- '--color-secondary': '#6c757d',
- '--color-background': '#fff',
- '--color-text': '#333',
-
- // Typography
- '--font-size-sm': '12px',
- '--font-size-md': '14px',
- '--font-size-lg': '18px',
-
- // Spacing
- '--space-xs': '4px',
- '--space-sm': '8px',
- '--space-md': '16px',
- '--space-lg': '24px',
-
- // Components
- '--button-bg': 'var(--color-primary)',
- '--button-text': '#fff',
- '--card-shadow': '0 2px 8px rgba(0,0,0,0.1)'
-})
-```
+import { useCssVariable } from 'cssxjs'
-### Always Provide Fallbacks
-
-In case a variable isn't set, provide sensible defaults:
+function Avatar () {
+ const size = useCssVariable('--Avatar-size', '2.5rem')
+ return
+}
+```
-```stylus
-.button
- // Good - has fallback
- background var(--button-bg, #007bff)
+`useCssVariable()` is provider-aware and subscribes only to the variables it
+actually resolves, including nested `var()` dependencies. It returns
+React-Native-friendly values: `16px` becomes `16`, `0.5rem` becomes `8`,
+percentages remain strings, and computed colors become compatible color
+strings.
- // Risky - no fallback
- background var(--button-bg)
-```
+Use `useCssVariableRaw()` to read the raw resolved CSS text. Outside React,
+`getCssVariable()` and `getCssVariableRaw()` read global/default variables
+only; they are not provider-scoped.
-### Group Related Variables
+For colors, prefer `useCssColor()`:
```jsx
-// colors.js
-export const colors = {
- '--color-primary': '#007bff',
- '--color-secondary': '#6c757d',
- // ...
-}
+import { useCssColor } from 'cssxjs'
-// spacing.js
-export const spacing = {
- '--space-sm': '8px',
- '--space-md': '16px',
- // ...
-}
+const primary = useCssColor('primary')
+const subtlePrimary = useCssColor('primary', 0.15)
+```
-// theme.js
-import { setDefaultVariables } from 'cssxjs'
-import { colors } from './colors'
-import { spacing } from './spacing'
+## Naming Tips
-setDefaultVariables({
- ...colors,
- ...spacing
-})
-```
+- Use full CSS custom property names starting with `--`.
+- Use semantic names for theme tokens, such as `--primary`, `--background`,
+ and `--border`.
+- Use Tailwind-compatible `--color-*` variables for consumption, such as
+ `--color-primary`.
+- Use component prefixes for component-specific variables, such as
+ `--Button-height-m` or `--TextInput-border-color-focused`.
## Next Steps
-- [Pug Templates](/guide/pug) - Alternative JSX syntax
-- [Animations](/guide/animations) - CSS transitions and keyframes
-- [Caching](/guide/caching) - Performance optimization with teamplay
+- [Theming](/guide/theming) - Provider themes, `:root.dark`, and component tags
+- [Runtime CSS](/api/runtime) - Client-side CSS compilation
+- [Caching](/guide/caching) - Dependency-aware style caching
diff --git a/docs/index.md b/docs/index.md
index e17c67c..cf51ea6 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -24,12 +24,12 @@ features:
details: Style internal elements of child components using :part() selectors, similar to CSS Shadow Parts
icon: 🧩
- title: Dynamic CSS Variables
- details: Use var(--name) with runtime updates. Change themes instantly and components re-render automatically
+ details: Use provider-scoped :root variables, var(--name), and precise runtime subscriptions
icon: ⚡
- - title: 8px Grid System
- details: Built-in 'u' unit (1u = 8px) for consistent spacing following Material Design guidelines
+ - title: Provider Themes
+ details: Layer Tailwind tokens, shadcn-style semantics, component defaults, and app overrides
icon: 📐
- title: Performance Caching
- details: Optional style caching with teamplay prevents unnecessary re-renders for optimal performance
+ details: Built-in dependency-aware style caching reuses stable style props without observer wrappers
icon: 🚀
---
diff --git a/docs/migration-guides/0.4.md b/docs/migration-guides/0.4.md
new file mode 100644
index 0000000..1e36b6a
--- /dev/null
+++ b/docs/migration-guides/0.4.md
@@ -0,0 +1,307 @@
+# Upgrade 0.3 to 0.4
+
+- Change `cssxjs` in your `package.json` to `^0.4`
+- Change `eslint-plugin-cssxjs` and any directly installed `@cssxjs/*` packages to `^0.4`
+- Make sure your app is on React 19
+- Run your package manager install command after the version bump
+
+CSSX 0.4 is a breaking release focused on the new unified CSS engine, runtime CSS compilation, CSS variables, provider-scoped theming, and CSS-first customization.
+
+## Quick Migration Checklist
+
+1. Keep `cssxjs/babel` in your Babel presets.
+2. Replace any direct runtime CSS hook usage of `useCompiledCss()` with `useRuntimeCss()`.
+3. Move app-wide CSS variables, global classes, component tag overrides, and theme overrides into `CssxProvider style`.
+4. Use `theme='auto'` if you want the provider to select `:root.dark` on dark systems.
+5. Replace JS theme/color helpers with `useCssVariable()`, `useCssColor()`, `getCssVariable()`, and `getCssColor()`.
+6. Move media helpers to CSSX `useMedia()` and `@custom-media`.
+7. Replace new usage of `u` units with `rem`, `calc()`, or CSS variables. Existing `u` usage still compiles for migration.
+8. If you use component override styles, use component tag selectors and `:part()` / `::part()` from outside the component.
+9. Run your tests and app build so Babel recompiles all CSSX templates.
+
+## Unified CSS Engine
+
+CSSX now owns the full CSS-to-React-Native/web style pipeline through `@cssxjs/css-to-rn`.
+
+This replaces the previous split across CSSX, `css-to-react-native-transform`, and `css-to-react-native`. The compiler, runtime resolver, CSS variable system, media tracking, style caching, and React subscriptions now live in one engine.
+
+Most projects do not need to import `@cssxjs/css-to-rn` directly. Keep using the public `cssxjs` API:
+
+```js
+import {
+ css,
+ styl,
+ pug,
+ CssxProvider,
+ cssx,
+ useRuntimeCss,
+ useCssVariable,
+ useCssColor,
+ useMedia
+} from 'cssxjs'
+```
+
+## Runtime Caching
+
+CSSX now caches resolved style props by default. The cache tracks only the variables, media queries, dimensions, and inline values used by a resolved style.
+
+You no longer need a wrapper around every component for CSSX style invalidation. The old Babel option `cache: 'teamplay'` is still accepted for compatibility, but CSSX runtime caching is now handled by `@cssxjs/css-to-rn`.
+
+## Provider Styles And Themes
+
+Use `CssxProvider style` for global/provider CSS:
+
+```jsx
+import { CssxProvider, css } from 'cssxjs'
+
+const appStyle = css`
+ :root {
+ --primary: oklch(0.55 0.2 250);
+ --primary-foreground: white;
+ --color-primary: var(--primary);
+ --color-primary-foreground: var(--primary-foreground);
+ }
+
+ :root.dark {
+ --primary: oklch(0.75 0.18 250);
+ }
+
+ Button {
+ border-radius: var(--radius-md);
+ }
+
+ Button:part(text) {
+ font-weight: 600;
+ }
+`
+
+export default function App () {
+ return (
+
+
+
+ )
+}
+```
+
+`theme` values:
+
+- `default`: applies only `:root`.
+- `auto`: uses `dark` when the OS color scheme is dark and the provider styles define `:root.dark`.
+- `dark`: applies `:root` plus `:root.dark`.
+- `light`: aliases `default` unless an explicit `:root.light` exists.
+- any custom name: applies `:root` plus `:root.`.
+
+When the root provider has no saved theme preference and no `theme` prop, CSSX
+starts with `default`. Use `theme='auto'` when you intentionally want system
+light/dark selection.
+
+Root theme blocks accept only CSS custom properties. Use theme media for normal styles:
+
+```css
+@media (--theme-dark) {
+ Button {
+ box-shadow: none;
+ border-color: var(--color-border);
+ }
+}
+```
+
+## Theme Assets
+
+CSSX ships optional Tailwind and shadcn-compatible token layers:
+
+```js
+import tailwindTheme from 'cssxjs/themes/tailwind'
+import shadcnTheme from 'cssxjs/themes/shadcn'
+
+const appStyle = css`
+ :root {
+ --primary: oklch(0.52 0.2 250);
+ --color-primary: var(--primary);
+ }
+`
+
+
+
+
+```
+
+These assets are plain CSSX-compatible CSS. They do not enable Tailwind utility classes by themselves.
+
+## Variables
+
+CSS variables can be provided through `CssxProvider style` or through the imperative global variable store:
+
+```js
+import { variables } from 'cssxjs'
+
+variables['--accent'] = 'red'
+delete variables['--accent']
+
+variables.set('--accent', 'red')
+variables.assign({
+ '--surface': 'white',
+ '--text': 'black'
+})
+variables.clear()
+```
+
+Variable names must be valid CSS custom property names.
+
+In React, prefer provider-scoped hooks:
+
+```js
+import { useCssVariable, useCssColor } from 'cssxjs'
+
+function Avatar () {
+ const size = useCssVariable('--Avatar-size', '2.5rem')
+ const online = useCssColor('success')
+}
+```
+
+`useCssColor('primary')` resolves `var(--color-primary)`. Passing `var(--custom)` resolves that exact CSS expression. Passing `--primary` is intentionally unsupported because it is ambiguous.
+
+## Runtime CSS Compilation
+
+Use runtime compilation when CSS is generated on the client, for example by AI or by user-authored configuration:
+
+```jsx
+import { cssx, useRuntimeCss } from 'cssxjs'
+
+function Button ({ generatedCss, label }) {
+ const sheet = useRuntimeCss(generatedCss)
+
+ return (
+