Vanilla-extract logo

Vanilla-extract

A zero-runtime CSS-in-TS library that generates static CSS files at build time.

npm install @vanilla-extract/css
10.2K963.1K/weekv1.17.5336.49 KBMIT95 issues
Last updated: 2025-11-25
Star history chart for seek-oss/vanilla-extract

TL;DR

Vanilla-extract is a CSS-in-TS library that allows you to write styles in TypeScript and generates static, framework-agnostic CSS files at build time.

It combines the safety and expressiveness of TypeScript with the performance of static CSS, offering a true zero-runtime styling solution.

Why vanilla-extract?

Vanilla-extract offers a unique proposition: write styles in TypeScript and get static CSS files as output. This "CSS-in-TS" approach provides full type safety and local scoping for your styles, but with absolutely no runtime overhead. Unlike traditional CSS-in-JS libraries that need a client-side runtime to process styles, vanilla-extract does all the work at build time, resulting in highly optimized, static CSS stylesheets.

  • Zero-Runtime: All styles are compiled to static .css files. This means a smaller client bundle, faster initial page loads, and no performance cost for style evaluation in the browser.
  • Type-Safe Styles: Write your styles in TypeScript and get full autocompletion, type checking, and the ability to share values between your application code and your stylesheets.
  • Local Scoping by Default: All styles are locally scoped by default, generated with unique class names to prevent collisions. This makes styling components predictable and maintainable.
  • Framework Agnostic: It generates plain CSS, so it works with any framework, including React, Next.js, Svelte, or Vue.
  • Advanced Theming: Provides a powerful, type-safe theming system that allows you to define contracts for themes, making it easy to support multiple themes (e.g., light/dark modes) with full static analysis.

This makes vanilla-extract a superior choice for performance-critical applications where bundle size is a major concern and teams want the safety and developer experience of a TypeScript-first workflow.

Code Snippet

The following example shows how to create a style file with vanilla-extract. The styles are defined in a .css.ts file, which is then imported and used in a React component.

// styles/Button.css.ts
import { style } from '@vanilla-extract/css';
import { sprinkles } from './sprinkles.css'; // Assume sprinkles is your atomic styles file

export const button = style([
  sprinkles({
    background: 'blue',
    color: 'white',
    paddingX: 'large',
    paddingY: 'medium',
  }),
  {
    border: 'none',
    borderRadius: '8px',
    cursor: 'pointer',
    fontFamily: 'sans-serif',
    transition: 'transform 0.2s ease',

    ':hover': {
      transform: 'scale(1.05)',
    },
  },
]);
// components/Button.jsx
import React from 'react';
import * as styles from './styles/Button.css';

// At build time, `styles.button` becomes a string like "styles_button__1c4x9y0"
function Button({ children }) {
  return <button className={styles.button}>{children}</button>;
}

export default Button;

This snippet demonstrates how styles are authored in TypeScript and then imported as a module. The exported button class name is generated at build time, ensuring styles are scoped and optimized.

Pros and Cons

No library is perfect; understanding the trade-offs is key to selecting the right tool.

Pros

  • Maximum Performance: The zero-runtime approach means no client-side performance penalty, leading to the fastest possible load and render times.
  • Excellent Type Safety: Writing styles in TypeScript prevents typos and enables easy refactoring. Sharing types between styles and components is seamless.
  • Platform Agnostic: Since the output is just static CSS, it integrates cleanly into any build system and works with any frontend framework.

Cons

  • Build-Time-Only: Because styles are generated at build time, it does not support dynamic styling based on runtime values (e.g., a color from an API response) in the same way as CSS-in-JS libraries.
  • Setup Complexity: Requires specific integration with your build tool (e.g., Vite, Webpack) which can be more complex than dropping in a runtime library.
  • Learning Curve: The paradigm of writing styles in .css.ts files and the concepts of contracts and sprinkles can take time to master for teams new to the library.

Comparison with Other Styling Libraries

The table below outlines the positioning differences between vanilla-extract and other popular Styling libraries to help you make an informed decision:

LibraryDesign PhilosophyBest ForPain Points
Vanilla-extractZero-Runtime CSS-in-TS
Locally scoped, type-safe styles compiled to static CSS at build time.
High-Performance Apps
Projects where maximum performance and minimum bundle size are critical.
No Runtime Dynamics
Cannot create styles based on dynamic runtime values.
Styled ComponentsCSS-in-JS
Component-based styling where CSS is written in tagged template literals.
Dynamic & Themed UIs
When styles need to adapt heavily to props or a global theme.
Runtime Overhead
Introduces a performance cost due to style computation at runtime.
Tailwind CSSUtility-First
Applies styles via pre-built classes directly in markup.
Custom Designs
Projects that need a bespoke UI without the constraints of a component library.
Verbose HTML
Can lead to cluttered markup if not managed with components.

Verdict: When to Adopt

Adopt vanilla-extract when performance is your top priority and your team is comfortable with a TypeScript-centric workflow. It is the ideal choice for building highly optimized applications, design systems, or component libraries where the trade-off of losing runtime style dynamism is acceptable in exchange for best-in-class performance and type safety. It's particularly well-suited for static sites or applications where every kilobyte of JavaScript counts.