# How does it work (https://yak.style/docs/how-does-it-work)

> How yak turns styled-components syntax into static CSS at build time: the Rust SWC plugin, the webpack, Turbopack, Vite and Rsbuild integrations, and the small runtime.

## Playground [#playground]

You can watch the code transformation live in the [playground](/playground).

## The basics [#the-basics]

yak has 3 parts:

1. The compile time part (SWC plugin + bundler integration)
2. The runtime part (minimal class name merging)
3. Optional: Context (theme provider)

The compile time part is responsible for extracting styles and transforming your code into something that uses the runtime part. The runtime part is responsible for merging styles and props.

yak supports four bundler backends: **webpack** (Next.js), **Turbopack** (Next.js), **Vite** and **Rsbuild**. The SWC plugin is shared across all of them, while each bundler has its own loader or plugin for CSS delivery.

## The compile time part [#the-compile-time-part]

yak uses a Rust-based SWC plugin and a bundler-specific loader to transform your code. The SWC plugin transforms the tagged template literals (like `styled` and `css`) and extracts CSS, while the loader resolves cross-module dependencies and delivers the CSS through the bundler's native CSS pipeline.

### The SWC plugin [#the-swc-plugin]

See: [yak-swc](https://github.com/galaxuslabs/yak/tree/main/packages/yak-swc)

The SWC plugin takes in the source code and transforms the tagged template literals into `@yak/react` runtime function
calls with the generated class names of the extracted plain CSS as arguments. It also transforms the dynamic parts of the
tagged template literals into function calls with the properties as argument and adds it as css value to the style object.
On top of that it resolves the `css` prop and folds static JSX usages of styled components directly into DOM elements.

#### Change import [#change-import]

The first step of the transformation is to change the import statement from `styled` and `css` to the `@yak/react` runtime.

```tsx title="output"
import * as __yak from "@yak/react/internal";
```

The normal import references stub behavior, so tests can run without a transpilation step.

#### Add style import [#add-style-import]

The next step of the transformation is to add the import statement for the styles. These styles don't exist yet, but will be generated by the bundler-specific loader/plugin at a later stage. Since yak controls all generated class names (passing them as strings directly), this import does not need to export class name bindings. The import format depends on the bundler:

**Bundler setup**

Webpack:

```tsx title="output"
import "./page.yak.css!=!./page?./page.yak.css";
```

This strange looking import string uses a poorly documented webpack feature called [inline matchResource](https://webpack.js.org/api/loaders/#inline-matchresource). It tells webpack to treat the current file (`./page`) as if it were a CSS file named `./page.yak.css`.

`./page.yak.css` doesn't exist on disk. It's a virtual file whose content the webpack loader generates from the styles of the current file. `./page.yak.css!=!./page` says that the loaders matching `.yak.css` files should run with the current file as input. The CSS they produce then goes through Next.js's regular CSS pipeline like any other plain CSS file.

Turbopack:

```tsx title="output"
import "data:text/css;base64,LkJ1dHRvbiB7Li4ufQ==";
```

Turbopack uses a different approach than webpack. Instead of an inline match resource, the SWC plugin encodes the extracted CSS as a base64 data URL. Turbopack handles that import as CSS without any resource matching tricks.

Vite:

```tsx title="output"
import "virtual:yak-css:{{__MODULE_PATH__}}.css";
```

For Vite, yak uses a virtual module system. The `{{__MODULE_PATH__}}` placeholder is replaced by the SWC plugin with the actual file path during transformation. The Vite plugin then resolves these virtual modules and serves the extracted CSS through Vite's standard CSS pipeline.

Rsbuild:

```tsx title="output"
import "data:text/css;base64,LkJ1dHRvbiB7Li4ufQ==";
```

Rsbuild uses the same base64 data URL import as Turbopack. The `@yak/react/rsbuild` plugin adds a rule that tells Rspack to treat `text/css` data URLs as CSS.

#### Transform tagged template literals [#transform-tagged-template-literals]

In another step, the tagged template literals are transformed into `@yak/react` runtime function calls. The static CSS parts are passed as class name strings directly, since yak controls all generated class names.

<SideBySide>
<div>

```tsx title="input"
import { styled } from "@yak/react";

const Button = styled.button`
  font-size: 2rem;
  font-weight: bold;
  color: blue;
  &:hover {
    color: red;
  }
`;
```

</div>
<div>

```tsx title="output"
import * as __yak from "@yak/react/internal";

const Button = __yak.__yak_button(
  "input_Button_m7uBBu",
);
```

</div>
</SideBySide>

The dynamic parts are preserved and apply the classes or CSS variables at runtime. There are two types of dynamic parts:

**Dynamic CSS property values** are transformed into CSS variables. The function is preserved and its return value is set as an inline style on the element at runtime:

<SideBySide>
<div>

```tsx title="input"
import { styled } from "@yak/react";

const Button = styled.button`
  color: ${(props) => (props.$primary ? "white" : "blue")};
`;
```

</div>
<div>

```tsx title="output"
import * as __yak from "@yak/react/internal";

const Button = __yak.__yak_button("input_Button_m7uBBu", {
  style: {
    "--input_Button__color_m7uBBu": (props) =>
      props.$primary ? "white" : "blue",
  },
});
```

</div>
</SideBySide>

**Dynamic CSS blocks** (conditional styles using `css`) are extracted into separate class names that are toggled at runtime based on the function's return value:

<SideBySide>
<div>

```tsx title="input"
import { styled, css } from "@yak/react";

const Button = styled.button`
  font-size: 2rem;
  ${(props) =>
    props.$primary &&
    css`
      background: black;
    `}
`;
```

</div>
<div>

```tsx title="output"
import { css } from "@yak/react/internal";
import * as __yak from "@yak/react/internal";

const Button = __yak.__yak_button(
  "input_Button_m7uBBu",
  (props) => props.$primary && css("input_Button__props_$primary_m7uBBu"),
);
```

</div>
</SideBySide>

#### Transform the css prop [#transform-the-css-prop]

The [css prop](/docs/features#css-prop) is resolved at compile time as well. The styles are extracted like all other styles and the prop itself is replaced with a plain `className` attribute:

<SideBySide>
<div>

```tsx title="input"
import { css } from "@yak/react";

const Elem = () => (
  <div
    css={css`
      color: red;
    `}
  />
);
```

</div>
<div>

```tsx title="output"
const Elem = () => <div className="input_Elem_m7uBBu" />;
```

</div>
</SideBySide>

Conditional styles stay conditional, but the condition now toggles class names instead of css blocks:

<SideBySide>
<div>

```tsx title="input"
const Elem = ({ on }) => (
  <div
    css={
      on
        ? css`
            color: red;
          `
        : css`
            color: blue;
          `
    }
  />
);
```

</div>
<div>

```tsx title="output"
const Elem = ({ on }) => (
  <div
    className={
      on ? "input_Elem_m7uBBu" : "input_Elem_m7uBBu-01"
    }
  />
);
```

</div>
</SideBySide>

The class names only have to be merged at render time if the element already has a `className` attribute or spread attributes. In that case the plugin hands the attributes to the small `__yak_mergeCssProp` runtime helper: the css value first, then one argument per merged source in JSX order.

```tsx title="output"
const Elem = (props) => (
  <div {...__yak_mergeCssProp(css("input_Elem_m7uBBu"), { className: "card" }, props)} />
);
```

#### Transform CSS [#transform-css]

The next step is to extract the CSS to a comment in the source code. Doing it here keeps the transformation fast. The SWC plugin
is already transforming the current file, and extracting the CSS there is way easier and faster than in the bundler loader.

<SideBySide>
<div>

```tsx title="input"
import { styled, css } from "@yak/react";

const Button = styled.button`
  font-size: 2rem;
  font-weight: bold;
  color: ${(props) => (props.$primary ? "white" : "blue")};
  ${(props) =>
    props.$primary
      ? css`background: black;`
      : css`background: white;`}
  &:hover {
    color: ${(props) => (props.$primary ? "red" : "blue")};
  }
`;
```

</div>
<div>

```tsx title="output (extracted CSS comment)"
/*YAK Extracted CSS:
.input_Button_m7uBBu {
  font-size: 2rem;
  font-weight: bold;
  color: var(--input_Button__color_m7uBBu);
}
.input_Button__props_\$primary_m7uBBu {
  background: black;
}
.input_Button__props_\$primary_m7uBBu-01 {
  background: white;
}
.input_Button_m7uBBu {
  &:hover {
    color: var(--input_Button__color_m7uBBu-01);
  }
}
*/
```

</div>
</SideBySide>

The comment starts with `/*YAK Extracted CSS:` so the loader can find it and pull out the CSS.
The runtime call right after the comment carries a `/*#__PURE__*/` annotation. It tells the minifier that the call has no side effects, so it can drop the component when nothing uses it.

Class names for conditional blocks include the condition they depend on, like `props_$primary` above. When the same name comes up again in a component, the plugin appends a counter such as `-01`. The `$` is escaped in the CSS selector but not in the class name string.

#### Fold static JSX usages [#fold-static-jsx-usages]

A styled component is itself a React component: when it renders it computes its class name, merges it with the received props and renders the actual DOM element. That is one extra component in the render tree for every usage. When a styled component is declared and used in the same file, the plugin already knows what it will render and folds the usage directly into the DOM element:

<SideBySide>
<div>

```tsx title="input"
import { styled } from "@yak/react";

const Card = styled.div`
  color: red;
`;

const Page = () => <Card>Hello</Card>;
```

</div>
<div>

```tsx title="output"
const Card = __yak.__yak_div("input_Card_m7uBBu");

const Page = () => (
  <div className="input_Card_m7uBBu">Hello</div>
);
```

</div>
</SideBySide>

React now renders one fewer component for this usage. Folding reaches further than fully static components: it inlines class-toggling props, collapses `styled(Component)` chains and folds the static `css` prop, and it keeps the runtime wrapper whenever it cannot prove the result is safe. See [Folding](/docs/folding) for the full rules.

### The bundler loaders/plugins [#the-bundler-loadersplugins]

Each bundler has a dedicated loader or plugin that takes the SWC plugin output, resolves cross-module dependencies, and delivers the final CSS through the bundler's native CSS pipeline.

- **Webpack** ([webpack-loader.ts](https://github.com/galaxuslabs/yak/blob/main/packages/yak-react/loaders/webpack-loader.ts)): The loader reads the extracted CSS from the SWC plugin comments, resolves cross-module dependencies, and hands the final CSS over as a plain CSS file so Next.js can process it like any other CSS file.
- **Turbopack** ([turbo-loader.ts](https://github.com/galaxuslabs/yak/blob/main/packages/yak-react/loaders/turbo-loader.ts)): The loader runs the SWC plugin itself, resolves cross-module dependencies in the extracted CSS and writes the result back into the data URL import. No virtual file is involved.
- **Vite** ([vite-plugin.ts](https://github.com/galaxuslabs/yak/blob/main/packages/yak-react/loaders/vite-plugin.ts)): The plugin resolves the virtual module imports and serves the extracted CSS through Vite's standard CSS pipeline.
- **Rsbuild** ([rsbuild/index.ts](https://github.com/galaxuslabs/yak/blob/main/packages/yak-react/rsbuild/index.ts)): The `@yak/react/rsbuild` plugin registers the Turbopack loader as an Rspack `pre` loader. Rspack's loader API is close enough to webpack's that the same data URL approach works unchanged, and it runs before Rsbuild's own SWC pass.

## The runtime part [#the-runtime-part]

See: [styled.tsx](https://github.com/galaxuslabs/yak/blob/main/packages/yak-react/runtime/styled.tsx)

Despite being called "zero-runtime", yak does ship a small runtime. Here, "zero-runtime" means no code manipulates the DOM or CSSOM. No `<style>` or `<link>` elements are injected at runtime.

Folded JSX usages from the compile time step skip this runtime completely, because they are plain DOM elements. The runtime only handles the usages that could not be folded.

The runtime is essentially a type-safe `classnames` utility. It takes the generated class name strings and the component's props, evaluates the dynamic functions (conditionals, CSS variable values), and returns:

- The final `className` string (merging any incoming `className`, base classes and conditional classes)
- An optional `style` object (setting CSS variable values from props)

Since it has no side effects, it works in both server and client components.

Here is a simplified version of what `__yak.__yak_button` does:

```tsx title="simplified runtime"
function __yak_button(className, ...dynamicParts) {
  // returns a React component
  return (props) => {
    const classNames = new ClassNames(props.className);
    classNames.add(className);
    const style = {};

    for (const part of dynamicParts) {
      if (typeof part === "function") {
        // conditional css block, e.g.:
        // (props) => props.$primary && css("Button_primary")
        // css("Button_primary") returns a function that
        // adds "Button_primary" to the class names
        const result = part(props);
        if (result) result(props, classNames, style);
      } else if (part?.style) {
        // css variable values, e.g.:
        // { style: { "--color": (props) => props.$color } }
        for (const [key, value] of Object.entries(part.style)) {
          style[key] = typeof value === "function" ? value(props) : value;
        }
      }
    }

    return <button className={classNames.value} style={style} />;
  };
}
```

When the component is rendered, the runtime passes the received props through all the dynamic functions, collects the resulting class names into a single string and CSS variable values into a `style` object, and forwards them as `className` and `style` props to the underlying DOM element.

## Context [#context]

The yak theme context lets you configure the theme of your yak application. It works for react-server components
and normal react components, including full support for async server functions in Next.js 15+.

The first part of the puzzle is to export two different modules for the server and the client.

```json title="package.json"
"./context": {
  "react-server": {
    "types": "./dist/context/index.server.d.ts",
    "default": "./dist/context/index.server.js"
  },
  "default": {
    "types": "./dist/context/index.d.ts",
    "default": "./dist/context/index.js"
  }
}
```

The package ships ESM only, so there's no `require` condition.

For the client components, we use a regular React context. The provider can also receive a Promise of the theme, which `useTheme` unwraps with `use`.

```tsx title="client: index.tsx"
"use client";
//
// This file is the client component (browser & ssr) version of index.server.tsx
//
import React, { ReactNode, createContext, use } from "react";

export interface YakTheme {}

/**
 * The yak theme context
 * @see https://github.com/galaxuslabs/yak/blob/main/packages/yak-react/runtime/context/README.md
 */
const YakContext = createContext<YakTheme>({});

/**
 * Returns the current yak theme context
 *
 * @see https://github.com/galaxuslabs/yak/blob/main/packages/yak-react/runtime/context/README.md
 */
export const useTheme = (): YakTheme => {
  const context = use(YakContext);
  return context instanceof Promise ? use(context) : context;
};

/**
 * Yak theme context provider
 *
 * @see https://github.com/galaxuslabs/yak/blob/main/packages/yak-react/runtime/context/README.md
 */
export const YakThemeProvider = ({
  children,
  theme = {},
}: {
  children: ReactNode;
  theme?: YakTheme;
}) => <YakContext.Provider value={theme}>{children}</YakContext.Provider>;
```

For the server components, we use a module that exports exactly the same API as the one for client components, but with a different implementation. It wraps the user's context function in React's `cache`, so it runs once per request. If that function is async, `useTheme` unwraps the Promise with `use`.

```tsx title="server: index.server.tsx"
//
// This file is the react-server component version of index.tsx
//

// @ts-ignore - in the current @types/react "cache" is not typed
import React, { ReactNode, cache, use } from "react";
import { YakTheme, YakThemeProvider as YakThemeClientProvider } from "./index.js";

// the following import might be changed by
// the user config in withYak to point to their own
// context
import { getYakThemeContext } from "@yak/react/context/baseContext";

/** Request based RSC YAK Context */
const getYakContext = cache(() => getYakThemeContext());
export const useTheme = (): YakTheme | undefined => {
  const theme: YakTheme | undefined | Promise<YakTheme> = getYakContext();
  return theme instanceof Promise ? use(theme) : theme;
};
export const YakThemeProvider = ({ children }: { children: ReactNode }) => {
  return <YakThemeClientProvider theme={getYakContext()}>{children}</YakThemeClientProvider>;
};
```

The `getYakThemeContext` function is imported from the user's `yak.context.ts` file. This file is a regular typescript file that exports a function that returns the theme context. You can use any API that works with server components, like [headers](https://nextjs.org/docs/app/api-reference/functions/headers) or [cookies](https://nextjs.org/docs/app/api-reference/functions/cookies),
to determine your theme values.

```tsx title="yak.context.ts"
import { cookies } from "next/headers";

export async function getYakThemeContext() {
  const cookieStore = await cookies();
  return {
    highContrast: cookieStore.get("highContrast")?.value === "true",
  };
}

declare module "@yak/react" {
  export interface YakTheme extends Awaited<ReturnType<typeof getYakThemeContext>> {}
}
```

The second part of the puzzle is a resolve alias. Every bundler integration (webpack and Turbopack through `withYak`, the Vite plugin and the Rsbuild plugin) points `@yak/react/context/baseContext` at your own `yak.context.ts` file in the project root. This is the webpack version:

```ts title="withYak/index.ts"
// With the following alias the internal @yak/react code
// is able to import a context which works for server components
const yakContext = resolveYakContext(
  yakOptions.contextPath,
  webpackConfig.context || process.cwd(),
);
if (yakContext) {
  webpackConfig.resolve.alias["@yak/react/context/baseContext"] = yakContext;
}
```
