storybook: Can't get emotion's css prop working inside storybook

Describe the bug I can’t seem to get emotion’s css prop to work inside of Storybook. Seems like emotion is now the dominant css-in-js library, and the css prop is emotion’s recommended primary way of styling components, so I thought I was choosing a well-travelled happy path here, but I’m stuck. I’m very open to the fact that I’m doing something dumb though, of course.

In my actual project repo I’m getting the same thing described here and here. I tried all of the recommendations in both those threads, and still no joy – and it looks like I’m not the only one.

So, I created a brand new React Storybook repo to just hello-world using emotion css prop and storybook. And I can’t get that to work either! I’m wondering if someone could look at the super simple repro repo I made and tell me where I went wrong, here it is:

https://github.com/jaredh159/storybook-emotion-css

I’d be up for submitting a PR to the docs clarifying how to do this, if someone can show me where I messed up.

To Reproduce Steps to reproduce the behavior:

  1. git clone git@github.com:jaredh159/storybook-emotion-css.git
  2. cd storybook-emotion-css && yarn && yarn storybook
  3. Load http://localhost:6016 and see the error 😦

TypeError: Cannot read property 'name' of null

Expand for full error message
ERROR in ./src/CssPropButton.js
Module build failed (from ./node_modules/babel-loader/lib/index.js):
TypeError: Cannot read property 'name' of null
   at getDeclaratorName (/test-repo/node_modules/babel-plugin-emotion/dist/babel-plugin-emotion.cjs.dev.js:212:32)
   at getIdentifierName (/test-repo/node_modules/babel-plugin-emotion/dist/babel-plugin-emotion.cjs.dev.js:267:24)
   at getLabelFromPath (/test-repo/node_modules/babel-plugin-emotion/dist/babel-plugin-emotion.cjs.dev.js:190:19)
   at transformExpressionWithStyles (/test-repo/node_modules/babel-plugin-emotion/dist/babel-plugin-emotion.cjs.dev.js:450:19)
   at transformCssCallExpression (/test-repo/node_modules/babel-plugin-emotion/dist/babel-plugin-emotion.cjs.dev.js:708:31)
   at /test-repo/node_modules/babel-plugin-emotion/dist/babel-plugin-emotion.cjs.dev.js:797:9
   at Array.forEach (<anonymous>)
   at /test-repo/node_modules/babel-plugin-emotion/dist/babel-plugin-emotion.cjs.dev.js:796:26
   at Array.forEach (<anonymous>)
   at Object.emotionCoreMacroThatsNotARealMacro [as @emotion/core] (/test-repo/node_modules/babel-plugin-emotion/dist/babel-plugin-emotion.cjs.dev.js:794:27)
   at PluginPass.ImportDeclaration (/test-repo/node_modules/babel-plugin-emotion/dist/babel-plugin-emotion.cjs.dev.js:891:45)
   at newFn (/test-repo/node_modules/@babel/traverse/lib/visitors.js:193:21)
   at NodePath._call (/test-repo/node_modules/@babel/traverse/lib/path/context.js:53:20)
   at NodePath.call (/test-repo/node_modules/@babel/traverse/lib/path/context.js:40:17)
   at NodePath.visit (/test-repo/node_modules/@babel/traverse/lib/path/context.js:88:12)
   at TraversalContext.visitQueue (/test-repo/node_modules/@babel/traverse/lib/context.js:118:16)
@ ./stories/index.stories.js 5:0-49 6:143-156
@ ./stories sync \.stories\.js$
@ ./.storybook/config.js
@ multi ./node_modules/@storybook/core/dist/server/common/polyfills.js ./node_modules/@storybook/core/dist/server/preview/globals.js ./.storybook/config.js (webpack)-hot-middleware/client.js?reload=true

Expected behavior I expected to be able to use the css prop from emotion within Storybook.

Code snippets The repro repo does add a .babelrc inside the stories dir as recommended in the emotion docs and on this similar issue, like so:

