CSS in TypeScript with Vanilla Extract

Write type-safe, zero-runtime stylesheets that generate static CSS at build time. Perfect for modern web development with Next.js, React, and beyond.

What Makes Vanilla Extract Different

Traditional CSS-in-JS libraries inject styles into the document at runtime, which provides flexibility but comes with performance costs. JavaScript must parse and execute to generate styles, and the styling engine ships with your production bundle. Vanilla Extract takes a fundamentally different approach by generating static CSS files at build time.

The library was created by Mark Dalgleish, co-creator of CSS Modules, and builds upon that foundation with full TypeScript integration. When you write styles in Vanilla Extract, you're essentially authoring a type-safe stylesheet that gets compiled to regular CSS during your build process. This means your production JavaScript bundle contains no styling runtime, and the generated CSS can be cached independently by browsers.

The core philosophy centers on three principles: type safety throughout the styling process, zero runtime overhead, and framework agnosticism. Unlike many CSS-in-JS solutions that tie you to a specific framework ecosystem, Vanilla Extract works with React, Vue, Svelte, or any component library. This approach aligns well with professional web development services that prioritize performance and maintainability.

Core Benefits of Vanilla Extract

Why modern development teams are adopting this zero-runtime approach

Zero Runtime Overhead

Styles compile to static CSS at build time. No JavaScript runtime ships with your styles, resulting in smaller bundles and faster page loads.

Full TypeScript Integration

Catch typos and invalid CSS values at compile time. Autocomplete for properties and values directly in your editor.

Locally Scoped Classes

Generated class names are hashed and scoped to prevent collisions. Use generic names like 'container' without conflicts.

Framework Agnostic

Works with React, Vue, Svelte, Next.js, or any JavaScript framework. The styling logic is independent of your UI library.

Creating Styles with the style Function

The primary building block in Vanilla Extract is the style function, which creates locally scoped CSS classes. You define styles using a TypeScript object that maps CSS properties to their values, with property names following camelCase conventions familiar from JavaScript style objects.

The function returns a string containing the generated class name, which you then import and use in your components. The returned string is deterministic based on the styles you define, meaning the same styles always produce the same class name across builds.

Basic style function example
1import { style } from '@vanilla-extract/css';2 3export const button = style({4 backgroundColor: 'hsl(210deg, 50%, 50%)',5 color: 'white',6 padding: '12px 24px',7 borderRadius: '8px',8 fontSize: '16px',9 fontWeight: 500,10 border: 'none',11 cursor: 'pointer',12 ':hover': {13 backgroundColor: 'hsl(210deg, 50%, 45%)'14 },15 '@media': {16 'screen and (max-width: 768px)': {17 padding: '10px 20px',18 fontSize: '14px'19 }20 }21});

Type-Safe Theming

Theming in Vanilla Extract goes beyond simple CSS variables by providing TypeScript-level guarantees about your design tokens. The createTheme function creates a typed contract between theme definitions and their usage, catching inconsistencies at build time.

When you create a theme, you define the structure of your design tokens as a TypeScript object. Vanilla Extract generates both the CSS custom properties and TypeScript types that reference them. Any component using the theme gets full autocomplete for available tokens and compile-time errors when tokens are missing or misspelled.

Creating type-safe themes
1import { createTheme, style } from '@vanilla-extract/css';2 3export const [theme, vars] = createTheme({4 color: {5 primary: 'hsl(210deg, 50%, 50%)',6 secondary: 'hsl(260deg, 50%, 50%)',7 background: 'white',8 text: 'hsl(0deg, 0%, 20%)'9 },10 spacing: {11 small: '8px',12 medium: '16px',13 large: '24px'14 },15 font: {16 body: 'system-ui, sans-serif',17 heading: 'Georgia, serif'18 }19});20 21export const button = style({22 backgroundColor: vars.color.primary,23 padding: vars.spacing.medium,24 fontFamily: vars.font.body25});

Theme Variants and Dark Mode

The theming system supports creating variants of a base theme, making features like dark mode implementation straightforward. You can define theme variants that share the same structure but provide different values, with TypeScript ensuring consistency across variants.

Components apply themes by adding the generated class name to a parent element. All nested components using theme variables automatically receive the appropriate values based on which theme class is active. This approach enables instant theme switching without JavaScript runtime overhead for style calculation.

Dark mode with theme variants
1import { createTheme } from '@vanilla-extract/css';2 3export const [lightTheme, lightVars] = createTheme({4 color: {5 background: 'white',6 text: 'hsl(0deg, 0%, 20%)'7 }8});9 10export const [darkTheme, darkVars] = createTheme({11 color: {12 background: 'hsl(0deg, 0%, 10%)',13 text: 'hsl(0deg, 0%, 90%)'14 }15});16 17// Re-export vars for consistent access18export const vars = lightVars;

Recipes API for Component Variants

The Recipes API provides a declarative way to define component variants, enabling sophisticated style combinations while maintaining type safety. This pattern proves particularly valuable for design systems where components need consistent variation patterns.

