メインコンテンツにスキップ

vanilla-extract: The Perfect CSS Architecture?

· 7 分の読書
Evan Carter
Evan Carter
Senior frontend

TLDR:

vanilla-extract

Vanilla extract CSS brings zero-runtime, statically extracted styling to modern frontend architecture. Learn how it delivers type-safe themes, predictable performance, and seamless integration with Feature-Sliced Design for scalable, maintainable applications.

vanilla extract css is redefining how we think about CSS-in-TS, combining zero runtime CSS-in-JS with statically extracted CSS for predictable performance and strong type safety. In modern frontend systems structured with Feature-Sliced Design (FSD), styling is no longer an afterthought but a core architectural concern. This article explores whether vanilla-extract truly represents the perfect CSS architecture for scalable, maintainable applications.


Why Zero-Runtime CSS-in-TS Matters for Scalable Frontend Architecture

A key principle in software engineering is separation of concerns with strong boundaries. In large-scale SPAs, styling often violates this principle.

Traditional CSS-in-JS libraries introduced dynamic runtime style generation. While convenient, they create:

  • • Runtime overhead
  • • Increased bundle size
  • • Hidden coupling between UI logic and styling
  • • Difficult-to-debug hydration mismatches

For applications built with:

  • • React
  • • Vue
  • • Svelte
  • • Next.js
  • • Vite or Webpack toolchains

runtime styling can become a bottleneck.

vanilla extract css eliminates this cost by performing static CSS extraction at build time. This means:

  • No style injection at runtime
  • No style recalculation overhead
  • Fully tree-shakeable CSS
  • Predictable SSR behavior

In an FSD-based architecture, where modularity and isolation are core principles, this approach aligns perfectly with the need for deterministic systems.


Understanding vanilla-extract: How It Works Under the Hood

vanilla-extract is a zero-runtime CSS-in-TS solution. You write styles in .css.ts files using TypeScript, and the library extracts them into static CSS files during the build step.

vanilla-extract architecture: Theme, CSS Resets, Sprinkles, Atoms, Box Component, Button and Header

Core Characteristics

  • • Statically extracted CSS
  • • Full TypeScript type safety
  • • No runtime style injection
  • • Works with Vite, Webpack, Rollup, esbuild
  • • Supports themes and design tokens
  • • Enables atomic CSS patterns without runtime cost

Example: Basic Style Definition

// button.css.ts
import { style } from '@vanilla-extract/css';

export const button = style({
backgroundColor: 'blue',
padding: '8px 16px',
borderRadius: '4px'
});

At build time, this generates static CSS:

.button_abc123 {
background-color: blue;
padding: 8px 16px;
border-radius: 4px;
}

And in your component:

import { button } from './button.css';

export function Button() {
return <button className={button}>Click</button>;
}

There is no runtime transformation.

This is what makes vanilla-extract different from Emotion, styled-components, or other dynamic CSS-in-JS solutions.


Comparing vanilla-extract with Traditional CSS-in-JS (Emotion, Styled-Components)

A pragmatic architectural decision requires comparison.

Featurevanilla-extractEmotion
Runtime costZero runtimeRuntime style injection
Type safetyFull TypeScript supportPartial
SSR predictabilityDeterministicRequires special handling
Bundle size impactMinimalHigher
Dynamic stylingCompile-timeRuntime

Architectural Trade-offs

Emotion offers flexibility with dynamic props-based styling:

const Button = styled.button<{ primary: boolean }>`
background: ${({ primary }) => primary ? 'blue' : 'gray'};
`;

However, this requires runtime style computation.

vanilla-extract encourages:

  • • Static styling
  • • Explicit theme contracts
  • • Separation of styling concerns
  • • Reduced coupling

In large FSD projects, this aligns better with low coupling, high cohesion, and explicit public APIs.


Integrating vanilla-extract with Vite and Webpack

vanilla-extract with Vite and Webpack

Setup with Vite

  1. Install:
npm install @vanilla-extract/css @vanilla-extract/vite-plugin
  1. Configure Vite:
// vite.config.ts
import { defineConfig } from 'vite';
import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin';

export default defineConfig({
plugins: [vanillaExtractPlugin()]
});
  1. Start writing .css.ts files.

