Storybook: Can't access styled-components theme in stories

Created on 23 Oct 2019  路  23Comments  路  Source: storybookjs/storybook

Describe the bug
I'm using styled-components in combination with @storybook/react. I'm wrapping all my stories in the SC ThemeProvider like this:

addDecorator((storyFn) => (
    <ThemeProvider theme={themeBasic}>
        {storyFn()}
    </ThemeProvider>
));

This works fine and all of my Styled Components have access to the theme. However, I also want to access the theme in my stories:

import { ThemeContext } from 'styled-components';

export const Configurable = () => {
    const theme = useContext(ThemeContext);
    console.log(theme); // undefined

    return (
        // Some React stuff here
    );
};

Shouldn't this be working?

System:

Environment Info:

  System:
    OS: macOS 10.15
    CPU: (16) x64 Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz
  Binaries:
    Node: 10.16.0 - /usr/local/bin/node
    Yarn: 1.17.3 - /usr/local/bin/yarn
    npm: 6.12.0 - /usr/local/bin/npm
  Browsers:
    Chrome: 78.0.3904.70
    Firefox: 69.0.3
    Safari: 13.0.2
  npmPackages:
    @storybook/addon-actions: ^5.2.5 => 5.2.5
    @storybook/addon-backgrounds: ^5.2.5 => 5.2.5
    @storybook/addon-info: ^5.2.5 => 5.2.5
    @storybook/addon-knobs: ^5.2.5 => 5.2.5
    @storybook/react: ^5.2.5 => 5.2.5
react compatibility with other tools help wanted question / support

Most helpful comment

