storybook-dark-mode
storybook-dark-mode copied to clipboard
Storybook 6 docs theming
Can this be made to also configure the storybook docs theme which is supplied separately in v6.0?
It could. The last time I looked that wasn't configurable. Would happily accept a PR for this :D
I had a go at this but I can't see how to make it work. I can store the relevant docs theme in the globals but I can't see how to provide the theme to the docs. The DocContainer gets the values from the parameters but I don't see any api to set the parameters. I tried using a decorator and setting the parameter value but these seem to only be applied to the stories within the docs - which already worked by the existing mechanism.
I would love to see this be possible somehow as well. I'm running into this issue as well. I may have a look.
In preview.js
you can supply a custom container and override the styles using the sbdocs
css class(es). This could be a route to making this work:
// preview.js
export const parameters = {
docs: {
container: DocsContainerTheme,
},
}
Then through some styling solution manipulate the theme. As a proof of concept, the following worked to switch the background and color, but would be better to use a theme parameter rather than reconstruct the whole css here:
// DocContainerTheme
import React from 'react'
import { DocsContainer } from '@storybook/addon-docs/blocks'
import { useDarkMode } from 'storybook-dark-mode'
import { makeStyles } from '../../src'
const useStyles = makeStyles({
override: {
'& .sbdocs': {
background: ({ dark }) => (dark ? 'black' : 'white'),
color: ({ dark }) => (dark ? 'white' : 'black'),
},
},
})
export const DocsContainerTheme = ({ children, context }) => {
const dark = useDarkMode()
const classes = useStyles({ dark })
return (
<div className={classes.override}>
<DocsContainer context={context}>{children}</DocsContainer>
</div>
)
}
I think official support for this is still waiting on https://github.com/storybookjs/storybook/issues/10523
Without a way to dynamically set params for the docs theme, this plugin can't do much. The approach above seems nice though in the interim
Thanks for the sharing @stuarthendren!
I found a better workaround that will fully respect the used theming without hacking the css by overriding the context using spread operators.
First, create a doc container as the above example:
// .storybook/components/DocContainer.tsx
import React from 'react'
import { DocsContainer as BaseContainer } from '@storybook/addon-docs/blocks'
import { useDarkMode } from 'storybook-dark-mode'
import { themes } from '@storybook/theming';
export const DocsContainer = ({ children, context }) => {
const dark = useDarkMode()
return (
<BaseContainer
context={{
...context,
parameters: {
...context.parameters,
docs: {
// This is where the magic happens.
theme: dark ? themes.dark : themes.light
},
},
}}
>
{children}
</BaseContainer>
);
}
Then on your preview.js
config file:
// .storybook/preview.js
import { DocsContainer } from './components/DocContainer';
export const parameters = {
docs: {
container: DocsContainer,
},
// Rest of your configuration
};
And voilà! :tada:
UPDATE: Not working since Storybook 6.4. See https://github.com/hipstersmoothie/storybook-dark-mode/issues/127#issuecomment-983056445 or https://github.com/hipstersmoothie/storybook-dark-mode/issues/127#issuecomment-1070524402 for 6.4 compatible solution.
return ( <BaseContainer context={{ ...context, parameters: { ...context.parameters, docs: { // This is where the magic happens. theme: dark ? themes.dark : themes.light }, }, }} > {children} </BaseContainer> );
This is life affirming. Surprised it's not in the docs as I don't consider it to be working properly if everything is dark and Docs are still blindingly white 😅
I'm not using React (but Vue3) and didn't see anything on creating containers in Vue (I'm not even sure it's possible).
So I went with a very simple (but not reactive) solution, adding this in my preview.ts
:
export const parameters = {
/* ... */
docs: {
get theme() {
let isDarkMode = parent.document.body.classList.contains("dark");
return isDarkMode ? themes.dark : themes.light;
}
},
/* ... */
};
It's not reactive, so you must reload the page. The check is kinda dirty/hacky. But it works for my case, so I thought I might share.
the above solution is working for me for the theme but it stopped showing props table. any ideas?
Error: Args unsupported. See Args documentation for your framework. Read the docs
Yeah, same for me:

@soullivaneuh @gsingh1370 @DominicTobias have you managed to make the props work?
@gazpachu use this. Added docs from context
import React from 'react' import { DocsContainer as BaseContainer } from '@storybook/addon-docs/blocks' import { useDarkMode } from 'storybook-dark-mode' import { themes } from '@storybook/theming';
export const DocsContainer = ({ children, context }) => { const dark = useDarkMode()
return ( <BaseContainer context={{ ...context, parameters: { ...context.parameters, docs: { ...context.parameters.docs, // This is where the magic happens. theme: dark ? themes.dark : themes.light }, }, }} > {children} </BaseContainer> ); }
Thanks, at the end, with lodash/set, the props work fine:
import set from 'lodash/set';
import React, { ReactNode } from 'react';
import { useDarkMode } from 'storybook-dark-mode';
export const DocsContainer = ({
children,
context
}: {
children: ReactNode;
context: any;
}) => {
const dark = useDarkMode();
set(context, 'parameters.docs.theme', dark ? themes.dark : themes.light);
return <BaseContainer context={context}>{children}</BaseContainer>;
};
@gsingh1370 @gazpachu Not tested because I don't use props for my case, but as an alternative as the lodash example, you may use an additional spread operator:
diff --git a/.storybook/components/DocContainer.tsx b/.storybook/components/DocContainer.tsx
index a0fd4b7..d1d775d 100644
--- a/.storybook/components/DocContainer.tsx
+++ b/.storybook/components/DocContainer.tsx
@@ -18,6 +18,7 @@ export const DocsContainer: FC<DocsContainerProps> = ({ children, context }) =>
parameters: {
...parameters,
docs: {
+ ...parameters.docs,
theme: dark ? themes.dark : themes.light,
},
},
This is surely why you don't have any prop, the docs
part is completely overridden on my previous sample.
Hmm I just upgraded from 6.4.0-beta.19
and the magic is no longer working, switching back to a "stuck light mode".
Does anyone got the same issue? Do you know if a workaround is possible?
Storybook has released 6.4
.
I haven't found a new way to set the theme dynamically. My very, very dirty hack with page refresh looks like this:
import React, { useEffect } from 'react';
import { addParameters } from '@storybook/react';
import { DocsContainer as BaseContainer } from '@storybook/addon-docs';
import { themes } from '@storybook/theming';
import { useDarkMode } from 'storybook-dark-mode';
const isInitialDark = JSON.parse(localStorage.getItem('sb-addon-themes-3') ?? '{}')?.current === 'dark';
addParameters({
docs: {
theme: isInitialDark ? themes.dark : themes.light,
},
});
export const DocsContainer: typeof BaseContainer = (props) => {
const isDark = useDarkMode();
useEffect(
() => {
if (isInitialDark !== isDark) {
window.location.reload();
}
},
[isDark],
)
return <BaseContainer {...props} />;
};
I think this could potentially lead to endless page reloads. Use at your own risk.
@dartess , @soullivaneuh after digging through the updates to DocsContainer in 6.4 , was able to get it working this way:
<BaseContainer
context={{
...context,
storyById: (id) => {
const storyContext = context.storyById(id);
return {
...storyContext,
parameters: {
...storyContext?.parameters,
docs: {
theme: dark ? themes.dark : themes.light,
},
},
}
},
}}
>
Not sure if this is the intended way, but it's working for me 😄
Anyone have a similar issue with the controls addon? I've got everything dark but that portion of the UI.
I was able to get both the args table to work correctly with subcomponents and the controls section to show up in dark mode with this:
<BaseContainer
context={{
...context,
storyById: (id) => {
const storyContext = context.storyById(id)
return {
...storyContext,
parameters: {
...storyContext?.parameters,
docs: {
...storyContext?.parameters?.docs,
theme: dark ? darkTheme : themes.light,
},
},
}
},
}}
>
Мне удалось заставить таблицу аргументов правильно работать с подкомпонентами и разделом управления, чтобы он отображался в темном режиме с помощью этого:
<BaseContainer context={{ ...context, storyById: (id) => { const storyContext = context.storyById(id) return { ...storyContext, parameters: { ...storyContext?.parameters, docs: { ...storyContext?.parameters?.docs, theme: dark ? darkTheme : themes.light, }, }, } }, }} >
tell me where you used it
For anyone reading this in 2022. This is full fix description:
- Create
.storybook/DocsContainer.js
import React from "react";
import { DocsContainer as BaseContainer } from "@storybook/addon-docs/blocks";
import { useDarkMode } from "storybook-dark-mode";
import { themes } from "@storybook/theming";
export const DocsContainer = ({ children, context }) => {
const dark = useDarkMode();
return (
<BaseContainer
context={{
...context,
storyById: (id) => {
const storyContext = context.storyById(id);
return {
...storyContext,
parameters: {
...storyContext?.parameters,
docs: {
...storyContext?.parameters?.docs,
theme: dark ? themes.dark : themes.light,
},
},
};
},
}}
>
{children}
</BaseContainer>
);
};
- Edit & tune for your needs:
.storybook/preview.js
import React from "react";
import { useDarkMode } from "storybook-dark-mode";
import { themes } from "@storybook/theming";
import { darkTheme } from "@retrolove-games/ui-themes";
import { DocsContainer } from './DocsContainer';
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
viewMode: "docs",
docs: {
// theme: themes.dark,
container: DocsContainer,
},
};
export const decorators = [
(Story) => (
<div className={useDarkMode() ? darkTheme.className : "light"}>
<Story />
</div>
),
];
Also published in my gist.
Same problem here, docs are not updating when changing theme. Would love a similar option for styling docs, just like { stylePreview: true }
For now I'm sticking with the hack by @hugoattal because I'm also using Vue for my UI library. I tried the custom DocsContainer solution, but I can't install React as a devDependency in Storybook 6.4.20, because npm is complaining about dependency conflicts... Let's hope this Storybook issue gets solved.
I'm not using React (but Vue3) and didn't see anything on creating containers in Vue (I'm not even sure it's possible).
So I went with a very simple (but not reactive) solution, adding this in my
preview.ts
:export const parameters = { /* ... */ docs: { get theme() { let isDarkMode = parent.document.body.classList.contains("dark"); return isDarkMode ? themes.dark : themes.light; } }, /* ... */ };
Here is how I used @fedek6 DocsContainer with MUI5 theme:
import { createTheme, CssBaseline, ThemeProvider } from "@mui/material";
import { getDesignTokens } from "theme/theme";
import { useDarkMode } from "storybook-dark-mode";
import { themes } from "@storybook/theming";
import { DocsContainer } from "./DocsContainer";
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
darkMode: {
dark: { ...themes.dark },
light: { ...themes.normal },
},
docs: {
container: DocsContainer,
},
};
function ThemeWrapper(props) {
const mode = useDarkMode() ? "dark" : "light";
const theme = createTheme(getDesignTokens(mode));
return (
<ThemeProvider theme={theme}>
<CssBaseline /> {props.children}
</ThemeProvider>
);
}
export const decorators = [
(Story) => (
<ThemeWrapper>
<Story />
</ThemeWrapper>
),
];
Hey!
It's time to smoothly change the name to "Storybook 7 docs theming"
In storybook@7
(alpha now) BaseContainer
has prop theme
, and passing theme is now much easier.
But storybook-dark-mode
does not set the class for the body in the new docs page. You can do it yourself. But I don't know how to get the global parameters of the storybook correctly (the way I found does not converge on types and I use ts-ignore).
The result of my research is this:
diff --git a/.storybook/components/DocContainer.tsx b/.storybook/components/DocContainer.tsx
index 2e0d9fb8b..e1be68bf2 100644
--- a/.storybook/components/DocContainer.tsx
+++ b/.storybook/components/DocContainer.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useEffect } from 'react';
import { DocsContainer as BaseContainer } from '@storybook/addon-docs';
import { useDarkMode } from 'storybook-dark-mode';
@@ -15,24 +15,16 @@ so a workaround is used.
export const DocsContainer: typeof BaseContainer = ({ children, context }) => {
const dark = useDarkMode();
+ useEffect(() => {
+ // @ts-ignore
+ const { darkClass, lightClass } = context.store.projectAnnotations.parameters.darkMode;
+ const [addClass, removeClass] = dark ? [darkClass, lightClass] : [lightClass, darkClass]
+ document.body.classList.remove(removeClass);
+ document.body.classList.add(addClass);
+ }, [dark])
+
return (
- <BaseContainer
- context={{
- ...context,
- storyById: id => {
- const storyContext = context.storyById(id);
- return {
- ...storyContext,
- parameters: {
- ...storyContext?.parameters,
- docs: {
- theme: dark ? themes.dark : themes.light,
- },
- },
- };
- },
- }}
- >
+ <BaseContainer context={context} theme={dark ? themes.dark : themes.light}>
{children}
</BaseContainer>
);
Tested on version 7.0.0-alpha.19
(everything may change by release)
I'm not really sure what the solution is here but if there are any that could be landed in the package feel free to make a PR!
After upgrading to SB7+Next+Webpack my addons channel is no longer working (so no useDarkMode()
possible) and didn't find for now the origin (ref: https://github.com/hipstersmoothie/storybook-dark-mode/issues/205).
So for me, the quick solution is to use as wrapper:
import { DocsContainer as BaseContainer } from '@storybook/addon-docs';
import { themes } from '@storybook/theming';
import React, { useEffect, useState } from 'react';
function isDarkInStorage(): boolean {
const themeString = localStorage.getItem('sb-addon-themes-3');
if (themeString) {
const theme = JSON.parse(themeString);
return theme['current'] !== 'light';
}
return false;
}
export const ThemedDocsContainer = ({ children, context }) => {
const [isDark, setIsDark] = useState(isDarkInStorage());
const handler = () => {
setIsDark(isDarkInStorage());
};
useEffect(() => {
window.addEventListener('storage', handler);
return function cleanup() {
window.removeEventListener('storage', handler);
};
});
return (
<BaseContainer context={context} theme={isDark ? themes.dark : themes.light}>
{children}
</BaseContainer>
);
};
(big sorry for this dirty workaround)
I was able to get both the args table to work correctly with subcomponents and the controls section to show up in dark mode with this:
<BaseContainer context={{ ...context, storyById: (id) => { const storyContext = context.storyById(id) return { ...storyContext, parameters: { ...storyContext?.parameters, docs: { ...storyContext?.parameters?.docs, theme: dark ? darkTheme : themes.light, }, }, } }, }} >
thank you
- Create
.storybook/DocsContainer.js
import React from "react"; import { DocsContainer as BaseContainer } from "@storybook/addon-docs/blocks"; import { useDarkMode } from "storybook-dark-mode"; import { themes } from "@storybook/theming"; export const DocsContainer = ({ children, context }) => { const dark = useDarkMode(); return ( <BaseContainer context={{ ...context, storyById: (id) => { const storyContext = context.storyById(id); return { ...storyContext, parameters: { ...storyContext?.parameters, docs: { ...storyContext?.parameters?.docs, theme: dark ? themes.dark : themes.light, }, }, }; }, }} > {children} </BaseContainer> ); };
Thanks @fedek6 - This solution also worked for me, but now my source code block is only showing raw code — equivalent to setting the docs source parameter as type: 'code'
. Do you know if it's possible to edit the custom DocsContainer to allow for type: 'dynamic'
?
@dartess I did nearly the similar but without having to hack with the document.body
object:
diff --git a/.storybook/components/DocContainer.tsx b/.storybook/components/DocContainer.tsx
index 84bf99b..abd2e6d 100644
--- a/.storybook/components/DocContainer.tsx
+++ b/.storybook/components/DocContainer.tsx
@@ -13,27 +13,13 @@ import {
} from '@storybook/theming';
// @see https://github.com/hipstersmoothie/storybook-dark-mode/issues/127#issuecomment-1070524402
-export const DocsContainer: FC<DocsContainerProps> = ({ children, context }) => {
+export const DocsContainer: FC<DocsContainerProps> = ({ children, ...rest }) => {
const dark = useDarkMode();
return (
<BaseContainer
- context={{
- ...context,
- storyById: (id) => {
- const storyContext = context.storyById(id);
- return {
- ...storyContext,
- parameters: {
- ...storyContext?.parameters,
- docs: {
- ...storyContext?.parameters?.docs,
- theme: dark ? themes.dark : themes.light,
- },
- },
- };
- },
- }}
+ {...rest}
+ theme={dark ? themes.dark : themes.light}
>
{children}
</BaseContainer>
The only thing I have to do is to fill the theme
prop with the right one.
Why do you need to play with the DOM body here? :thinking:
@soullivaneuh it was necessary for storybook-dark-mode@2
Now I have the same version as you.
Hey!
It's time to smoothly change the name to "Storybook 7 docs theming"
In
storybook@7
(alpha now)BaseContainer
has proptheme
, and passing theme is now much easier.But
storybook-dark-mode
does not set the class for the body in the new docs page. You can do it yourself. But I don't know how to get the global parameters of the storybook correctly (the way I found does not converge on types and I use ts-ignore).The result of my research is this:
diff --git a/.storybook/components/DocContainer.tsx b/.storybook/components/DocContainer.tsx index 2e0d9fb8b..e1be68bf2 100644 --- a/.storybook/components/DocContainer.tsx +++ b/.storybook/components/DocContainer.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { DocsContainer as BaseContainer } from '@storybook/addon-docs'; import { useDarkMode } from 'storybook-dark-mode'; @@ -15,24 +15,16 @@ so a workaround is used. export const DocsContainer: typeof BaseContainer = ({ children, context }) => { const dark = useDarkMode(); + useEffect(() => { + // @ts-ignore + const { darkClass, lightClass } = context.store.projectAnnotations.parameters.darkMode; + const [addClass, removeClass] = dark ? [darkClass, lightClass] : [lightClass, darkClass] + document.body.classList.remove(removeClass); + document.body.classList.add(addClass); + }, [dark]) + return ( - <BaseContainer - context={{ - ...context, - storyById: id => { - const storyContext = context.storyById(id); - return { - ...storyContext, - parameters: { - ...storyContext?.parameters, - docs: { - theme: dark ? themes.dark : themes.light, - }, - }, - }; - }, - }} - > + <BaseContainer context={context} theme={dark ? themes.dark : themes.light}> {children} </BaseContainer> );
Tested on version
7.0.0-alpha.19
(everything may change by release)
This is useful for me. Thanks