Getting Started
Install TypeStyles and ship your first typed component styles in a few minutes
TypeStyles is CSS in TypeScript: style objects (selectors, media queries, pseudos), typed variants, tokens as CSS variables, and ordinary className strings in React, Vue, Svelte, or HTML.
In production, TypeStyles extracts styles at build time into a static CSS file with zero runtime overhead — the same approach as StyleX and Vanilla Extract. During development, the runtime injects styles for instant feedback with HMR.
Installation
pnpm add typestyles @typestyles/vite
npm install typestyles @typestyles/vite
yarn add typestyles @typestyles/vite
The bundler plugin gives you zero-runtime production builds and HMR in dev. Plugins are also available for Next.js, Rollup, esbuild, and webpack.
Prototyping without a plugin? TypeStyles also works as a pure runtime library — just
npm install typestylesand skip the plugin. You can add build extraction later without changing any application code. See Zero-runtime extraction for the migration path.
Set up the Vite plugin
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import typestyles from '@typestyles/vite';
export default defineConfig({
plugins: [react(), typestyles()],
});
Create a convention entry that imports all your style registrations. The plugin discovers it automatically and extracts CSS on vite build:
// src/typestyles-entry.ts
import './tokens';
import './components/Button.styles';
import './components/Card.styles';
That's it. In dev you get runtime injection + HMR. In production, typestyles.css is emitted as a static asset with no runtime JS.
Your first styles
Use createTypeStyles once so styles and tokens share one scopeId (namespaced CSS variables and predictable class names). Put the module in a file you import from your UI — the live example below includes the full source and a className usage snippet.
Scoped button + tokens
Read-only live output. Toggle variants to see the preview, DOM classes, and emitted CSS update together.
Source · app/typestyles.ts
import { createTypeStyles } from 'typestyles';
export const { styles, tokens } = createTypeStyles({ scopeId: 'app' });
export const color = tokens.create('color', {
primary: '#0066ff',
surface: '#ffffff',
});
export const button = styles.component('button', {
base: {
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
padding: '8px 16px',
borderRadius: '6px',
fontWeight: 500,
border: 'none',
cursor: 'pointer',
color: color.surface,
backgroundColor: color.primary,
},
variants: {
intent: {
primary: { backgroundColor: color.primary, color: color.surface },
ghost: {
backgroundColor: 'transparent',
color: color.primary,
border: `1px solid ${color.primary}`,
},
},
},
defaultVariants: { intent: 'primary' },
});
Usage · Button.tsx
<button type="button" className={button({ intent: 'primary' })}>
Example button
</button>
DOM
class="app-button-base app-button-intent-primary"
Emitted CSS
:root {
--app-color-primary: #0066ff;
--app-color-surface: #ffffff;
}
.app-button-base {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 8px 16px;
border-radius: 6px;
font-weight: 500;
border: none;
cursor: pointer;
color: var(--app-color-surface);
background-color: var(--app-color-primary);
}
.app-button-intent-primary {
background-color: var(--app-color-primary);
color: var(--app-color-surface);
}
Toggle Ghost in the demo and check the DOM and Emitted CSS panels (or DevTools on the preview button). Scoped class names are prefixed with your scopeId, and token-backed values resolve to custom properties such as --app-color-primary.
What just happened: definitions register when the module loads. In dev, the runtime injects CSS rules into a managed <style> tag. In production with a bundler plugin, these same rules are extracted into a static .css file — no client-side injection at all. See Zero-runtime extraction for the full story.
For React-specific patterns (refs, merging className, server components), see React integration. Vue and Svelte work the same way — see the runnable examples below.
Runnable examples
Clone the typestyles monorepo and run from the repo root (pnpm install once):
| Example | Command | README |
|---|---|---|
| Vite + React (recommended, zero-runtime in production) | pnpm vite-app dev |
examples/vite-app |
| Next.js App Router + extraction verify | pnpm next-app dev |
examples/next-app |
| Vue 3 | pnpm vue-app dev |
examples/vue-app |
| Svelte 5 | pnpm svelte-app dev |
examples/svelte-app |
| Runtime only (no bundler plugin) | pnpm parcel-app dev |
examples/parcel-app |
Full index: examples/README.md · maintainer map: docs/README.md
Next steps
| Topic | What you will learn |
|---|---|
| Zero-runtime extraction | Production static CSS with Vite, Next.js, Rollup, esbuild, webpack |
| Styles | Flat vs dimensioned styles.component, styles.class, composition, mental model |
| Components | Variants, compounds, multipart slots |
| Tokens | Namespaces, themes, tokens.use |
| Framework comparison | How TypeStyles compares to other tools |
| Migration | Mapping from Panda, CVA, StyleX, Emotion |
| Design system with tokens | Primitives → semantics → components |
| Class naming | Semantic vs hashed output |