Migration from styled-components

Markdown

Move from styled-components to yak, a fast alternative that works with React Server Components. Most code only needs a new import. This guide covers the rest.

Guide

One of yak's core goals is an easy migration from styled-components. This guide walks you through the migration, including common scenarios and edge cases.

ESLint plugin

To make the migration easier, our ESLint plugin flags potential issues during the migration and afterwards.

See the ESLint plugin documentation to install it in your project.

Feature comparison

Supported

  • Styled syntax (incl. static and dynamic parts)
  • Css tagged template literal
  • Css prop
  • Keyframes
  • Attrs
  • References to other components
  • Mixins
  • Global styles (createGlobalStyle becomes globalStyle)
  • If some features are missing, please let us know and open a feature request on GitHub

Missing

  • Object syntax
  • css prop values that are not written in place, like variables, arrays or objects
  • as prop: you can replace it with a type-safe alternative
  • .withConfig

Just change the import

The following cases need the least work, and all other changes build on this one. Change the import from styled-components to @yak/react and change the import of styled from a default import to a named import.

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

Static component styles

For components with static styles, updating the import is enough:

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

const Button = styled.button`
  background: #bf4f74;
  color: white;
  padding: 1em 2em;
  border-radius: 4px;
  transition: all 0.2s ease-in-out;

  &:hover {
    transform: translateY(-1px);
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  }
`;

Static mixins

For static mixins, you can just change the import and you're done.

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

const mixin = css`
  color: green;
  font-size: 1rem;
`;

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

This applies to all static mixins that you use in your styled components, including exported mixins.

Keyframes

For keyframes you can just change the import and you're done.

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

const rotate = keyframes`
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
`;

const Rotate = styled.div`
  display: inline-block;
  animation: ${rotate} 2s linear infinite;
  padding: 2rem 1rem;
  font-size: 2rem;
`;

Component references

Styling based on other components remains straightforward. You only need to change the import.

component.tsx
import styled from "styled-components";
import { styled } from "@yak/react";
import { Button } from "./button";

const Container = styled.div`
  ${Button} {
    color: red;
    margin: 0.5em;

    &:hover {
      color: darkred;
    }
  }
`;

Attrs

If you use .attrs in your styled components, you can just change the import and you're done.

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

const Input = styled.input.attrs((props) => ({
  type: "text",
  size: props.size || "1em",
}))`
  color: palevioletred;
  font-size: 1em;
  border: 2px solid palevioletred;
  border-radius: 3px;
`;

The same applies to the .attrs function for wrapped components.

component.tsx
import styled from "styled-components";
import { styled } from "@yak/react";
import { Button, type ButtonProps } from "./button";

const LocalButton = styled(Button).attrs<Partial<ButtonProps>>(() => ({
  tabIndex: 0,
}))`
  color: red;
`;

Property 'as' does not exist

yak does not support the as prop. Instead, it provides a type-safe way to render dynamic HTML tags. By creating a reusable component with a defined set of allowed tags, you ensure valid HTML with stronger type safety:

type ElementType = "h1" | "h2" | "h3" | "h4" | "h5" | "h6";

const HtmlTag = <T extends ElementType>({
  tag: Tag,
  ...props
}: {
  tag: T;
  children?: React.ReactNode;
} & React.JSX.IntrinsicElements[T]) => <Tag {...(props as any)} />;

You can then pass one of the predefined tags of ElementType as a prop and style it like any other component:

<Title tag="h1">Heading 1</Title>;

const Title = styled(HtmlTag)`
  font-weight: bold;
`;

This approach is preferred over the as prop because it lists the allowed HTML tags explicitly. That gives you stronger type safety and prevents invalid usage like <Image as="button">, which would be valid in styled-components.

Change some more things

The last part of the migration guide is about more complex cases where you need to change a bit more than just the import.

Dynamic component styles

This is the most complex part of the migration. Unlike styled-components, yak distinguishes between dynamic css properties and dynamic property values. If you use property values, you shouldn't need to change more than the import.

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

const Button = styled.button<{ $primary: boolean }>`
  background: #bf4f74;
  color: ${(props) => (props.$primary ? "white" : "#BF4F74")};
`;

This is transformed into a CSS variable, and its value is set at runtime.

transformed.tsx
// pseudo code
const Button = styled.button`
  background: #bf4f74;
  color: var(--yak-1);
`;

If you use dynamic css properties, you need to use the css tag to define the css.

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

const Button = styled.button<{ $primary: boolean }>`
  background: #BF4F74;
  ${props => props.$primary
  ? ` // [!code --]
  ? css`
    color: white;
    font-size: 1rem;
    padding: 1em 2em;
  : ` // [!code --]
  : css`
    color: #BF4F74;
    font-size: 2rem;
    padding: 2em 4em;
  `
  }
`;

When yak finds a css tag, it transforms the css into a class name at build time.

transformed.tsx
// pseudo code

const Button = (props) => (
  <button className={props.$primary ? "yak-1" : "yak-2"}>Click me</button>
);

You can nest the css tags arbitrarily deep and yak will transform them into class names and css variables. You can't nest arbitrary styles inside property values, but you can nest property values inside css tags. To keep your styles fast, simplify them and avoid nesting where you can.

Dynamic mixins

If you use dynamic mixins in your styled components, you need to change them the same way as you would with dynamic component styles.

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

const mixin = css<{ $primary: boolean }>`
  color: green;

  ${(props) =>
    props.$primary
      ? css`
          background: white;
        `
      : css`
          background: black;
        `}
