style-zx
v0.0.14
Published
**Zero-runtime CSS-in-JS with a `zx` prop.**
Maintainers
Readme
Style-ZX
Zero-runtime CSS-in-JS with a zx prop.
style-zx is a lightweight, zero-runtime CSS-in-JS library designed for Vite. It allows you to style your React components using a zx prop, which is compiled to static CSS classes at build time. This combines the developer experience of CSS-in-JS with the performance of static CSS.
Features
- Zero Runtime: Styles are extracted to static CSS files during the build process. No runtime style injection or overhead.
- Ultra Lightweight: The plugin output is tiny (~5KB), ensuring minimal impact on your build process.
zxProp: Style any component directly with thezxprop (inspired by MUI'ssxand other similar libraries).createStyles: Define reusable static styles outside components that compile to class names.- TypeScript Support: Full type safety for CSS properties and theme variables.
- Theming: Define a theme and access variables easily (e.g.,
"$theme.colors.primary"). - Aliases: Shorthand properties for common styles (e.g.,
p,m,px,my,bg). - Nested Selectors: Support for pseudo-classes and nested selectors (e.g.,
&:hover,& > div). - Vite Integration: Seamless integration as a Vite plugin with HMR support.
Installation
Install the package (assuming local or published package):
npm install style-zx # or yarn add style-zxAdd the Vite plugin in
vite.config.ts:import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import styleZx from 'style-zx/plugin' export default defineConfig({ plugins: [styleZx(), react()], })
Usage
Basic Styling
Use the zx prop on any HTML element. Numeric values for dimensions are treated as pixels by default.
<div zx={{
bg: 'white',
p: 20, // padding: 20px
borderRadius: 8,
boxShadow: '0 4px 6px rgba(0,0,0,0.1)'
}}>
<h1 zx={{ color: 'blue', fontSize: 24 }}>Hello World</h1>
</div>Theming
Global Theme
Define your theme using createTheme. It injects CSS variables into :root and returns the theme object for direct JS access.
// src/theme.ts
import { createTheme } from 'style-zx';
export const theme = createTheme({
colors: {
primary: '#007bff',
background: '#f0f2f5',
text: '#333'
},
spacing: {
small: 8,
medium: 16
}
});Import the theme file early in your app (e.g., in main.tsx) to initialize CSS variables:
// main.tsx
import './theme'; // Initialize theme CSS variables
import App from './App';Use theme variables in zx prop with $theme. prefix:
<button zx={{
bg: '$theme.colors.primary',
color: 'white',
p: '$theme.spacing.small'
}}>
Click Me
</button>Scoped Themes (ThemeProvider)
Use ThemeProvider to override theme values for a section of your app. It inherits from the global theme and deep merges your overrides:
import { ThemeProvider } from 'style-zx';
// Only override what changes - everything else is inherited
<ThemeProvider theme={{ colors: { primary: '#a855f7', surface: '#1e1e2e' } }}>
<Sidebar /> {/* Uses purple primary, inherits other colors */}
</ThemeProvider>Nested ThemeProviders inherit from their parent:
<ThemeProvider theme={{ colors: { primary: 'blue' } }}>
<ThemeProvider theme={{ colors: { secondary: 'green' } }}>
{/* Gets blue primary + green secondary + all other inherited values */}
</ThemeProvider>
</ThemeProvider>useTheme Hook
Access the current theme in JavaScript with useTheme:
import { useTheme } from 'style-zx';
function MyComponent() {
const theme = useTheme();
return <p style={{ color: theme.colors.primary }}>Hello</p>;
}- Inside a
ThemeProvider: returns the merged theme (parent + overrides) - Outside a
ThemeProvider: returns the global theme
Nested Selectors
You can use standard CSS nesting syntax.
<div zx={{
color: 'black',
'&:hover': {
color: 'blue'
},
'& > span': {
fontWeight: 'bold'
}
}}>
Hover me <span>(Bold)</span>
</div>createStyles
For reusable styles outside components, use createStyles. It compiles to static class names at build time with full support for aliases and theme variables.
import { createStyles } from 'style-zx';
const styles = createStyles({
container: {
bg: '$theme.colors.background',
p: 20,
display: 'flex',
flexDirection: 'column',
},
title: {
fontSize: '2em',
color: '$theme.colors.primary',
m: 0,
},
card: {
bg: 'white',
borderRadius: 8,
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
p: 16,
},
});
function App() {
return (
<div className={styles.container}>
<h1 className={styles.title}>Hello</h1>
<div className={styles.card}>Content here</div>
</div>
);
}Conditional Classes: Use a library like clsx for conditional class logic:
import clsx from 'clsx';
<div className={clsx(styles.card, isActive && styles.active)} />Comparison & Concept
The Concept
style-zx relies on static analysis. The build plugin scans your code for the zx prop, extracts the object literal, generates a unique class hash, creates CSS rules, and replaces the zx prop with a className.
vs. Pigment CSS
Both libraries aim for zero-runtime CSS-in-JS.
- Pigment CSS: A more robust, complex solution often integrated with Next.js and MUI's ecosystem. It handles more complex dynamic scenarios but requires deeper integration. Also the project is not actively maintained at the moment.
- Style-ZX: A lightweight, Vite-first approach. It focuses on simplicity and the specific
zxprop API. It's easier to set up for simple Vite projects but may have fewer features than Pigment.
vs. Emotion / Styled-Components
- Emotion/Styled-Components: Runtime CSS-in-JS. They parse styles in the browser, generate classes, and inject tags. This offers great flexibility (dynamic props) but incurs a runtime performance cost (script execution + style recalculation).
- Style-ZX: No runtime cost. The browser just loads a CSS file.
vs. Tailwind CSS
- Tailwind: Utility-first. You compose classes (
p-4 bg-white). - Style-ZX: Object-based. You write CSS-like objects (
{ p: 16, bg: 'white' }). This is often preferred by developers who like keeping styles colocated but find long class strings hard to read.
Caveats & Limitations
- Static Analysis Only: The values in
zxmust be statically analyzable at build time.- ✅
zx={{ color: 'red' }} - ✅
zx={{ color: '$theme.colors.primary' }}(if theme is static) - ❌
zx={{ color: props.color }}(Dynamic props are not supported directly in the build step. Use CSS variables for dynamic values).
- ✅
- Vite Only: Currently designed specifically as a Vite plugin.
- No Dynamic Function Interpolations: You cannot pass a function to
zxthat depends on runtime state.
Gains
- Performance: Zero JS runtime for styles means faster TTI (Time to Interactive) and less main-thread work.
- Bundle Size: The plugin itself is extremely small (~5KB), keeping your dev dependencies lean.
- Developer Experience: Write styles in TypeScript right next to your components. Get autocomplete and type checking.
- Maintainability: Styles are scoped and colocated, reducing dead code and global namespace pollution.
