Features

Markdown

Everything yak can do: styled components, the css prop, keyframes, global styles, theming with context, mixins, atoms and build-time constants in .yak files.

Static CSS

yak is built around extracting static CSS. You can write your styles as you normally would with styled-components. At build time, yak extracts these styles and assigns your component a class that holds them.

my-component.tsx
import { styled } from "@yak/react";

const Headline = styled.h1`
  font-size: 2rem;
  font-weight: bold;
  color: rgba(253, 29, 29, 1);
`;

const Component = () => {
  return <Headline>Hello there!</Headline>;
};
See transformed output
Static CSS
const Headline = styled.h1`
  font-size: 2rem;
  font-weight: bold;
  color: rgba(253, 29, 29, 1);
`;

See styled in the API reference. How does it work explains the extraction step by step, and Folding shows how static components become plain elements.

Dynamic styles

The static functionality itself is already very useful, but the bread and butter is an easy way to create dynamic styles. Styled-components popularized the approach of using props to drive dynamic CSS parts. With yak, you can use the exact same familiar API.

TSDynamic styles
import { css, styled } from '@yak/react';

const Paragraph = styled.p<{ $primary?: boolean }>`
  background: ${props => props.$primary ? "#BF4F74" : "white"};
  ${props => props.$primary ?
    css`
      color: white;
    ` :
    css`
      color: #BF4F74;
    `}
  font-size: 2rem;
  font-weight: bold;
`;

const Component = () => {
  return (
    <>
      <Paragraph $primary>Hello there primary!</Paragraph>
      <Paragraph>Hello there non-primary!</Paragraph>
    </>
  );
}

Each css block inside a function gets its own class, which the component adds at runtime when the function returns that block. A function that returns a plain value, like the background above, becomes a CSS variable instead. The component sets that variable in the element's style attribute from the function's return value.

See transformed output
Compiled output
const Paragraph = styled.p`
  background: ${props => props.$primary ? "#BF4F74" : "white"};
  ${props => props.$primary ?
    css`
      color: white;
    ` :
    css`
      color: #BF4F74;
    `}
  font-size: 2rem;
  font-weight: bold;
`;

Compatible with utility-first CSS frameworks (e.g. Tailwind)

yak is fully compatible with utility-first CSS frameworks like Tailwind. Use the atoms function to reference classes from these frameworks.

TSTailwind with atoms
import { styled, atoms } from '@yak/react';

const Header = styled.nav<{ $variant?: 'primary' | 'secondary' }>`
  ${({$variant}) => $variant === "primary"
    ? atoms("mx-auto flex max-w-7xl items-center justify-between p-6 lg:px-8")
    : atoms("bg-white hover:bg-gray-100 text-gray-800 font-semibold py-2 px-4 border border-gray-400 rounded shadow")
  }
`

You can even define your own functions that manipulate the styles of your components based on props:

TSConditional atoms
import { styled, atoms } from '@yak/react';

const Header = styled.nav<{ $variant?: 'primary' | 'secondary' }>`
  ${atoms("mx-auto", (props, classNames, style) => {
    if(props.$variant === "primary"){
      classNames.add("flex");
    } else {
      classNames.add("bg-white");
    }
  })};
`
See transformed output
Compiled output
const Header = styled.nav`
  ${({$variant}) => $variant === "primary"
    ? atoms("mx-auto flex max-w-7xl items-center justify-between p-6 lg:px-8")
    : atoms("bg-white hover:bg-gray-100 text-gray-800 font-semibold py-2 px-4 border border-gray-400 rounded shadow")
  }
`

See atoms in the API reference.

Animations

To create CSS animations, use the keyframes API and specify the keyframes for the animation you want to create. You can use it in your animation declarations within the same file. The compiler writes the generated animation name straight into the CSS rule.

my-component.tsx
import { keyframes, styled } from "@yak/react";

const fadeIn = keyframes`
  0% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
`;

const FadeInButton = styled.button`
  animation: 1s ${fadeIn} ease-out;
`;
See transformed output
Animations
const fadeIn = keyframes`
  0% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
`;

const FadeInButton = styled.button`
  animation: 1s ${fadeIn} ease-out;
`;

See keyframes in the API reference.

Global styles

For styles that don't belong to a single component like CSS resets, @font-face declarations, :root variables or view transitions, you can use the globalStyle API. The styles are extracted at build time like all other yak styles and applied as soon as the file declaring them is imported, typically from your root layout or page entry.

globals.tsx
import { globalStyle, keyframes } from "@yak/react";

const fadeIn = keyframes`
  from {
    opacity: 0;
  }
`;

globalStyle`
  :root {
    --spacing: 4px;
  }

  body {
    margin: 0;
    font-family: sans-serif;
  }

  ::view-transition-new(root) {
    animation: ${fadeIn} 200ms ease;
  }
`;
layout.tsx
import "./globals";