I guess this issue is the same as https://github.com/storybookjs/storybook/issues/8426.
If so, this could solve your problem. (NOTE: I didn't test the code. Just saw the workaround.)

// Using StoryFn as component instead of invoking it
addDecorator(StoryFn => (
    <ThemeProvider theme={themeBasic}>
        <StoryFn/>
    </ThemeProvider>
));

All 23 comments

So any update on this issue? Does someone need additional information or something?

nope. there are over 400 issues in storybook and not many people to answer. hopefully somebody with styled components experience can answer. i know this repo uses it, so maybe check what they're doing here: https://github.com/storybookjs/frontpage

@daviddelusenet We inject it with a wrapper component that wraps all our stories

What do you mean @EdenTurgeman ? Isn't that the point of the ThemeProvider?

Hi @daviddelusenet 馃憢 -- please see this example:

https://github.com/kylesuss/styled-components-storybook-theme-example/blob/89f2193bb87f208a351b47b107c0eba1beb96b49/src/index.stories.js

You can do what you need to do, but you need another component to do it -- you can't just put the useContext stuff in the component story function. Why? Not sure exactly to be honest. Maybe somebody with a bit more knowledge on that can chime in here.

@kylesuss thanks, your suggestion eventually led me to a solution. I had the following story:

export const Configurable = () => {
    const colors = getColorsFromTheme(themeBasic, colorKeys);

    return (
        <div
            style={{
                color: select('Color', colors, colors['colorHeaderText-primary']),
                fontSize: select('Size', {
                    XSMALL: '12px',
                    SMALL: '16px',
                    MEDIUM: '20px',
                    LARGE: '24px',
                    XLARGE: '28px',
                    XXLARGE: '32px',
                }, '24px'),
            }}
        >
            <Icon type={select('Type', Icon.types, Icon.types.CALENDAR)} />
        </div>
    );
};

I wanted to dynamically get the colors from the selected theme, it's now hardcoded as you can see. By rewriting my story to this I could get it working:

const ConfigurableStory = () => {
    const theme = useContext(ThemeContext);
    const colors = getColorsFromTheme(theme, colorKeys);

    console.log(theme); /* <-- this works */

    return (
        <div
            style={{
                color: select('Color', colors, colors['colorHeaderText-primary']),
                fontSize: select('Size', {
                    XSMALL: '12px',
                    SMALL: '16px',
                    MEDIUM: '20px',
                    LARGE: '24px',
                    XLARGE: '28px',
                    XXLARGE: '32px',
                }, '24px'),
            }}
        >
            <Icon type={select('Type', Icon.types, Icon.types.CALENDAR)} />
        </div>
    );
};

export const Configurable = () => (
    <ConfigurableStory />
);

Nevermind, this doesn't work.

The theme is now available but all of the knobs are broken.

Hi everyone! Seems like there hasn't been much going on in this issue lately. If there are still questions, comments, or bugs, please feel free to continue the discussion. Unfortunately, we don't have time to get to every issue. We are always open to contributions so please send us a pull request if you would like to help. Inactive issues will be closed after 30 days. Thanks!

Same thing happens when using the material-ui theme provider, I have found a workaround and hope can help for styled-components too.

I don't really understand why, but it seems like "wrapping" the story with another component works:

This does not work, it throws an error "theme.spacing is not defined":

const useStyles = makeStyles(theme => {
  return {
    fooClass: {
      padding: theme.spacing(4)
    }
  };
});

export const fooStory = () => {
  const classes = useStyles();
  return (
    <Paper className={classes.fooClass}>
      Foo content
    </Paper>
  );
};

But, converting the story into a component, and exporting another component that simply renders the first one, does work:

const useStyles = makeStyles(theme => {
  return {
    fooClass: {
      padding: theme.spacing(4)
    }
  };
});

const FooStory = () => {
  const classes = useStyles();
  return (
    <Paper className={classes.fooClass}>
      Foo content
    </Paper>
  );
};

export const fooStory = () => (
  <FooStory />
);

This is not the ideal scenario, and I'd like to know why, but at least it works, and I think it is an admissible workaround until this issue is fixed, and could provide a clue about how to solve it definitively.

I have my Theme Provider defined as a global decorator:

import React from "react";

import { addDecorator } from "@storybook/react";

import { makeDecorator } from "@storybook/addons";

import CssBaseline from "@material-ui/core/CssBaseline";
import { ThemeProvider } from "@material-ui/core/styles";
import { muiTheme } from "src/theme";

const withMuiTheme = storyFn => {
  return (
    <ThemeProvider theme={muiTheme}>
      <CssBaseline />
      {storyFn()}
    </ThemeProvider>
  );
};

const muiThemed = makeDecorator({
  name: "mui-theme",
  wrapper: withMuiTheme
});

addDecorator(muiThemed);

I'm having some similiar unexplainable issue accessing my redux provider. I have a decorator that wraps every story with the from redux, but I cannot use useDispatch since it cannot find the Provider.

I guess this issue is the same as https://github.com/storybookjs/storybook/issues/8426.
If so, this could solve your problem. (NOTE: I didn't test the code. Just saw the workaround.)

// Using StoryFn as component instead of invoking it
addDecorator(StoryFn => (
    <ThemeProvider theme={themeBasic}>
        <StoryFn/>
    </ThemeProvider>
));

Hi everyone! Seems like there hasn't been much going on in this issue lately. If there are still questions, comments, or bugs, please feel free to continue the discussion. Unfortunately, we don't have time to get to every issue. We are always open to contributions so please send us a pull request if you would like to help. Inactive issues will be closed after 30 days. Thanks!

Hello @daviddelusenet , i have a working approach on my app using vue-styled-components and knobs as well.

You have to create a decorator and pass the knobs properties.
This worked for me:

In theme-decorator.js:

import { ThemeProvider } from 'vue-styled-components';
import { customTheme } from "custom-theme";

import { withKnobs } from '@storybook/addon-knobs';

export default (story, context) => {
  // Decorated with story-function
  const WrapButton = story();
  const storyWithKnobs = withKnobs(story, context);

  return {      
    ...storyWithKnobs,

    components: { ThemeProvider, WrapButton },

    props: {
      ...storyWithKnobs.props,
    },

    template: `<theme-provider :theme="themeKnob"><wrap-button/></theme-provider>`,
    data() {
      return { themeKnob: customTheme }
    }
  };
};

In config.js:

import { withTheme } from './theme-decorator';

addDecorator(withTheme);

I based my solution on this closed PR https://github.com/storybookjs/storybook/issues/2962 but i wasn't able to add the select to change the props.

I hope this helps!

Feel free to give a try to an addon I've built.
It supports also changing the background-color of the storybook (to simulate a real theming experience)

https://github.com/semoal/themeprovider-storybook

According to the latest docs (v5.3), you need to add a global decorator in .storybook/preview.js:

import React from "react";
import { addDecorator } from '@storybook/react';
import { ThemeProvider } from "styled-components";
import theme from "../src/theme";

addDecorator(storyFn => <ThemeProvider theme={theme}>{storyFn()}</ThemeProvider>)

https://storybook.js.org/docs/addons/introduction/#storybook-decorators

Hi everyone! Seems like there hasn't been much going on in this issue lately. If there are still questions, comments, or bugs, please feel free to continue the discussion. Unfortunately, we don't have time to get to every issue. We are always open to contributions so please send us a pull request if you would like to help. Inactive issues will be closed after 30 days. Thanks!

How do you add decorators in line with the new v6.0 migration to the main.js file?

@PatFawbertMills you add it to .storybook/preview.js.

In 5.3:

import { addDecorator } from '@storybook/react';
addDecorator(myDecorator)

In 6.0:

export const decorators = [myDecorator];

Hi everyone! Seems like there hasn't been much going on in this issue lately. If there are still questions, comments, or bugs, please feel free to continue the discussion. Unfortunately, we don't have time to get to every issue. We are always open to contributions so please send us a pull request if you would like to help. Inactive issues will be closed after 30 days. Thanks!

Hey there, it's me again! I am going close this issue to help our maintainers focus on the current development roadmap instead. If the issue mentioned is still a concern, please open a new ticket and mention this old one. Cheers and thanks for using Storybook!

According to the latest docs (v5.3), you need to add a global decorator in .storybook/preview.js:

import React from "react";
import { addDecorator } from '@storybook/react';
import { ThemeProvider } from "styled-components";
import theme from "../src/theme";

addDecorator(storyFn => <ThemeProvider theme={theme}>{storyFn()}</ThemeProvider>)

https://storybook.js.org/docs/addons/introduction/#storybook-decorators

This was a life saving

I guess this issue is the same as #8426.
If so, this could solve your problem. (NOTE: I didn't test the code. Just saw the workaround.)

// Using StoryFn as component instead of invoking it
addDecorator(StoryFn => (
    <ThemeProvider theme={themeBasic}>
        <StoryFn/>
    </ThemeProvider>
));

This worked for me! Thank you so much, it was driving me crazy!

In version 6 I managed to do this by adding the theme to the args property that is passed to all components :

import theme from './../theme.tsx';
const withThemeProvider = (Story, context) => {
  // Pass the theme to all story args.
  context.args = { ...context.args, ...{ theme } };
  return (
    <ThemeProvider theme={theme}>
      <>
        <div>
          <GlobalStyle />
          <AppGlobalStyle />
          <Story {...context} />
        </div>
      </>
    </ThemeProvider>
  );
};
export const decorators = [withThemeProvider];

This way in a story I can grab the theme like this :

export const NavbarBox = ({ theme }) => {
  return (
    <Navbar>
      <img src={theme.logo} alt="Logo" title="Logo" />
      <div>
        <MenuLink>Home</MenuLink>
        <MenuLink>My account</MenuLink>
        <MenuLink>Settings</MenuLink>
        <MenuLink>Logout</MenuLink>
      </div>
    </Navbar>
  );
};
NavbarBox.story = {
  title: 'Header'
};

Main use case for me is that I am using the same storybook and shipping to many clients and some of the branding I want to have in the stories (e.g. logo in this story should adapt to whatever logo set in the theme object

Was this page helpful?
0 / 5 - 0 ratings