We use cookies to enhance your experience on the site
CodeWorlds

Zero-runtime CSS-in-JS - Cosmic Style Compilation

Pilot, imagine two types of spaceship engines. The first calculates the flight trajectory in flight -- consuming fuel on the go (runtime CSS-in-JS like styled-components). The second calculates the trajectory before launch and saves it in the onboard computer -- during flight it doesn't consume any extra energy (zero-runtime CSS-in-JS). In the frontend world, this is the difference between runtime and zero-runtime approaches to styling.

The problem with runtime CSS-in-JS

Styled-components and Emotion work like this: every time React renders a component, the library at runtime parses the template literal, generates a unique class hash, and injects a

<style>
tag into the DOM. This means:

1// Styled-components -- runtime
2const Card = styled.div`
3  background: ${({ theme }) => theme.colors.surface};
4  padding: ${({ theme }) => theme.spacing.lg};
5  border-radius: 12px;
6`;
7// On every render: parse template -> generate hash -> inject <style>

Problems:

  • Larger bundle -- the library (~12-15 KB gzip) must be loaded
  • Slower rendering -- generating styles at runtime costs time
  • SSR complications -- styles must be extracted and injected into HTML

What is zero-runtime CSS-in-JS?

Zero-runtime CSS-in-JS is an approach where styles are generated at compile time (build time) -- the resulting code contains no JavaScript for handling styles. You write styles in TypeScript, and the bundler converts them into a plain CSS file.

Vanilla-extract -- TypeScript-first styling

Vanilla-extract is the most popular zero-runtime library. You define styles in

.css.ts
files:

1// styles.css.ts -- styles file
2import { style } from '@vanilla-extract/css';
3
4export const card = style({
5  background: '#1a1a4e',
6  border: '2px solid #3949ab',
7  borderRadius: '12px',
8  padding: '20px',
9  transition: 'all 0.3s ease',
10  ':hover': {
11    borderColor: '#7c4dff',
12    transform: 'translateY(-2px)',
13  },
14});
15
16export const heading = style({
17  color: '#7c4dff',
18  fontSize: '20px',
19  marginBottom: '16px',
20});

In the React component, you import classes as plain strings:

1// Component.tsx
2import { card, heading } from './styles.css.ts';
3
4function MissionCard({ title, children }) {
5  return (
6    <div className={card}>
7      <h3 className={heading}>{title}</h3>
8      {children}
9    </div>
10  );
11}

After compilation,

card
and
heading
are just strings with CSS class names -- zero JavaScript at runtime.

Sprinkles -- typed utility classes

Vanilla-extract offers

sprinkles
-- a typed equivalent of utility classes (like Tailwind, but with full TypeScript typing):

1// sprinkles.css.ts
2import { defineProperties, createSprinkles } from '@vanilla-extract/sprinkles';
3
4const responsiveProperties = defineProperties({
5  conditions: {
6    mobile: {},
7    tablet: { '@media': 'screen and (min-width: 768px)' },
8    desktop: { '@media': 'screen and (min-width: 1024px)' },
9  },
10  defaultCondition: 'mobile',
11  properties: {
12    display: ['none', 'flex', 'block', 'grid'],
13    padding: { sm: '8px', md: '16px', lg: '24px' },
14    gap: { sm: '8px', md: '16px', lg: '24px' },
15  },
16});
17
18export const sprinkles = createSprinkles(responsiveProperties);

Usage:

1<div className={sprinkles({
2  display: { mobile: 'block', tablet: 'grid' },
3  padding: { mobile: 'sm', desktop: 'lg' },
4  gap: 'md',
5})}>

Everything is typed -- TypeScript will suggest available values and catch typos.

Linaria -- zero-runtime with styled-components syntax

Linaria offers syntax identical to styled-components but compiles styles at build time:

1import { styled } from '@linaria/react';
2
3const Card = styled.div`
4  background: #1a1a4e;
5  border: 2px solid #3949ab;
6  border-radius: 12px;
7  padding: 20px;
8  &:hover {
9    border-color: #7c4dff;
10  }
11`;

It looks identical to styled-components, but after compilation

Card
renders
<div className="hash123">
-- with no runtime JS.

Panda CSS -- a modern alternative

Panda CSS is the newest player -- it combines the utility-first approach (like Tailwind) with typing and zero-runtime:

1import { css } from '../styled-system/css';
2
3function MissionPanel() {
4  return (
5    <div className={css({
6      bg: 'surface',
7      p: '6',
8      borderRadius: 'lg',
9      border: '2px solid',
10      borderColor: 'border',
11      _hover: { borderColor: 'primary' },
12    })}>
13      Mission Panel
14    </div>
15  );
16}

Panda generates atomic CSS (like Tailwind) at build time -- each property is a separate class, which minimizes duplication.

Comparison: runtime vs zero-runtime

| Feature | styled-components | Vanilla-extract | Linaria | Panda CSS | |---------|------------------|-----------------|---------|-----------| | Runtime JS | Yes (~12 KB) | No | No | No | | Dynamic styles | Full (props) | Limited (recipes) | Limited | Limited | | TypeScript | Good | Native | Good | Native | | SSR | Configuration needed | Seamless | Seamless | Seamless | | DX (experience) | Great | Good | Great | Great |

When to choose zero-runtime?

  • Large applications -- every kilobyte of runtime counts
  • SSR (Next.js, Remix) -- no problems with style hydration
  • TypeScript-first -- full typing of CSS values
  • Performance -- no runtime cost = faster rendering
Go to CodeWorlds