Global styles are part of the stylesheet, not part of the render tree. Unlike styled-components createGlobalStyle there is no component to mount or unmount.

If you need conditional styling, you can use CSS native functionality for that like @media, @supports, :root[data-theme] or :has().

You can interpolate everything that is known at build time like constants, keyframes, static css mixins and even references to your styled components:

my-dialog.tsx
import { globalStyle, styled } from "@yak/react";

export const Dialog = styled.dialog`
  padding: var(--spacing);
`;

globalStyle`
  /* prevent scrolling while the dialog is open */
  body:has(${Dialog}[open]) {
    overflow: hidden;
  }
`;

Runtime values (${(props) => ...}) are not supported inside globalStyle as a global rule has no element to attach them to. Declare a CSS custom property instead and toggle it via an attribute or class on the root element:

globals.tsx
import { globalStyle } from "@yak/react";

globalStyle`
  :root {
    --sidebar-width: 280px;
  }

  :root[data-collapsed] {
    --sidebar-width: 64px;
  }
`;
See transformed output
Global styles
globalStyle`
  body {
    margin: 0;
  }
`;

See globalStyle in the API reference.

Mixins

Mixins are declarations of the css utility function. You can declare a variable which holds the css declaration and use it inside your styled declarations.

my-component.tsx
import { css, styled } from '@yak/react';

const mixin = css`
  color: green;
`;

const MyComp = styled.div`
  background-color: yellow;
  ${mixin}
`;

You can also make it dynamic, where the props are passed to the mixin.

my-component.tsx
import { css, styled } from "@yak/react";

const mixin = css`
  color: ${(props) => (props.$green ? "green" : "blue")};
`;

const MyComp = styled.div`
  background-color: yellow;
  ${mixin}
`;

A mixin doesn't get a class name of its own. At build time its styles are inlined under the class of every component that uses it. Dynamic parts like the color function are copied into that component too, so the component sets the CSS variable itself.

See transformed output
input
const mixin = css`
  color: ${(props) => props.$green ? 'green' : 'blue'};
`;

const MyComp = styled.div`
  background-color: yellow;
  ${mixin}
`;

See css in the API reference.

Automatic CSS variables

You may have noticed that some functions return a css block and others return a plain value. If the property name is already written in the template and only its value is dynamic, return the value directly. The compiler turns these into CSS variables at build time.

TSAutomatic CSS variables
import { styled } from '@yak/react';

const Box = styled.div<{ $variant: "primary" | "secondary", $color: string }>`
  font-size: ${props => props.$variant === "primary" ? "2rem" : "1rem" };
  color: ${props => props.$color};
  display: flex;
`

The component sets each variable through its style prop. Variable names are scoped to the file and component, so they don't clash with your own custom properties.

See transformed output
Compiled output
const Box = styled.div`
  font-size: ${props => props.$variant === "primary" ? "2rem" : "1rem" };
  color: ${props => props.$color};
  display: flex;
`

Theming

Theming lets you pass the same values to many components, which matters more as your application grows.

Static Design Tokens vs Dynamic Context

For static design tokens like colors, spacing, typography, and other unchanging design values, you often don't need a context at all. You can declare and import them anywhere in your application:

// theme.ts
export const colors = {
  primary: "#009688",
  secondary: "#ff5722",
  background: "#ffffff",
};

export const spacing = {
  xs: "4px",
  sm: "8px",
  md: "16px",
  lg: "32px",
};

// Button.tsx
import { styled } from "@yak/react";
import { colors, spacing } from "./theme";

const Button = styled.button`
  background: ${colors.primary};
  padding: ${spacing.md};
`;

This approach has better performance because these values can be extracted and optimized during build time, whereas context values are resolved at runtime.

Dynamic Theming with Context

Use the context approach described below when you need dynamic theming that changes based on user preferences, server state, or runtime conditions. yak integrates it for both Server Components and Client Components, with no difference in usage. Wrap your root with YakThemeProvider and add a yak.context.ts file to your root directory.

yak.context.ts
import { cookies } from "next/headers";

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

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

getYakThemeContext() can be async, so you can await request APIs like cookies() and headers() inside it. yak resolves the promise with React's use() hook and passes the finished theme object to your styled components. Your template functions always receive a plain, synchronous theme.

Once this context file is in place, you can access the theme props on every component.

TSDynamic theming
import { styled, css } from '@yak/react';

const Button = styled.button`
  display: block;
  ${({theme}) => theme.brandName === 'myFancyBrand'
    ? css`
      color: black;
      background-color: white;
    `
    : css`
      color: blue;
      background-color: white;
    `}
`;

See Theming in the API reference for useTheme, YakThemeProvider and yak.context.ts.

CSS Prop

yak supports the css prop out of the box, a shorthand for adding styles to an element. Like inline styles, it lets you write local styles for certain elements on your page. Unlike inline styles, it lets you use selectors that target wrapped elements.