`;

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

Dynamic mixins work differently in yak than in styled-components. With yak you're currently not able to export a dynamic mixin and use it in other components. As we transform the styles on a per-file basis, we can't share dynamic values between files. If you need to use a dynamic mixin in multiple components, you unfortunately need to duplicate the mixin in each file or move the components to the same file.

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

import { mixin } from "./mixin";
const mixin = css<{ $primary: boolean }>`
  color: green;
  ${(props) =>
    props.$primary
      ? css`
          background: white;
        `
      : css`
          background: black;
        `}
`;

const MyComp = styled.div<{ $primary: boolean }>`
  background-color: yellow;
  ${mixin};
`;
mixin.ts
import { css } from "styled-components";

export const mixin = css<{ $primary: boolean }>`
  color: green;
  ${(props) =>
    props.$primary
      ? `background: white;`
      : `background: black;`}
`;

Global styles

styled-components createGlobalStyle returns a component that you render. In yak global styles are a statement and globalStyle applies as soon as the file with the global styles is imported. To migrate your global styles, remove the <GlobalStyle /> JSX and import the file where you used to render it. For app-wide resets that is your root layout, but if it only rendered on a certain page, import the file from that page instead:

globals.tsx
import { createGlobalStyle } from "styled-components";
import { globalStyle } from "@yak/react";

const GlobalStyle = createGlobalStyle`
globalStyle`
  body {
    margin: 0;
  }
`;
layout.tsx
import { GlobalStyle } from "./globals";
import "./globals";

export default function Layout({ children }) {
  return (
    <html>
      <body>
        <GlobalStyle />
        {children}
      </body>
    </html>
  );
}

Theme props like ${(props) => props.theme.color} inside global styles become CSS custom properties toggled via an attribute or class on the root element. A conditionally rendered <GlobalStyle /> becomes a CSS condition (@media, @supports, :root[data-theme]). See Global styles for the patterns.

Reference external styles

If you reference external styles (styles that aren't component styles) in your styled components, write the selector as it is. yak writes plain CSS, so it does not rename .myGlobalClass:

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

const Button = styled.button`
  color: blue;

  .myGlobalClass {
    color: red;
  }
`;

Functions that generate styles

If you have utility functions that generate styles, you need to change them. Either turn them into static values that cover every variant, or create a dynamic mixin.

util.ts
export const generateAllColors = (primaryColor: string) => {
  return {
    primary: primaryColor,
    secondary: "#F7B801",
  };
};
export const allColors = {
  red: {
    primary: "red",
    secondary: "#F7B801",
  },
  blue: {
    primary: "blue",
    secondary: "#F7B801",
  },
  green: {
    primary: "green",
    secondary: "#F7B801",
  },
};

or

component.tsx
import styled from "styled-components";
import { styled } from "@yak/react";
import { generateAllColors } from "./util";

const Button = styled.button<{ $color: string }>`
  color: ${generateAllColors};
  color: ${(props) => props.$color};
`;

CSS prop (optional)

If you use the css prop in your styled components, changing the import is almost all you need. You also need to tell TypeScript that native JSX elements can have a css prop. To do that, add the following to your tsconfig.json.

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

To use the css prop in a single file only, add the following to the top of that file instead.

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

Then change the import:

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

const MyComponent = () => {
  return (
    <div
      css={css`
        background: papayawhip;
        color: red;
      `}
    />
  );
};

If you need dynamic values, use the same interpolation as in your other styled components.

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

const MyComponent = ({ color }: { color: string }) => {
  return (
    <div
      css={css`
        background: papayawhip;
        color: ${() => color};
      `}
    />
  );
};

Move some code to yak files

Migrating styled-components code with complex logic can take more work. Yak files are evaluated at build time and can hold static values that yak extracts during the build. You can create a .yak.ts or .yak.tsx file anywhere in your project and yak picks it up.

Styles that use calculated values

Move calculated values to .yak.ts files:

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

const RADIUS = 5;
const CIRCUMFERENCE = 2 * Math.PI * RADIUS;
import { CIRCUMFERENCE } from "./constants.yak";

const Circle = styled.div`
  width: ${CIRCUMFERENCE}px;
  height: ${CIRCUMFERENCE}px;
  border-radius: 50%;
  border: 1px solid black;
`;
constants.yak.ts
const RADIUS = 5;
export const CIRCUMFERENCE = 2 * Math.PI * RADIUS;

As a rule of thumb, yak doesn't run JavaScript to resolve interpolated values unless the code lives in a .yak.ts (or .yak.js) file or is imported from a yak file.

Values that don't need code to run before they have a value (so everything except calculations and string concatenations) work without yak files. Prefer writing your styles that way to keep your build times fast.

Troubleshooting

For build errors, missing styles and how to see what yak generates, see Troubleshooting.

Styles from styled-components win

If you wrap a styled-components component with a yak component while both libraries run side by side, add && before the styles to raise the specificity. Otherwise styled-components always wins:

component.tsx
const Button = styled(StyledComponentsButton)`
  && {
    color: red;
  }
`;

Once you've fully migrated to yak, you can remove the && again.