npm.io
2.9.4 • Published 9h agoCLI

chaincss

Licence
MIT
Version
2.9.4
Deps
5
Size
3.0 MB
Vulns
0
Weekly
0

ChainCSS v2.9.2 npm version npm downloads license

Write styles with a fluent TypeScript API. ChainCSS compiles them into static CSS at build time, leaving zero styling runtime in production. When you need runtime values, only those values stay in JavaScript.

npm install chaincss

Why ChainCSS?

You write this:

const btn = chain()
  .bg('#6366f1')
  .color('#ffffff')
  .padding('12px 24px')
  .rounded(8)
  .hover().bg('#4f46e5').end()
  .$el('button');

Your users get this:

.chain-button {
  background-color: #6366f1;
  color: #ffffff;
  padding: 12px 24px;
  border-radius: 8px;
}
.chain-button:hover {
  background-color: #4f46e5;
}

Nothing ships to the browser. No runtime. No overhead.

ChainCSS Styled Components Vanilla Extract Tailwind
Runtime cost 0KB ~14KB 0KB 0KB
Dynamic styles Mixed mode
TypeScript First-class Partial
Atomic CSS Opt-in

Mixed Mode: The Killer Feature

Static properties compile to CSS at build time. Dynamic functions stay in JS — and only those functions.

import { chain } from 'chaincss'

export const btn = chain.dynamic()
  .bg('#6366f1')                                    // → static CSS
  .color('#ffffff')                                  // → static CSS
  .padding('12px 24px')                              // → static CSS
  .opacity(() => isActive ? 1 : 0.5)                 // → runtime only
  .shadow(() => isActive 
    ? '0 8px 25px rgba(16,185,129,0.6)' 
    : '0 2px 8px rgba(0,0,0,0.3)')                  // → runtime only
  .$el('btn')
import { useChainStyles } from 'chaincss/runtime'

function Button({ isActive }) {
  const classes = useChainStyles({ btn }, [isActive])
  return <button className={`${btnClass} ${classes.btn}`}>Click</button>
}

Quick Start: Vite

// vite.config.ts
import chaincss from 'chaincss/plugin/vite'

export default defineConfig({
  plugins: [chaincss(), react()]
})

Create a style file anywhere in src/:

// src/styles/button.chain.ts
import { chain } from 'chaincss'

export const btn = chain()
  .bg('#6366f1')
  .color('#ffffff')
  .padding('12px 24px')
  .rounded(8)
  .hover().bg('#4f46e5').end()
  .$el('button')

Import and use — it's a plain string:

import { btn } from '../styles/button.chain'

function Button() {
  return <button className={btn}>Click me</button>
}

The plugin handles file discovery, compilation, CSS generation, and HMR automatically.


CLI

npx chaincss init     # Create config
npx chaincss build    # Build once
npx chaincss watch    # Watch for changes

API at a Glance

Properties & Shorthands

Every CSS property is a chainable method. Shorthands keep things concise:

chain()
  .bg('#6366f1')       // background-color
  .fs(16)               // font-size (px added automatically)
  .fw(600)              // font-weight
  .rounded(8)           // border-radius
  .p('12px 24px')       // padding
  .flex()               // display: flex
Macros

One method, multiple declarations:

Macro Result
center() display: flex; align-items: center; justify-content: center
pill() Fully rounded pill with inline-flex centering
glass() Frosted glass backdrop blur
truncate() Single-line ellipsis truncation
skeleton() Loading skeleton animation
clickScale() Scale down on press

See all macros →

States & Nesting
chain()
  .hover().bg('red').end()
  .focus().outline('2px solid blue').end()
  .nest('.child', (c) => c.color('blue'))
  .media('(min-width: 768px)', (c) => c.flexDirection('row'))

Performance

Benchmarked on Node.js v22, Linux, 4 CPUs, 4GB RAM with realistic CSS fixtures:

Scenario Rules Time Output
Small 5 0.5ms 383B
Medium 50 2.4ms 11.5KB
Large 500 23ms 133KB
X-Large 2,000 127ms 530KB

Cold start: ~61ms. Compiler never ships to the browser.


What Happens Under the Hood

ChainCSS runs your styles through a build-time compiler that:

  • Fixes common mistakesflexboxflex, handpointer, typos in property names
  • Produces smaller CSS — shortens hex colors, removes redundant values
  • Checks accessibility — flags low contrast, missing focus indicators, small touch targets
  • Suggests patterns — detects repeated styles and recommends extracting them as recipes
  • Extracts atomic classes — optionally converts repeated declarations into utility classes

All of this happens at build time. Your users see none of it.


Framework Support

// React
import { btn } from './button.chain'
<button className={btn}>Click</button>

// Vue, Svelte, Solid — same pattern

Dynamic styles in any framework:

import { useChainStyles } from 'chaincss/runtime'
const classes = useChainStyles(styles, [dependencies])

React, Vue, Svelte, and SolidJS are optional peer dependencies.


Debug Mode

const debugChain = chain({ debug: true })
  .bg('red')
  .padding(16);

console.log(debugChain.explain().visualization);
┌──────────────────────────────────────────────────────────┐
│              ChainCSS Style Explanation                  │
├──────────────────────────────────────────────────────────┤
│ 📦 bg         → red        (static)                     │
│ 📦 padding    → 16         (static)                     │
├──────────────────────────────────────────────────────────┤
│ Static: 2 | Dynamic: 0                                  │
└──────────────────────────────────────────────────────────┘

API Reference

Export Description
chain(options?) Create a style chain
chain.dynamic(options?) Create a mixed-mode chain (static + dynamic)
compileToCSS(obj, opts?) Compile style object to CSS string
partitionForBuild(obj) Split static CSS from dynamic values
useChainStyles(styles, deps) React hook for dynamic styles
ChainCSSCompiler Full build compiler with pipeline control

Full documentation →


License

MIT

Author: Rommel Caneos

Keywords