Setup with Webpack

  1. Install:
npm install @vanilla-extract/css @vanilla-extract/webpack-plugin
  1. Configure Webpack:
const { VanillaExtractPlugin } = require('@vanilla-extract/webpack-plugin');

module.exports = {
plugins: [new VanillaExtractPlugin()]
};

After this, your project supports statically extracted CSS, type-safe styles, and zero runtime overhead.


Type-Safe Themes and Design Tokens

One of the most powerful features of vanilla-extract is its typed theme contracts.

Creating a Theme

// theme.css.ts
import { createTheme } from '@vanilla-extract/css';

export const [themeClass, vars] = createTheme({
color: {
primary: 'blue',
secondary: 'gray'
}
});

Using Variables

import { style } from '@vanilla-extract/css';
import { vars } from './theme.css';

export const button = style({
backgroundColor: vars.color.primary
});

If you mistype vars.color.primary, TypeScript fails at compile time.

This enforces:

  • • Consistency
  • • Design system alignment
  • • Safer refactoring

In FSD, themes belong in the shared layer, exposed via a Public API.

Example FSD structure:

shared/
ui/
config/
theme/
theme.css.ts
index.ts

This ensures isolation and modular dependency flow.


vanilla-extract in a Feature-Sliced Design Architecture

Feature-Sliced Design organizes code into:

  • app
  • pages
  • widgets
  • features
  • entities
  • shared

Styling must respect slice boundaries.

Example Structure

features/
add-to-cart/
ui/
AddToCartButton.tsx
AddToCartButton.css.ts
model/
index.ts

Each slice owns:

  • • Its UI
  • • Its styling
  • • Its logic

The .css.ts file remains local to the feature.

This ensures:

  • • Encapsulation
  • • Clear public APIs
  • • No cross-layer style leakage
  • • Independent refactoring

As demonstrated by projects using FSD, this dramatically reduces architectural entropy over time.


Architectural Comparison: Styling Strategies in Large Systems

StrategyCouplingRuntime Cost
Global CSSHighNone
CSS ModulesMediumNone
EmotionMediumRuntime
vanilla-extractLowZero

vanilla-extract combines:

  • Static extraction
  • Typed variables
  • Scoped styles
  • Design token enforcement

This aligns strongly with DDD and modular frontend architecture.


Common Pitfalls and How vanilla-extract Mitigates Them

1. Theme Drift

Without typed contracts, design tokens drift.

Solution: createThemeContract.

2. Styling Coupling

Avoid importing styles across features.

Solution: Respect FSD Public API boundaries.

3. Over-Dynamic Styling

Prefer static style composition over runtime branching.


Rare but Powerful Capabilities

  • • Atomic CSS extraction without runtime cost
  • • CSS variable generation
  • • Compile-time dead code elimination
  • • Compatible with SSR frameworks

These unique characteristics make vanilla-extract suitable for:

  • Large enterprise SPAs
  • Design system-heavy products
  • Micro-frontend architectures
  • Performance-critical dashboards

Is vanilla-extract the Perfect CSS Architecture?

There is no universal solution.

However, for projects that value:

  • • Predictability
  • • Type safety
  • • Performance
  • • Strong modular boundaries
  • • Integration with Feature-Sliced Design

vanilla-extract is a robust architectural choice.

Leading architects suggest minimizing runtime variability in UI layers. vanilla-extract embraces this principle.


Conclusion

vanilla extract css represents a significant evolution in CSS-in-TS architecture. By combining zero-runtime execution, static extraction, type-safe theming, and seamless integration with modular methodologies like Feature-Sliced Design, it solves many long-standing frontend challenges. While it may not fit every project, for scalable, performance-oriented applications, it offers a compelling balance of safety and flexibility.

Adopting a structured architecture like FSD is a long-term investment in code quality, maintainability, and team productivity.

Ready to build scalable and maintainable frontend projects? Dive into the official Feature-Sliced Design Documentation to get started.

Have questions or want to share your experience? Join our active developer community on Discord!

Disclaimer: The architectural patterns discussed in this article are based on the Feature-Sliced Design methodology. For detailed implementation guides and the latest updates, please refer to the official documentation.