An incredibly straightforward and simple CSS-in-JS solution with zero runtime dependencies, and out-of-the-box TypeScript support
npm install simplestyle-jsAn ultra-tiny, neat, easy-to-use CSS-in-JS library with SSR support,
tiny bundle size and only one runtime dependency.
Framework agnostic by design — works with React, Vue, Svelte, Next.js, Astro, or no framework at all.
6.48kB / 2.69kB gzip (courtesy of bundlejs.com)
bun
```
bun add simplestyle-js
npm
``
npm install simplestyle-js --save
pnpm
``
pnpm add simplestyle-js
yarn
``
yarn add simplestyle-js
Use the browser entrypoint for runtime CSS injection. This variant creates tags in the browser and is the most traditional CSS‑in‑JS experience.
`tsx
import { makeCssFuncs } from 'simplestyle-js/browser';
const { createStyles, createKeyframes, createRawStyles } = makeCssFuncs();
const { keyframe } = createKeyframes('Pulse', () => ({
'0%': { opacity: 0.6, transform: 'scale(0.98)' },
'100%': { opacity: 1, transform: 'scale(1)' },
}));
createRawStyles('GlobalReset', () => ({
', ::before, *::after': { boxSizing: 'border-box' },
body: { margin: 0, fontFamily: 'system-ui, sans-serif' },
}));
const { classes } = createStyles('Button', () => ({
root: {
backgroundColor: '#1f4aa8',
borderRadius: 6,
color: '#fff',
padding: '12px 16px',
animation: ${keyframe} 900ms ease-in-out infinite alternate,
'&:hover': { backgroundColor: '#2d6cdf' },
'@media (max-width: 768px)': { padding: '10px 12px' },
},
}));
document.querySelector('button')?.classList.add(classes.root);
`
Rules support nested selectors via &, media queries, and $className back‑references to other generated classes.
The SSR workflow requires usage of the built in ssjs CLI tool..css
This works by scanning your source files and generating a single file, containing all of the collected styles.
Your source code remains untouched and does not require any transformation or transpilation.
To leverage this, you must use the SSR entrypoint and place all styles in files named with the following format:
``
*.styles.{js|cjs|mjs|ts|mts|cts|jsx|tsx}
Run the ssjs CLI to compile a single CSS file. This works with Next.js, Astro, or any framework (SSR or SSG) because the output is just plain CSS.
`ts
// src/components/button.styles.ts
import { makeCssFuncs } from 'simplestyle-js/ssr';
const { createStyles } = makeCssFuncs();
export const { classes } = createStyles('Button', () => ({
root: {
backgroundColor: '#1f4aa8',
borderRadius: 6,
color: '#fff',
padding: '12px 16px',
'&:hover': { backgroundColor: '#2d6cdf' },
},
}));
`
`tsx
// src/components/Button.tsx
import { classes } from './button.styles';
export function Button() {
return ;
}
`
Define accurate --entrypoints CLI flags for your code so the compiler can follow your import graph and discover all of your styles.
The resulting CSS is written in the same order as your source code / import tree.
`bash`
bun ssjs --entrypoints src/app.tsx src/pages/*/.tsx --outfile public/my-styles.css`bash`
npx ssjs --entrypoints src/app.tsx src/pages/*/.tsx --outfile public/my-styles.css`bash`
pnpm ssjs --entrypoints src/app.tsx src/pages/*/.tsx --outfile public/my-styles.css`bash`
yarn ssjs --entrypoints src/app.tsx src/pages/*/.tsx --outfile public/my-styles.css
Optional watch mode:
`bash`
npx ssjs --entrypoints src/app.tsx src/pages/*/.tsx --outfile public/my-styles.css --watch
The output file is plain CSS. Include it the same way you would any stylesheet:
`html`
Or import it via your framework’s entry:
`ts`
// e.g. Next.js or Astro entry files, or any UI application that uses a bundler
import '../public/my-styles.css';
Important: always call makeCssFuncs() from the correct entrypoint:
- Browser runtime: import { makeCssFuncs } from 'simplestyle-js/browser'import { makeCssFuncs } from 'simplestyle-js/ssr'
- SSR:
Creates the CSS helpers and optionally binds your design tokens for typed access.
`ts
import { makeCssFuncs } from 'simplestyle-js/browser'; // or import the SSR variant from 'simplestyle-js/ssr';
const { createStyles } = makeCssFuncs({
variables: {
colors: { brand: '#1f4aa8' },
},
});
const { classes } = createStyles('Card', (vars) => ({
root: { backgroundColor: vars.colors.brand },
}));
`
Builds class names + CSS.
Returns { classes, stylesheet, updateSheet }.classes
Use the returned directly in your component code / HTML.
`ts`
const { classes } = createStyles('Nav', () => ({
wrapper: { display: 'flex', gap: 12 },
link: { '&:hover': { textDecoration: 'underline' } },
'@media (max-width: 600px)': { wrapper: { flexDirection: 'column' } },
}));
Back‑reference example (use $otherRule to reference another generated class):
`ts`
const { classes } = createStyles('Card', () => ({
title: { fontWeight: 700, marginBottom: 8 },
root: {
padding: 16,
borderRadius: 8,
backgroundColor: '#f6f7fb',
'& $title': { color: '#1f4aa8' },
},
}));
Generates a unique animation name and @keyframes CSS. Returns { keyframe, stylesheet }.
`ts
const { keyframe } = createKeyframes('FadeIn', () => ({
'0%': { opacity: 0, transform: 'translateY(6px)' },
'100%': { opacity: 1, transform: 'translateY(0px)' },
}));
const { classes } = createStyles('Notice', () => ({
root: {
animation: ${keyframe} 320ms ease-out,`
},
}));
Writes rules without generating class names. Keys must be selectors (html, body *, .app).
`ts`
createRawStyles('GlobalReset', () => ({
', ::before, *::after': { boxSizing: 'border-box' },
'html, body': {
margin: 0,
padding: 0,
fontFamily: 'system-ui, sans-serif',
lineHeight: 1.4,
backgroundColor: '#fff',
color: '#111',
},
img: { maxWidth: '100%', display: 'block' },
button: { font: 'inherit' },
}));
Creates @import rules. The array items must already be valid @import strings.
`ts`
createImports('Imports', () => [
'@import "https://unpkg.com/normalize.css/normalize.css"',
]);
Registers a transform that runs after CSS strings are generated, but before they’re flushed or written.
- SimpleStyleRules: { [selectorOrKey: string]: Properties | SimpleStyleRules }CreateStylesOptions
- : options for flushing/placement in the browser runtime.PosthookPlugin
- : signature for registerPosthook.
- Nested selectors: & is replaced with the parent selector. Comma‑separated selectors are supported (e.g., '&:hover, &:focus').$otherRule
- Back‑references: Use to reference another generated class in the same createStyles call (the referenced rule should appear earlier in the object).@media
- Media queries: Top‑level keys contain further rule objects.updateSheet
- Updating styles: merges new rules and updates the existing