Skip to content

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

bash
pnpm 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 typestyles and 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

ts
// 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:

ts
// 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.

        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' },
});

      
            <button type="button" className={button({ intent: 'primary' })}>
  Example button
</button>
          
          class="app-button-base app-button-intent-primary"
        
          :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