chaincss
v2.15.19
Published
The design-system compiler with compile-time accessibility and multi-target emission.
Maintainers
Readme
ChainCSS
A zero-runtime, type-safe style compiler platform for modern web applications.
ChainCSS transforms fluent TypeScript style definitions into optimized CSS through a real compiler pipeline. Instead of generating styles at runtime, it analyzes, validates, optimizes, and emits deterministic outputs during the build, resulting in fast applications, framework-independent styling, and predictable performance.
npm install chaincssQuick Example
Vite
npm create vite@latest my-app -- --template react-ts
cd my-app && npm install chaincss// vite.config.ts
import chaincss from 'chaincss/vite'
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [chaincss({ atomic: true }), react()],
})// button.chain.ts
import { chain } from 'chaincss';
export const button = chain()
.box({ p: '12px 24px', br: 8 })
.background({ color: '#6366f1' })
.typography({ color: '#ffffff', fontWeight: '600' })
.hover()
.background({ color: '#4f46e5' })
.end()
.$el('btn');Use in your component
import { button } from './button.chain';
export function App() {
return <button className={button}>Click Me</button>;
}Why ChainCSS?
Unlike traditional CSS-in-JS libraries, ChainCSS is built around a compiler architecture.
Your styles become an Intermediate Representation (IR), pass through multiple optimization stages, and are emitted into deterministic outputs such as CSS, Atomic CSS, Tailwind configuration, Design Tokens, or Figma tokens.
This keeps the runtime extremely small while enabling sophisticated compile-time analysis and optimization.
Compiler Architecture
ChainCSS is built as a compiler platform—not simply a styling library.
| Component | Purpose | Benefit | |-----------|---------|---------| | Collector | Collects fluent style definitions | Unified authoring model | | Intermediate Representation (IR) | Canonical representation of every style | Enables compiler transformations | | Dependency Graph | Tracks relationships between rules, components, animations and tokens | Incremental compilation & graph analysis | | Symbol Table | Stores semantic compiler information | Efficient analysis and lookups | | Pass Scheduler | Orders compiler passes based on dependencies | Extensible and deterministic compilation | | Compiler Pipeline | Normalize → Validate → Analyze → Optimize → Lower | Independent, testable compiler passes | | Persistent Cache | Stores compiler state between builds | Faster rebuilds, survives process restarts | | Incremental Compiler | Recompiles only affected nodes via graph analysis | Efficient watch mode | | Emitter Registry | Generates multiple outputs from the same IR | CSS, Atomic CSS, Tailwind, Tokens, Figma, Graph JSON | | Plugin System | Extends any compiler phase | Custom analysis, validation, optimization and emitters |
Features
Structured Styling API
Group related CSS properties into 16 expressive typed methods instead of large flat objects.
chain()
.flex({ direction: 'column', align: 'center', gap: 16 })
.box({ p: 24, br: 12, w: '100%' })
.background({ color: '#6366f1' })
.typography({ color: 'white', fw: 600 })
.transition({ tr: 'all 0.2s ease' })
.hover()
.background({ color: '#4f46e5' })
.transform({ custom: 'scale(1.02)' })
.end()
.$el('btn')Intelligent Unit Inference
Numeric values automatically receive CSS units where appropriate. Knows which properties are unitless (lineHeight, opacity, zIndex, fontWeight, etc.).
chain().box({ width: 300, borderRadius: 8 })↓
width: 300px; border-radius: 8px;Transform Composition
Individual transform properties are composed into a single transform declaration in the correct order.
chain().box({ x: 10, y: 20, rotate: 45 })↓
transform: translateX(10px) translateY(20px) rotate(45deg);Mixed Static + Dynamic Rendering
ChainCSS is the only library that allows per-property mixing of static and dynamic styles in a single definition. Static values compile to zero-runtime CSS. Dynamic values become CSS custom properties. Both use the same fluent API—no separate recipes, no different syntax, no compromises.
export const btn = chain.dynamic()
.box({ padding: '12px 24px', borderRadius: 8 }) // -> static CSS
.background({ color: (ctx) => ctx.isActive ? '#6366f1' : '#a5b4fc' }) // -> runtime var
.shadow({ box: (ctx) => ctx.isActive
? '0 8px 25px rgba(99,102,241,0.4)'
: '0 2px 8px rgba(0,0,0,0.1)'
})
.$el('btn')10 properties are static. 2 are dynamic. The CSS file contains 12 declarations—10 real values and 2 var() placeholders. The JS file contains only the 2 functions that actually need runtime evaluation.
/* Generated CSS */
.chain-btn {
padding: 12px 24px;
border-radius: 8px;
background-color: var(--chain-btn-background-color);
box-shadow: var(--chain-btn-box-shadow);
}import { useChainStyles } from 'chaincss/runtime'
import { btn } from './button.chain'
function Button({ isActive }: { isActive: boolean }) {
const { classes, styleVars } = useChainStyles({ btn }, { isActive })
return <button className={classes.btn} style={styleVars}>Click</button>
}Framework Adapters
useChainStyles returns { classes, styleVars } in every framework. Each adapter uses the framework's native reactivity system.
| Framework | Hook | State Change |
|-----------|------|-------------|
| React | useChainStyles(styles, deps) | Re-renders via useMemo deps |
| Vue | useChainStyles(styles, refs) | Auto-unwraps ref() values, watches changes |
| Svelte | useChainStyles(styles, stores) | Subscribes to store changes |
| Solid | useChainStyles(styles, signals) | Auto-tracks signal access via createMemo |
Token Dependency Graph
Tokens are relationships—not just variables. Changing one token automatically updates derived colors, harmony palettes, and contrast colors. All computed in the OKLCH color space for perceptual accuracy.
tokens: {
relationships: [
{ type: 'derived', source: 'colors.primary.500', target: 'colors.primary.100', method: 'mix-white 80%' },
{ type: 'derived', source: 'colors.primary.500', target: 'colors.primary.600', method: 'shade 20%' },
{ type: 'contrast', foreground: 'colors.text.onPrimary', background: 'colors.primary.500', target: 4.5, autoFix: 'auto' },
{ type: 'harmony', source: 'colors.primary.500', targets: ['colors.accent.500', 'colors.accent.300'], rule: 'complementary' }
]
}Semantic Intent System
Design with intent, not implementation.
Instead of repeatedly describing how an element should look using individual CSS properties, ChainCSS lets you describe what the element is or what design concept it represents.
chain()
.intents(['card'])
.$el('product-card')The compiler resolves the intent into its underlying style definition and passes the result through the normal ChainCSS compilation pipeline.
"card"
↓
Intent Registry
↓
Intent Resolution
↓
Style IR
↓
Compiler Pipeline
↓
Optimized CSSBuilt-in intent vocabulary
ChainCSS ships with built-in intents covering common layout, component, semantic, and interaction concepts.
| Category | Built-in Intents |
|----------|------------------|
| Layout | center-content, stack, sidebar-layout, grid-list |
| Component | card, button-primary, button-secondary, input-field, modal, tooltip |
| Semantic | hero-section, sticky-header |
| Interaction | hover-lift, focus-ring |
For example, card expands into properties such as display: flex; flex-direction: column; overflow: hidden; including generated interaction states and accessibility metadata.
User-defined intents
The built-in vocabulary is not a closed list. Applications can introduce their own design vocabulary through chaincss.config.ts:
export default defineConfig({
intents: {
glass: {
name: 'glass',
category: 'visual',
description: 'Frosted glass effect',
properties: {
background: 'rgba(255,255,255,0.1)',
backdropFilter: 'blur(10px)',
border: '1px solid rgba(255,255,255,0.2)',
borderRadius: '12px',
}
}
}
});The intent can then be used directly:
chain()
.intents(['glass'])
.$el('panel')Composite intents
Intents can be composed from other intents:
intents: {
premium: {
name: 'premium',
category: 'composite',
resolve: () => ({
expandsTo: ['card', 'center-content'],
})
}
}chain()
.intents(['premium'])
.$el('premium-card')premium
├── card
└── center-contentMultiple intents
chain()
.intents(['center-content', 'sticky-header'])
.box({ minHeight: '400px' })
.$el('hero')Natural-language intent descriptions
chain()
.describe('glass elevated spacious')
.$el('navbar')The description is parsed into intent names and resolved through the same intent registry:
"glass elevated spacious"
↓
intent parser
↓
["glass", "elevated", "spacious"]
↓
intent registry
↓
Style IR
↓
CSS outputIntent System — Complete Feature Set
ChainCSS includes a four-phase intent system that turns design vocabulary into production CSS.
Phase 1: Relationships & Constraints
Intents know how they relate to each other.
chain()
.intents(['card', 'compact'])
.$el('card')requires— Auto-adds required intents (cardrequiresrounded)conflicts— Prevents invalid combinations (compactconflicts withspacious)enhances— Suggests great combinations (cardenhanceselevated,glass)maxCombinations— Limits stackingpriority— Resolution order
Phase 2: Themes & Variants
Intents adapt to context.
chain()
.intents(['card'])
.$el('card')Compiles both light and dark CSS:
/* Light */
.chain-card { background: #ffffff; }
/* Dark — automatically generated */
[data-theme="dark"] .chain-card { background: #1e293b; }Variants provide named alternatives:
chain()
.intents(['card'])
.$el('premium-card')
// variant: 'premium' → gradient backgroundPhase 3: Composition Patterns
Intents compose into more complex components.
chain()
.intents(['premium-card'])
.$el('premium-card')premium-card expands to:
card + glass + elevatedWith conditional behavior:
// Dark mode automatically:
// + glow
// - elevatedPhase 4: Natural Language
Describe designs in plain English.
chain()
.describe('dark premium card')
.$el('premium-dark-card')The deterministic parser:
- Maps synonyms (
frosted→glass) - Detects themes (
dark,light,high-contrast) - Detects variants (
premium,outlined,success) - Resolves composition (
premium card→premium-card) - Ignores stop words (
a,the,with,and)
Intent Resolution Pipeline
.describe('dark premium card')
↓
Parser: intents=['premium-card'], theme='dark'
↓
Composition: premium-card → card + glass + elevated
↓
Conditions: dark → + glow, - elevated
↓
Relationships: card requires rounded
↓
Themes: apply dark overrides
↓
Tokens: resolve $colors.*
↓
CSS: [data-theme="dark"] .chain-premium-card { ... }Intent Catalog
| Phase | Feature | Count | |-------|---------|-------| | Base | Built-in intents | 58+ | | Phase 1 | Relationship rules | 50+ relationships | | Phase 2 | Theme variants | dark, high-contrast | | Phase 2 | Named variants | premium, outlined, success, danger, warning | | Phase 3 | Composition intents | premium-card, premium-button, glass-panel, hero-banner, modal-glass, input-group | | Phase 4 | Natural language | Dictionary + synonyms + stop words |
Accessibility Compilation
Six WCAG checks run during compilation—not in CI, not in the browser. Milliseconds, not seconds.
| Check | Severity | Criterion | Auto-Fix |
|-------|----------|-----------|----------|
| Contrast ratio | Error | 1.4.3 AA (4.5:1) | Binary search in OKLCH |
| Font size minimum | Warning | 1.4.4 AA (12px) | max(12px, value) |
| Touch target size | Warning | 2.5.8 AA (44x44px) | min-width/height or ::after |
| Focus visible | Error | 2.4.7 AA | Auto-inject :focus-visible |
| Reduced motion | Warning | 2.3.3 AAA | Wrap in @media query |
| Hover without focus | Warning | 1.4.13 AA | Mirror to :focus-visible |
chaincss check --strict # CI gate — fails on errors
chaincss audit --fix --write # Auto-fix and write to token filesCompile-Time Optimization
Nine optimization passes run on every stylesheet:
| Pass | What It Does |
|------|-------------|
| AST Optimizer | Simplifies calc() expressions, constant folding, identity removal |
| Duplicate Declaration Detector | Removes overridden declarations, preserves intentional fallbacks |
| Dead Code Eliminator | Removes unreferenced rules via graph analysis |
| CSS Compressor | Shortens hex colors, removes zero units, compresses box-model shorthands |
| Accessibility Optimizer | Auto-fixes font sizes, touch targets, focus rings |
| Atomic Extractor | Extracts repeated declarations (3+ usages) to utility classes |
| Media Query Packer | Merges identical queries via AST comparison, sorts by breakpoint |
| Source Optimizer | Deduplicates identical rules across files |
| Specificity Sorter | Orders rules by CSS specificity |
Relationship Macros (100+)
Express CSS relationships rather than selectors. Zero runtime. All outputs are pure CSS.
| Instead of | Write | Category |
|---|---|---|
| .group:has(> :hover) > &:not(:hover) | .peerDim() | Interaction |
| &:has(> :nth-child(3)) | .hasCount({ count: 3 }) | Layout |
| &:focus-within label, &:has(input:not(:placeholder-shown)) label | .entangleFocus() | Accessibility |
| Full scroll-driven animation setup | .entangle('scroll', { opacity: '0->1', y: '20px->0' }) | Animation |
| 8+ properties for glass morphism | .glass() | Effects |
| Complex grid with subgrid + container queries | .bento() | Layout |
Multi-Target Emission
A single .chain.ts file compiles to multiple output formats simultaneously from the same IR.
chaincss build --target css,atomic-css,tailwind,design-tokens,figma,graph-json| Target | Output | Use Case |
|--------|--------|----------|
| css | styles.css | Standard CSS |
| atomic-css | atomic.css | Utility-first atomic classes |
| tailwind | tailwind.config.generated.js | Tailwind theme extension |
| design-tokens | design-tokens.json | Platform-agnostic token export |
| figma | figma-tokens.json | Round-trip to Figma Tokens Studio |
| graph-json | chaincss-graph.json | Dependency graph visualization |
Component Variants (Recipes)
Type-safe component variants with compound conditions. All variants compile at build time.
export const buttonVariants = recipe({
base: chain().box({ p: '8px 16px', br: 8 }).$el('btn'),
variants: {
color: {
primary: chain().background('#6366f1').typography({ color: 'white' }).$el(),
secondary: chain().background('#48bb78').$el(),
danger: chain().background('#f56565').$el(),
},
size: {
sm: chain().box({ p: '4px 8px' }).typography({ fs: 12 }).$el(),
md: chain().box({ p: '8px 16px' }).typography({ fs: 14 }).$el(),
lg: chain().box({ p: '12px 24px' }).typography({ fs: 16 }).$el(),
}
},
defaultVariants: { color: 'primary', size: 'md' },
})
buttonVariants({ color: 'secondary', size: 'lg' }) // -> merged StyleDefinitionFigma Integration
Bidirectional sync: designers change colors in Figma → Tokens Studio pushes to GitHub → GitHub Action runs entanglement → derived tokens update → contrast auto-fixes.
chaincss figma init --repo org/design-tokens --fileId abc123
chaincss entanglement --input tokens.json --watch --fix --figmaDev Server with HMR
Zero-config development server with hot module replacement, build error overlay, and persistent compiler state.
chaincss dev --port 3000- Instant HMR: CSS changes stream via SSE, no page reload
- Build error overlay: Compilation errors injected into the page
- Persistent state: Survives restarts for cold-start incremental builds
- Compiler stats: Real-time metrics at
/__chaincss_stats
CLI Commands
chaincss init
chaincss create app my-app --template react --pm pnpm
chaincss dev --port 3000
chaincss watch --verbose
chaincss build --minify --atomic --persistent
chaincss build --target css,tailwind,design-tokens,figma,graph-json
chaincss check --strict
chaincss audit --fail-on AA --fix --write
chaincss entanglement --input tokens.json --watch --fix
chaincss figma init --repo org/design-tokens
chaincss cache stats
chaincss cache validate
chaincss timeline list
chaincss timeline diff --snapshot1 0 --snapshot2 5Supported Frameworks
| Framework | Static | Dynamic | Adapter |
|-----------|:------:|:-------:|---------|
| React | Yes | Yes | useChainStyles + useMemo |
| Vue | Yes | Yes | useChainStyles + ref/watch |
| Svelte | Yes | Yes | useChainStyles + stores |
| Solid | Yes | Yes | useChainStyles + signals |
| Next.js | Yes | Yes | Server + Client components |
| Vanilla HTML | Yes | Yes | styleInjector |
Build Integrations
- Vite — Plugin with HMR, virtual CSS, persistent state
- Next.js — Server Component + Client Component support
- Webpack — Plugin with incremental compilation
- PostCSS — Drop-in plugin for existing pipelines
- CLI — Full-featured standalone build tool
Performance
| Rules | Compile Time | Output Size | |-------|--------------|-------------| | 5 | 0.54 ms | 0.4 KB | | 50 | 4.80 ms | 11.3 KB | | 500 | 93.06 ms | 133.0 KB | | 2,000 | 1,709.92 ms | 533.2 KB |
Measurements taken on a Lenovo G560 (Node.js v22.23.1, 4 CPUs, 4GB RAM).
Philosophy
ChainCSS approaches styling the same way modern language compilers approach source code.
Instead of treating CSS as strings, it treats styles as structured data that can be analyzed, validated, optimized, transformed, cached, and emitted into multiple targets.
The objective is to provide the ergonomics of a fluent styling API while leveraging compiler techniques typically found in tools such as TypeScript, Babel, SWC, and LLVM.
License
MIT
Author: Rommel Edorot Caneos