The recipe function accepts a base style configuration plus variant definitions. Each variant can target different properties, and TypeScript ensures that only valid variant combinations are used when applying styles.

Building component variants with Recipes API
1import { recipe } from '@vanilla-extract/recipes';2 3export const button = recipe({4 base: {5 padding: '12px 24px',6 borderRadius: '8px',7 fontWeight: 500,8 cursor: 'pointer'9 },10 variants: {11 color: {12 primary: { backgroundColor: vars.color.primary },13 secondary: { backgroundColor: vars.color.secondary },14 danger: { backgroundColor: 'hsl(0deg, 60%, 50%)' }15 },16 size: {17 small: { fontSize: '14px', padding: '8px 16px' },18 medium: { fontSize: '16px', padding: '12px 24px' },19 large: { fontSize: '18px', padding: '16px 32px' }20 },21 variant: {22 solid: {},23 outline: { backgroundColor: 'transparent', border: '2px solid currentColor' },24 ghost: { backgroundColor: 'transparent' }25 }26 },27 compoundVariants: [28 {29 variants: {30 color: 'primary',31 variant: 'outline'32 },33 style: { borderColor: vars.color.primary, color: vars.color.primary }34 }35 ]36});

Sprinkles: Utility-First Styling

For teams preferring utility-first styling patterns, Sprinkles provides a type-safe alternative to libraries like Tailwind CSS. You define allowed values for each property, and Sprinkles generates a utility function that applies those styles.

Sprinkles supports responsive and conditional styles through conditions, enabling patterns like mobile-first responsive design or dark mode without runtime JavaScript overhead. This approach combines the developer experience of utility-first styling with the type safety that TypeScript provides.

Creating utility-first styles with Sprinkles
1import { defineProperties, createSprinkles } from '@vanilla-extract/sprinkles';2 3const colors = {4 blue50: '#eff6ff',5 blue100: '#dbeafe',6 blue500: '#3b82f6',7 blue600: '#2563eb',8 blue700: '#1d4ed8'9} as const;10 11const spacing = {12 0: '0',13 1: '4px',14 2: '8px',15 3: '12px',16 4: '16px',17 6: '24px',18 8: '32px'19} as const;20 21const properties = defineProperties({22 properties: {23 color: colors,24 backgroundColor: colors,25 padding: spacing,26 margin: spacing,27 borderRadius: {28 none: '0',29 sm: '4px',30 md: '8px',31 lg: '12px',32 full: '9999px'33 }34 }35});36 37export const sprinkles = createSprinkles(properties);

Framework Integrations

Vanilla Extract provides official integrations for all major modern build tools and frameworks:

  • Next.js: @vanilla-extract/next package handles the build process for both Pages Router and App Router
  • Vite: @vanilla-extract/vite-plugin provides seamless integration with hot module replacement
  • webpack: @vanilla-extract/webpack-plugin handles file processing and CSS extraction

The framework-agnostic design means you can use Vanilla Extract regardless of your component library choice. Whether you're building with React, Vue, Svelte, or plain JavaScript, the styling approach remains consistent. Our web development team has extensive experience implementing these integrations for production applications.

Performance Best Practices

Optimizing CSS Output

While Vanilla Extract generates static CSS, the size of that CSS depends on how you structure your styles:

  • Avoid style duplication: Extract common styles into shared definitions and compose them
  • Use minimal specificity: The style function generates class selectors with hashed names
  • Leverage tree shaking: Unused style imports are automatically eliminated from output

Critical CSS and Performance

Static CSS generation enables traditional critical CSS strategies. Since the CSS is known at build time, you can inline critical styles in HTML or use plugins to extract critical path styles. The absence of runtime styling code means faster page rendering, particularly on slower devices or networks.

For teams building modern web applications with a focus on performance, Vanilla Extract's zero-runtime approach aligns well with best practices for fast initial page loads and efficient caching strategies.

Frequently Asked Questions

Is Vanilla Extract production-ready?

Yes, Vanilla Extract has been used in production by many companies. It has stable APIs, comprehensive documentation, and active maintenance. The library has reached version 1.0, indicating API stability.

How does Vanilla Extract compare to CSS Modules?

Vanilla Extract builds on CSS Modules concepts with additional type safety and theming capabilities. Both provide locally scoped classes, but Vanilla Extract adds TypeScript integration and zero-runtime CSS generation.

Can I use Vanilla Extract with React Server Components?

Yes, Vanilla Extract works with React Server Components through Next.js integration. Styles are generated at build time and imported as regular CSS files, which are compatible with server-side rendering.

Does Vanilla Extract support CSS nesting?

Vanilla Extract doesn't support native CSS nesting since it would require runtime processing. Instead, use the selectors API for pseudo-classes and parent selectors. This maintains zero-runtime performance.

How do I debug Vanilla Extract styles?

Vanilla Extract generates standard CSS with readable class names. You can use browser dev tools to inspect and modify styles. Source maps map generated CSS back to your .css.ts files during development.

Ready to Modernize Your Styling?

We help teams implement type-safe, performant styling architectures with Vanilla Extract and modern web development best practices.