Yak

Migration from styled-components

next-yak provides a RSC compatible API which is very similar to the styled-components API. In most cases the only change necessary is the import. However there are some edge cases which have to be migrated manually.

Guide

One of the core goals of next-yak is to provide a seamless migration path from styled-components to next-yak. This comprehensive guide will walk you through the migrating, covering common scenarios and edge cases you might encounter.

Feature comparison

Supported

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

Missing

  • Object syntax
  • Dynamic usage of the css prop
  • .withConfig
  • Special handling for the as prop
  • CreateGlobalStyle - You can just import any global styles in your files directly
  • As of next.js 15, the server components theme provider is not supported. We are working on a solution for this.

Just change the import

You need to do the least amount of work for the following changes. All other changes build upon this one. Just change the import from styled-components to next-yak 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 'next-yak';

Static component styles

For components with static styles, simply updating the import is sufficient:

component.tsx
import styled from 'styled-components';
import { styled } from 'next-yak';
 
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);
  }
`;

Important Note on Selectors: While most selectors work out of the box, you might encounter the error: Selector "h1" is not pure (pure selectors must contain at least one local class or id). To resolve this, either:

  1. Update your Next.js PostCSS version
  2. Add an ampersand (&) before the selector

For more detailed information, refer to the FAQ.

Static mixins

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

component.tsx
import styled, { css } from 'styled-components';
import { styled } from 'next-yak';
 
const mixin = css`
  color: green;
  font-size: 1rem;
`;
 
const MyComp = styled.div`
  background-color: yellow;
  ${mixin};
`;

This counts for all static mixins that you use in your styled components including exported mixins.

Important note on semicolons: You need to add a semicolon after the mixin to separate it from the rest of the styles. Otherwise the parser can not differentiate between a selector, a constant or a mixin.

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 'next-yak';
 
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 just need to change the import

component.tsx
import styled from 'styled-components';
import { styled } from 'next-yak';
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 'next-yak';
 
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 'next-yak';
import { Button, type ButtonProps } from './button';
 
const LocalButton = styled(Button).attrs<Partial<ButtonProps>>(() => ({
    tabIndex: 0,
  }))`
  color: red;
`;

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 will be the most complex part of the migration. Next-yak differentiates between dynamic css properties and property values which is different than styled-components. 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 'next-yak';
 
const Button = styled.button<{ $primary: boolean }>`
  background: #BF4F74;
  color: ${props => props.$primary ? "white" : "#BF4F74"};
`;

This will be transformed to a css variable that gets injected during build time.

transformed.tsx
// pseudo code
const Button = styled.button`
  background: #BF4F74;
  color: var(--next-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 { styled } from 'next-yak';
 
const Button = styled.button<{ $primary: boolean }>`
  background: #BF4F74;
  ${props => props.$primary
  ? `
  ? css`
    color: white;
    font-size: 1rem;
    padding: 1em 2em;
  : `
  : css`
    color: #BF4F74;
    font-size: 2rem;
    padding: 2em 4em;
  `
  }
`;

When next-yak encounters a css tag, it will transform the css into a class name that gets injected during build time.

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

You can nest the css tags arbitrarily deep and next-yak will transform them into class names and css variables. It's important to note that you can't nest arbitrary styles inside property values, but you can nest property values inside css tags. Generally, you should try to simplify your styles and avoid nesting as much as possible to keep your styles as fast as possible.

Dynamic mixins

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

component.tsx
import styled, { css } from 'styled-components';
import { styled } from 'next-yak';
 
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 next-yak than in styled-components. With next-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 { styled } from 'next-yak';
 
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;`
  }
`;

Reference external styles

If you reference external styles (styles that aren't component styles) in your styled components, you need to change the identifier in addition to the import.

component.tsx
import styled from 'styled-components';
import { styled } from 'next-yak';
 
const Button = styled.button`
  color: blue;
 
  .myGlobalClass {
  :global(.myGlobalClass) {
    color: red;
  }
`;

The reason for this is that next-yak transforms your styles to module-css styles. And if you want to reference global selectors, you need to use the :global selector. More can be found in the docs of css-modules

Functions that generate styles

If you have somewhere utility functions that generate styles, you need to change them. Either transform them so that they cover every different static style 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 'next-yak';
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, you can almost just change the import and you're done. But you also need to tell TypeScript that native JSX elements can have a css prop. In order to do that, you can add the following to your tsconfig.json.

tsconfig.json
{
  "compilerOptions": {
      "jsxImportSource": "next-yak"
  }
}

Or if you want to use the css prop in a single file, you can just add the following to the top of the file.

component.tsx
/** @jsxImportSource next-yak */

And afterwards you can just change the import and you're done.

component.tsx
import styled from 'styled-components';
import { css, styled } from 'next-yak';
 
const MyComponent = () => {
  return (
    <div
      css={css`
        background: papayawhip;
        color: red;
      `}
    />
  );
};

If you need dynamic values you can use the same interpolation as you would with your other styled components.

component.tsx
import { css } from "next-yak";
 
const MyComponent = ({color}: {color: string}) => {
  return (
    <div
      css={css`
        background: papayawhip;
        color: ${() => color};
      `}
    />
  );
};

Move some code to yak files

Migrating styled-components with complex logic might need a bit more work. Yak files are special files that are evaluated during build time and can be used to store static values that are extracted during build time. You can create a .yak.ts or .yak.tsx file everywhere in your project and it will be picked up by next-yak.

Important note on .yak.tsx files: As yak files are evaluated on every code change, they will slow down your dev experience and your production build times. They should only be used as a last resort.

Styles that use calculated values

Move calculated values to .yak.ts files:

component.tsx
import styled from 'styled-components';
import { styled } from 'next-yak';
 
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;

A general rule of thumb is that next-yak doesn't run any JavaScript code to figure out any interpolated values except the code recides in a .yak.ts(or .yak.js) file or is imported from a yak file.

Everything except calculations and string contatinations, or more generally, values that don't need a runtime in order to have a value, should work out of the box without any yak files and should be the preferred way to write your styles to keep your build times as fast as possible.

Troubleshooting

This section covers common issues you might encounter during the migration process and how to resolve them.

Debugging

If you encounter issues during the migration process, you can enable debug mode to get more information about what's going wrong.

Just enable it in the next config file.

next.config.js
export default withYak({
  experiments: {
    debug: {
      filter: (path: string) => path.includes("myPage"),
      type: "all", // or "css" or "js"
    },
  },
},nextConfig);

And you should see the transformed files and styles. This can help you to understand what's going wrong.

My styles are not applied

If you see that your styles are not applied, you should check if the styles are transformed correctly. Debugging can help you with that.

If you wrap a styled-component component with a next-yak component, you need to add && before the styles to increase the specificity as otherwise styled-components would always win.

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

This is only needed if you simultaneously use styled-components and next-yak. If you fully migrated to next-yak, you don't need to do that.

I get a compile error

Please read the error message carefully. It should give you a hint on what's going wrong.

If you can't figure it out, please open an issue on GitHub

I think I found a bug

Please open an issue on GitHub