{
  "presets": ["@emotion/babel-preset-css-prop"]
}

I also tried this workaround, but no joy.

System:

  • OS: Mac
  • Device: Macbook Pro 2015
  • Browser: Chrome
  • Framework: React
  • Version: 5.1.9

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 26
  • Comments: 61 (16 by maintainers)

Commits related to this issue

Most upvoted comments

I fixed it with the settings below. Hope it helps someone.

pacakge.json

"react": "^17.0.1",
"react-dom": "^17.0.1",
"@storybook/react": "^6.1.21",
"@emotion/react": "^11.1.2",
"typescript": "^4.1.3",

.storybook/main.js

module.exports = {
  reactOptions: {
    fastRefresh: true,
    strictMode: true,
  },
  stories: ['../src/**/*.stories.@(ts|tsx|mdx)'],
  addons: ['@storybook/addon-links', '@storybook/addon-essentials'],
  webpackFinal: async (config) => {
    config.module.rules[0].use[0].options.presets = [
      require.resolve('@babel/preset-env'),
      require.resolve('@babel/preset-typescript'),
      [
        require.resolve('@babel/preset-react'),
        {
          runtime: 'automatic',
          importSource: '@emotion/react',
        },
      ],
    ]

    config.module.rules[0].use[0].options.plugins = [
      ...config.module.rules[0].use[0].options.plugins,
      '@emotion/babel-plugin',
    ]

    return config
  },
}

We’ve just encountered this issue. The project uses "typescript": "^4.3.2", "@emotion/react": "^11.4.1", "@storybook/react": "^6.3.8".

tsconfig.json has "jsx": "react-jsx", "jsxImportSource": "@emotion/react", settings.

To fix it, npm i -D @emotion/babel-preset-css-prop and in .storybook/main.js add it to the default presets:

module.exports = {
  stories: […],
  addons: ['@storybook/addon-links', '@storybook/addon-essentials'],
  babel: async (options) => {
    options.presets.push('@emotion/babel-preset-css-prop')
    return options
  },
}

So far so good.

@DominicTobias-b1 — recently upgraded to Emotion 11 with the latest storybook (6.1.xx). Here are the relevant details of my setup. We are using recent Typescript (4.1.x) + Babel (7.12.x), so YMMV depending on your setup.