my-component.tsx
import { css } from "@yak/react";

const Component = () => {
  return (
    <div
      css={css`
        color: red;
      `}
    >
      Hello there!
    </div>
  );
};

It's meant for simple styling requirements, where you don't have to think about a name for a specialized component.

The css prop is allowed on any element that can accept className and style.

Its value has to be styles written in place: a css template or an atoms() call and logical combinations of them. Everything else fails the build, because yak only compiles the styles it sees on the element. To combine several styles, nest them in one template instead of passing an array:

my-component.tsx
const base = css`
  color: red;
`;
const highlight = css`
  font-weight: bold;
`;

const Component = () => {
  return (
    <div
      css={css`
        ${base};
        ${highlight};
      `}
    >
      Hello there!
    </div>
  );
};

Set the strictCssProp option to false to leave unsupported values untouched instead, for example when another library on the same element owns the css prop.

If you prefer working with the utility first approach, you can use our atoms function in conjunction with the css prop.

my-component.tsx
import { atoms } from "@yak/react";

const Component = (props) => {
  return <div css={atoms("bg-red-500", props.primary && "bg-blue-500")}>Hello there!</div>;
};

TypeScript

To get the correct types, add the following line to the top of the file:

my-component.tsx
/** @jsxImportSource @yak/react */

import { css } from "@yak/react";

const Component = () => {
  return (
    <div
      css={css`
        color: red;
      `}
    >
      Hello there!
    </div>
  );
};

Or add this to your tsconfig.json and all your css props will have the correct types.

tsconfig.json
{
  "compilerOptions": {
    "jsxImportSource": "@yak/react"
  }
}

See the css prop in the API reference.

Cross-File Imports

yak supports importing constants, mixins, and selectors from normal modules or .yak files. This works consistently across all bundlers (webpack, Turbopack, Rsbuild and Vite).

Build-Time Constants with .yak Files

.yak.ts / .yak.tsx files are evaluated at build time, which means you can use real JavaScript logic to compute values that then become part of your static CSS. This is the key difference from ordinary .ts files. You can map, iterate, and calculate at build time.

theme/spacing.yak.ts
export const spacing = {
  xs: "4px",
  sm: "8px",
  md: "16px",
  lg: "32px",
};

export const spacingTokens = Object.entries(spacing).reduce(
  (acc, [key, value]) => ({
    ...acc,
    [key]: `padding: ${value}; margin: ${value};`,
  }),
  {} as Record<string, string>,
);
components/Box.tsx
import { styled, css } from "@yak/react";
import { spacingTokens } from "../theme/spacing.yak";

const Box = styled.div`
  ${css`
    ${spacingTokens.sm}
  `}
  ${css`
    ${spacingTokens.md}
  `}
`;

Here spacingTokens is computed at build time: an ordinary .ts file would keep Object.entries() and .reduce() as runtime code, but a .yak file evaluates these expressions and inlines the final object. The resulting CSS contains fully static class names, which means no JavaScript runs for these styles at runtime, even though their values were computed with array operations. You can reference any key like spacingTokens.sm as a build-time constant.

This also works for generating media query ranges, animation durations from a config, or any other calculated value.

Importing Mixins Across Files

You can also import CSS mixins defined with css from other regular modules:

mixins.tsx
import { css } from "@yak/react";

export const truncate = css`
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
`;
components/Label.tsx
import { styled } from "@yak/react";
import { truncate } from "../mixins";

const Label = styled.span`
  ${truncate}
  max-width: 200px;
`;

Generic Yak Component

If you use yak to wrap a generic component, you can use the GenericYakComponentOf component to preserve the type of your component while still being able to use it as a regular yak component.

my-generic-component.tsx
import { type ReactElement } from "react";
import { type GenericYakComponentOf, styled } from "@yak/react";

type GenericComponentType = <T extends object>(props: T) => ReactElement<T>;

const MyGenericComponent: GenericComponentType = (props) => <div {...props}>hello</div>;

const StyledComponent = styled(MyGenericComponent)`
  color: red;
` as GenericYakComponentOf<GenericComponentType>;

// usage
<StyledComponent<{ myProp: string }> myProp="test" />;

This also works for cases where you want to add properties that are not part of the generic component's props.

my-generic-component.tsx
import { type ReactElement } from "react";
import { type GenericYakComponentOf, styled } from "@yak/react";

type GenericComponentType = <T extends object>(props: T) => ReactElement<T>;

const MyGenericComponent: GenericComponentType = (props) => <div {...props}>hello</div>;

type MyAdditionalProps = {
  additionalProp: string;
};

const StyledComponent = styled(MyGenericComponent)`
  color: red;
` as GenericYakComponentOf<GenericComponentType, MyAdditionalProps>;

// usage
<StyledComponent<{ myProp: string }> myProp="test" additionalProp="me" />;

See Types in the API reference.