sx-chain
v0.1.0
Published
Chainable styled elements for React - write styles and DOM in one expression
Maintainers
Readme
sx-chain
Chainable styled elements for React - write styles and DOM in one expression.
Features
- 🔗 Chainable API - Write styles, variants, and DOM elements in a single chain
- 🎯 Type-safe - Full TypeScript support with autocomplete
- ⚡ Zero runtime overhead - Uses native className strings
- 🎨 Framework agnostic - Works with Tailwind, vanilla CSS, CSS Modules (limited)
- 📦 Tiny bundle - ~2KB minified + gzipped
- 🚀 React 19+ - Built for React 19+ applications with full type safety
Requirements
- React 19.0.0 or higher - This library requires React 19+ for proper TypeScript type safety with custom components
- TypeScript 5.0+ (recommended for full autocomplete support)
Installation
npm install sx-chain
# or
yarn add sx-chain
# or
pnpm add sx-chainQuick Start
import sx from 'sx-chain';
// Define your styles (Tailwind classes or plain strings)
const styles = {
container: 'max-w-7xl mx-auto px-4',
title: 'text-2xl font-bold text-gray-900',
btn: {
base: 'px-4 py-2 rounded transition-colors',
primary: 'bg-blue-500 text-white hover:bg-blue-600',
secondary: 'bg-gray-500 text-white hover:bg-gray-600',
}
};
const s = sx(styles);
// Use it in your components
function App() {
return (
<s.container.div>
<s.title.h1>Hello World</s.title.h1>
<s.btn.primary.button>Click me</s.btn.primary.button>
</s.container.div>
);
}With Tailwind Autocomplete
For Tailwind class autocomplete in your IDE, use a template literal helper:
import sx from 'sx-chain';
// Helper function for Tailwind IntelliSense
const tw = (strings: TemplateStringsArray, ...values: any[]) => {
return String.raw(strings, ...values);
};
const styles = {
// Now you get full Tailwind autocomplete!
container: tw`max-w-7xl mx-auto px-4`,
title: tw`text-2xl font-bold text-gray-900`,
btn: {
base: tw`
px-4 py-2 rounded
transition-colors font-medium
`,
primary: tw`bg-blue-500 text-white hover:bg-blue-600`,
secondary: tw`bg-gray-500 text-white hover:bg-gray-600`,
}
};
const s = sx(styles);IDE Setup for Tailwind IntelliSense
VS Code:
- Install Tailwind CSS IntelliSense extension
- Add to your VS Code settings (
.vscode/settings.json):
{
"tailwindCSS.experimental.classRegex": [
["tw`([^`]*)`", "([^`]*)"]
]
}WebStorm / IntelliJ:
- Install the Tailwind CSS plugin
- The plugin should automatically detect
twtemplate literals - If needed, configure in Settings → Languages & Frameworks → Style Sheets → Tailwind CSS
- Add to Class Detection:
"classRegex": ["tw([^]*)"]`
- Add to Class Detection:
API
Basic Usage
const s = sx(styles);
// Any style can be applied to any HTML element
<s.container.div /> // <div className="max-w-7xl mx-auto px-4" />
<s.container.section /> // <section className="max-w-7xl mx-auto px-4" />
<s.title.h1 /> // <h1 className="text-2xl font-bold" />
<s.title.span /> // <span className="text-2xl font-bold" />Variants
Define variants using nested objects (works with tw helper too):
const tw = (strings: TemplateStringsArray, ...values: any[]) =>
String.raw(strings, ...values);
const styles = {
btn: {
base: tw`px-4 py-2 rounded`,
primary: tw`bg-blue-500 text-white`,
secondary: tw`bg-gray-500 text-white`,
danger: tw`bg-red-500 text-white`,
}
};
const s = sx(styles);
// Use variants
<s.btn.button>Base button</s.btn.button> // base styles only
<s.btn.primary.button>Primary</s.btn.primary.button> // base + primary
<s.btn.secondary.button>Secondary</s.btn.secondary.button> // base + secondary
<s.btn.danger.button>Danger</s.btn.danger.button> // base + dangerWith CSS Modules (Limited Support)
CSS Modules work at runtime but without TypeScript autocomplete:
import styles from './styles.module.css';
import sx from 'sx-chain';
const s = sx(styles);
// ⚠️ Works, but no autocomplete or type checking
<s.container.div>
<s.heading.h1>CSS Modules</s.heading.h1>
</s.container.div>Note: For full type safety and autocomplete, use inline style objects:
// ✅ Full autocomplete and type safety
const styles = {
container: 'max-w-7xl mx-auto px-4',
heading: 'text-2xl font-bold'
};
const s = sx(styles);With Custom Components
import sx from 'sx-chain';
const styles = {
card: 'bg-white rounded-lg shadow p-4',
};
// Register custom components
const s = sx(styles, {
customComponents: {
Card: MyCardComponent,
Icon: MyIconComponent,
}
});
// Use custom components
<s.card.Card>Custom card</s.card.Card>
<s.card.Icon />Next.js Integration
import Image from 'next/image';
import Link from 'next/link';
import sx from 'sx-chain';
import { createNextComponents } from 'sx-chain/next';
const s = sx(styles, {
customComponents: createNextComponents({ Image, Link })
});
// Now you can use Next.js components
<s.hero.Image src="/hero.jpg" alt="Hero" />
<s.navLink.Link href="/about">About</s.navLink.Link>Additional Classes
You can add extra classes using the standard className prop:
<s.btn.primary.button className="ml-2">
Button with margin
</s.btn.primary.button>Default Element
If you don't specify an element, it defaults to div:
<s.container>This is a div</s.container>
// Same as: <s.container.div>This is a div</s.container.div>TypeScript
sx-chain is written in TypeScript and provides full type safety:
import sx, { type StylesConfig } from 'sx-chain';
const styles = {
container: 'max-w-7xl mx-auto',
btn: {
base: 'px-4 py-2',
primary: 'bg-blue-500',
}
} as const satisfies StylesConfig;
const s = sx(styles);
// TypeScript will autocomplete available styles and HTML elements
// s.container. -> div, span, section, article, etc.
// s.btn. -> primary, secondary, button, div, etc.Comparison
vs styled-components / emotion
// styled-components / emotion
const Button = styled.button`
padding: 8px 16px;
background: ${props => props.primary ? 'blue' : 'gray'};
`;
<Button primary>Click</Button>
// sx-chain
<s.btn.primary.button>Click</s.btn.primary.button>vs clsx / classnames
// clsx
<button className={clsx('btn', variant === 'primary' && 'btn-primary')}>
Click
</button>
// sx-chain
<s.btn.primary.button>Click</s.btn.primary.button>vs CVA
// CVA
const button = cva('btn', {
variants: {
intent: {
primary: 'btn-primary',
secondary: 'btn-secondary',
}
}
});
<button className={button({ intent: 'primary' })}>Click</button>
// sx-chain
<s.btn.primary.button>Click</s.btn.primary.button>Performance
sx-chain uses:
- WeakMap for component caching
- Proxy for lazy evaluation
- No runtime style generation
- Minimal bundle size
License
MIT
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Author
keisuke-na