I switched my components to use new “automatic” React JSX transform. I find this the only reasonable and clean way forward. Note: this requires Typescript 4.1 or higher (see https://emotion.sh/docs/emotion-11#css-prop-types)

In babel configuration add runtime and importSource, and @emotion/babel-plugin. Excerpt:

{
  "presets": [
    [
      "@babel/preset-react", {
        "runtime": "automatic",
        "importSource": "@emotion/react",
      }
    ],
  ],
  "plugins": [
     "@emotion"
  ]
}

In Typescript configuration (tsconfig.json) add:

{
  "compilerOptions": {
    "jsx": "preserve",
    "jsxImportSource": "@emotion/react"
  }
}

We keep jsx: preserve, since we are not using Typescript to transform JSX, but Emotion babel plugin.

Storybook is still using Emotion 10 and depends on the “classic” React JSX transformation so you have to add custom babel configuration under .storybook folder that will use babel-preset-css-prop. Here is excerpt of .storybook/babel.config.js:

module.exports = {
  presets: [
    "@emotion/babel-preset-css-prop"
  ]
};

Also see discussion under #13145

In my project I was able to get Storybook to recognize css-prop (without using the jsx pragma) by adding this config to /.storybook/webpack.config.js.

Line 17 - Line 19:

  config.module.rules[0].use[0].options.presets = [
    require.resolve('@babel/preset-react'),
    require.resolve('@babel/preset-env'),
    // Emotion preset must run BEFORE reacts preset to properly convert css-prop.
    // Babel preset-ordering runs reversed (from last to first). Emotion has to be after React preset.
    require.resolve('@emotion/babel-preset-css-prop'),
  ];

Also, I am including the @emotion/babel-preset-css-prop for .ts and .tsx files

Line 41 - Line 43:

  config.module.rules.push({
    test: /\.(ts|tsx)$/,
    loader: require.resolve('babel-loader'),
    options: {
      presets: [
        ['react-app', { flow: false, typescript: true }],
        // Emotion preset must run BEFORE reacts preset to properly convert css-prop.
        // Babel preset-ordering runs reversed (from last to first). Emotion has to be after React preset.
        require.resolve('@emotion/babel-preset-css-prop'),
      ],
      // other plugins here...
    },
  });

I created a template to share my project config that uses Gatsby + TypeScript + Emotion + Storybook + React Intl + SVGR + Jest: https://github.com/duncanleung/gatsby-typescript-emotion-storybook

Here’s a link to my webpack.config.js that enables @emotion/babel-preset-css-prop:

For those people who want to use css prop in CRA based project with TS, modify your .storybook/main.js as below:

module.exports = {
  stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
  addons: [
    '@storybook/addon-links',
    '@storybook/addon-essentials',
    '@storybook/preset-create-react-app',
  ],
  framework: '@storybook/react',
  core: {
    builder: 'webpack5',
  },
  webpackFinal: async (config, { configType }) => {
    const oneOfRule = config.module.rules.find((rule) => rule.oneOf)
    const babelRule = oneOfRule.oneOf.find((rule) =>
      rule.loader?.includes('babel-loader')
    )

    babelRule.options.presets.push('@emotion/babel-preset-css-prop')

    return config
  },
}

There are 2 solutions:

  1. Using the older @emotion/babel-preset-css-prop.
  2. Modifying Storybook’s babel-loader to use a custom configuration.

Both of these versions are super hacky and aren’t the correct solution nor will they always be guaranteed to work through Storybook upgrades.

Instead, forget the Webpack config altogether. Storybook has a native way of modifying the Babel config: https://storybook.js.org/docs/react/configure/babel

The proper option is adding Babel overrides:

// The location of your Babel config.
const babelConfig = require('../babel.config.js');

const storybookConfig = {
  // ...
  babel: async (
    options
  ) => {
    options
      .overrides
      .push({
        ...babelConfig,
        test: '*', // This says "for all files, use this override".
      })

    return options;
  },
  // ...
}

module.exports = storybookConfig

Thanks @ndelangen & @nerdyman - I didn’t know you could set a babel export.

Here’s the .storybook/main.js I ended up with, using Storybook 6.0.12

module.exports = {
  stories: ["../stories/**/*.stories.tsx"],
  babel: (config) => {
    config.presets.push(require.resolve("@emotion/babel-preset-css-prop"));
    return config;
  },
};

With the solutions above I get this in the DOM:

css="You have tried to stringify object returned from `css` function. It isn't supposed to be used directly (e.g. as value of the `className` prop), but rather handed to emotion so it can handle it (e.g. as value of `css` prop).

Don’t know what I’m missing. I’m using this same Babel configuration for the project build and it works fine.

It worked with the following settings. Just for reference.

.storybook/main.js

const path = require('path');

module.exports = {
  stories: ['../src/**/*.stories.@(tsx|mdx)'],
  addons: [
    "@storybook/addon-essentials",
    // ...
  ],
  webpackFinal: async (config) => {
    config.module.rules.push({
      test: /\.(ts|tsx)$/,
      loader: require.resolve('babel-loader'),
      options: {
        presets: [require.resolve('@emotion/babel-preset-css-prop')],
      },
    });
    return config;
  },
};

dependencies

"babel-loader": "^8.2.2",
"@emotion/babel-preset-css-prop": "^11.0.0",
"@emotion/core": "^10.0.28",
"@emotion/react": "^11.1.4",

"@storybook/addon-essentials": "^6.1.16",
"@storybook/react": "^6.1.16",

"react": "^16.13.1",
"react-dom": "^16.13.1",

I cannot get this to work at all. The only way I can get this to work is if I use the jsx pragma, and then, it breaks storybook’s ability to generate or infer prop descriptions for the docs. I’m not certain, but I’m assuming this is because both storybook’s doc addon and emotion want to rewrite React.createElement?

If that helps anyone, here is what worked for me (using storybook 7):

// .storybook/main.js
module.exports = {
  // ...
  babel: (options) => ({
    ...options,
    presets: [...options.presets, '@emotion/babel-preset-css-prop'],
  }),
}

I managed to make it work using TypeScript + Emotion 11 and JSX Pragma.

  • TypeScript: v4.1.4
  • Emotion: 11.1.5
  • Storybook: v6.1.17

I’m not using Babel at all because I want to typecheck during build.

I had to change the @babel/preset-react runtime to be “classic” in order to use the JSX Pragma.

.storybook/main.js:

const path = require('path')

const toPath = _path => path.join(process.cwd(), _path)

module.exports = {
  stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
  addons: ['@storybook/addon-links', '@storybook/addon-essentials'],
  babel: config => {
    config.presets[2][1] = { runtime: 'classic' } // <-- this is the change
    return config
  },
  webpackFinal: async config => {
    return {
      ...config,
      resolve: {
        ...config.resolve,
        alias: {
          ...config.resolve.alias,
          '@emotion/core': toPath('node_modules/@emotion/react'),
          '@emotion/styled': toPath('node_modules/@emotion/styled'),
          'emotion-theming': toPath('node_modules/@emotion/react'),
        },
      },
    }
  },
}

@crushjz Thanks sir, now it’s works!

Adding this in .storybook/main.js worked for me. Thanks for mentioning.

babel: (config) => {
    const babelConfig = babel.loadPartialConfig({
      configFile: require.resolve('../babel.config'),
    });
    if (!babelConfig) {
      throw new Error('Failed to load Babel config');
    }
    return {
      ...config,
      ...babelConfig.options,
    };
  },

Here’s the .storybook/main.js that works for me, using Storybook 5.3.19

module.exports = {
  stories: ["../stories/**/*.stories.tsx"],
  addons: ["@storybook/preset-typescript"],
  webpackFinal: async (config) => {
    config.module.rules[0].use[0].options.presets.push(
      require.resolve("@emotion/babel-preset-css-prop")
    );
    return config;
  },
};

The OG post is unrelated to most of the discussion on this thread. I actually pulled his repo and fixed the error. He was exporting an anonymous component.

Error message: TypeError: /storybook-emotion-css/src/CssPropButton.js: Cannot read property 'name' of null

Original CssPropButton.js:

export default function() {
  return (
    <button
      css={css`
        color: hotpink;
      `}
    >
      I should be hot pink!
    </button>
  );
}

Fixed with no error:

export default function CssPropButton() {
  return (
    <button
      css={css`
        color: hotpink;
      `}
    >
      I should be hot pink!
    </button>
  );
}

Unfortunately, this didn’t solve the error I am having. His build set with the .bablerc in .storybook dir is working fine.

@illinar Have you tried the babelrc: false option in the loader

Thanks. This option seems to do the trick.

I’m seemingly bumping into this too. Also have tried all the suggested workarounds in the referenced issues.

My CSS props work just fine outside of Storybook but once I am running in storybook my css props aren’t working. Anything that’s basically just a string works but any time I am referencing props in the css / styled tag, the css property name isn’t present.

There are 2 babel configs you have to worry about. Depending on if you have JSX Pragma setup correctly and the latest babel-preset-react, it changes which you’ll want to use.

@emotion/babel-plugin is used when you’re using React v17 or higher, you’ll want this comment at the top of each file using Emotion:

/** @jsxRuntime classic @jsx jsx */
import {
  css,
  jsx,
} from '@emotion/react'

^^ You need this for Create-React-App.

If you have the correct react babel preset with JSX pragma set to automatic, then using the @emotion/babel-plugin works out-of-the-box without any jsx import or that comment.

If you have an older React, you’ll want to use @emotion/babel-preset-css-prop. You can use both in your project, but I’d recommend figuring out how to get @emotion/babel-plugin for all builds or simply add the jsxRuntime comment.

I know this is confusing, and I don’t fully understand it myself, but I’ve dealt with these issues on many projects.

Getting Emotion working Next.js was probably one of the hardest, but I figured out a hack-fix to make it work.

@se7entyse7en - if you link the repository or a minimal repro I will take a look for you

Perhaps we could create a storybook preset for emotion?

@JakeElder would you be interested in helping us with that?

There’s a babel property in main.js too that allows you to change the babel config, without having to find the exact webpack loader. That’s a bit safer, since it’s pretty fragile to find the correct loader to mutate this way.

In case this helps anybody, I just upgraded to storybook 7 and the babel-preset-css-prop solution didn’t work for me, but setting importSource: "@emotion/react" did!

// .storybook/main.tsx
import type { StorybookConfig } from "@storybook/react-webpack5";

const config: StorybookConfig = {
  stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
  addons: [
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    "@storybook/addon-onboarding",
    "@storybook/addon-interactions",
    "@storybook/addon-mdx-gfm"
  ],
  framework: {
    name: "@storybook/react-webpack5",
    options: {
      builder: {
        useSWC: true
      }
    }
  },
  swc: (config, options) => ({
    jsc: {
      transform: {
        react: {
          runtime: "automatic",
          importSource: "@emotion/react"
        }
      }
    }
  }),
  docs: {
    autodocs: "tag"
  }
};
export default config;

I managed to make it work using TypeScript + Emotion 11 and JSX Pragma.

  • TypeScript: v4.1.4
  • Emotion: 11.1.5
  • Storybook: v6.1.17

I’m not using Babel at all because I want to typecheck during build.

I had to change the @babel/preset-react runtime to be “classic” in order to use the JSX Pragma.

.storybook/main.js:

const path = require('path')

const toPath = _path => path.join(process.cwd(), _path)

module.exports = {
  stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
  addons: ['@storybook/addon-links', '@storybook/addon-essentials'],
  babel: config => {
    config.presets[2][1] = { runtime: 'classic' } // <-- this is the change
    return config
  },
  webpackFinal: async config => {
    return {
      ...config,
      resolve: {
        ...config.resolve,
        alias: {
          ...config.resolve.alias,
          '@emotion/core': toPath('node_modules/@emotion/react'),
          '@emotion/styled': toPath('node_modules/@emotion/styled'),
          'emotion-theming': toPath('node_modules/@emotion/react'),
        },
      },
    }
  },
}

Yes. This is probably unavoidable in some scenarios until Storybook changes. If you are transpiling with TS (not Babel) try to create custom tsconfig for storybook to disable noUnusedLocals.

@caspardue is that something we can add to Storybook’s MDX loader/compiler somehow?

Feel free to open a meeting request with me, and I can show you around the codebase, get your started: https://calendly.com/ndelangen/storybook

Here’s the docs for writing presets: https://storybook.js.org/docs/react/api/writing-presets

@ndelangen I couldn’t find an example using a custom babel prop in main.js. To get it working I imported Storybook’s default Babel config and added it into the babel config in main.js.

This is what I ended up with:

// main.js
const babelConfig = require('@storybook/core/dist/server/common/babel').default();

module.exports = {
  babel: {
    ...babelConfig,
    presets: [
      ...babelConfig.presets,
      require.resolve('@emotion/babel-preset-css-prop'),
    ],
};

Is there a better way to do it? It would be cool to add to the default config rather than replacing it.

🙌 require.resolve('@emotion/babel-preset-css-prop') + blowing away node_modules and rerunning yarn fixed it.

Based our now working config kinda on this: https://github.com/emotion-js/emotion/issues/1359#issuecomment-509798083 but with some additional crap for resolving sass stuff (we’re in the process of migrating and our storybook stuff stopped working without anyone noticing).

Sorry for any inconvenience and thanks for the awesome library.