How do I make the canvas background color change, when a theme is selected? #25183
Replies: 14 comments 15 replies
-
|
Thanks for taking the time to reach out to us with your question. We appreciate it 🙏 ! So that you're aware, the styling addon you referred to is officially [deprecated}(https://storybook.js.org/addons/@storybook/addon-styling), and we're encouraging users to use one of the alternatives provided in the disclaimer (ie., Looking forward to hearing from you. Hope you have a great day. Stay safe |
Beta Was this translation helpful? Give feedback.
-
|
Using Tailwind with add on-theme, my preference is to use |
Beta Was this translation helpful? Give feedback.
-
|
There is actually a quite easy way to do this, with themes addon alone: export const decorators = [
withThemeByClassName({
themes: {
light: "light",
dark: "dark bg-neutral-900",
},
defaultTheme: "light",
}),
]Just add Tailwind background class as part of your themes css classes list. Be mindful that in Might be worth also disabling backgrounds in your preview config: const preview: Preview = {
parameters: {
backgrounds: { disable: true },
}
}as they will be conflicting with your theme classes. |
Beta Was this translation helpful? Give feedback.
-
|
For those in tailwindcss land I got it working with this from inspiration from everyone else! parameters: {
backgrounds: {
options: {
"light/dark": {
name: "light/dark",
value: "var(--light-dark)",
},
white: { name: "white", value: "#ffffff" },
"gray-50": { name: "gray-50", value: "#f7f7f7" },
"gray-950": { name: "gray-950", value: "#292929" },
},
},
},
initialGlobals: {
backgrounds: {
value: "light/dark",
},
},
decorators: [
withThemeByClassName({
themes: {
light: "light [--light-dark:#ffffff]", // gray-50
dark: "dark [--light-dark:#292929]", // gray-950
},
defaultTheme: "light",
}),As someone else mentioned- make sure your tailwind config is looking at this file! |
Beta Was this translation helpful? Give feedback.
-
|
Doesn't work. Interesting that so popular tool can't do so simple and logical thing - just apply dark background in dark mode... |
Beta Was this translation helpful? Give feedback.
-
|
I was looking for the same solution but couldn’t find anything anywhere. Eventually, I created this, but it’s a bit buggy. I’m not sure if anyone has a better solution. |
Beta Was this translation helpful? Give feedback.
-
|
The only way I could reliably find to make it work is by making a decorator like this: export const ThemeSwitchDecorator: Decorator = (Story, context) => {
const { scheme, viewMode } = context.globals;
const isIframe = viewMode === "story";
const darkmodeBackgroundColor = "black";
function WithColor(props: {
children: React.ReactNode;
themeClassName: string;
}) {
return (
<div
style={{
padding: context.viewMode == "docs" ? "30px" : "15px",
backgroundColor:
props.themeClassName === "dark" ? darkmodeBackgroundColor : "white",
width: "100%",
}}
className={`${props.themeClassName}`}
>
{props.children}
</div>
);
}
if (scheme === "light") {
return (
<WithColor themeClassName="light">
<Story />
</WithColor>
);
}
if (scheme === "dark") {
return (
<WithColor themeClassName="dark">
<div
style={{
position: "fixed",
width: "100%",
height: context.viewMode == "docs" ? "100%" : "100vh",
backgroundColor: darkmodeBackgroundColor,
left: 0,
top: 0,
}}
></div>
<Story />
</WithColor>
);
}
return (
<>
<div
style={{
display: "flex",
width: "100%",
height: context.viewMode == "docs" ? "100%" : "100vh",
}}
>
<WithColor themeClassName="light">
<Story />
</WithColor>
<WithColor themeClassName="dark">
<Story />
</WithColor>
</div>
</>
);
};And also setting the fullscreen parameter in preview: const preview: Preview = {
parameters: {
layout: "fullscreen", // <----- here
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
}; |
Beta Was this translation helpful? Give feedback.
-
|
I'm using Tailwind and CSS variables for dark mode (based on shadcn) and I came up with this solution Screen.Recording.2025-01-21.165312.mp4My @tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
...
}
.dark {
--background: 222.2 84% 4.9%;
...
}
}My import type { Config } from 'tailwindcss';
import tailwindCssAnimate from 'tailwindcss-animate';
export default {
darkMode: [`class`],
theme: {
extend: {
colors: {
background: `hsl(var(--background))`,I update the Storybook const preview: Preview = {
...
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
docs: {
canvas: {
// override the canvas background based on the theme
className: `!bg-background`,
},
},
},
};I also have to add the class to the export default {
...
safelist: [
// allow Storybook to override the canvas background based on the theme
`!bg-background`,
] |
Beta Was this translation helpful? Give feedback.
-
|
I need to solve the "order of operations" issue.
import {withThemeByClassName} from "@storybook/addon-themes";
// brings in tailwind css
import '../css/index.css'
const preview = {
parameters: {
backgrounds: {disable: true},
},
decorators: [
withThemeByClassName({
themes: {
light: '',
dark: 'dark'
},
defaultTheme: 'light'
}),
],
}
export default preview
<!-- Adding files to the preview-head.html will include the necessary files when viewing components through Storybook -->
<!-- Pull in static files served from your Static directory or the internet -->
<style>
.dark body {
/* var(--color-gray-950) */
background: #0F0F0F;
}
</style>Now I just need to figure out the order of operations so that I can use my actual "var" from Tailwind but I'm happy with this for now. Thank you everyone for sharing your ideas. |
Beta Was this translation helpful? Give feedback.
-
|
I'm new to storybook, and I just spent 3 hours trying to get dark mode to work properly. It works on stories, but not autodocs. Couldn't make it happen so eventually just gave up. Kinda disappointing that such a popular tool like storybook doesn't have straightforward support for dark mode, one of the most basic use cases of every website. |
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
|
@johan44co Awesome, this works well. Here's the full config:
import { defineMain } from "... your framework"; // INFO: this is different for you config
export default defineMain({
// framework: .. // INFO: this is different for you config
addons: [
"@storybook/addon-onboarding",
"@storybook/addon-docs",
"@storybook/addon-a11y",
"@storybook/addon-links",
"@storybook/addon-vitest",
"@storybook/addon-themes", // INFO: this part is relevant! provides a theme switcher for components see https://storybook.js.org/addons/@storybook/addon-themes
"@vueless/storybook-dark-mode", // INFO: helpful if you also want to switch storybook's UI theme. provides a theme switcher for storybook's UI, not the component's UI, see https://storybook.js.org/addons/@vueless/storybook-dark-mode
],
stories: [
"../stories/**/*.mdx",
"../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)",
],
});`preview.js: import addonA11y from "@storybook/addon-a11y";
import addonDocs from "@storybook/addon-docs";
import { definePreview, Renderer } from "... your framework";
import { withThemeByClassName } from "@storybook/addon-themes";
export default definePreview({
addons: [addonDocs(), addonA11y()],
parameters: {
// automatically create action args for all props that start with 'on'
actions: {
argTypesRegex: "^on.*",
},
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
a11y: {
// 'todo' - show a11y violations in the test UI only
// 'error' - fail CI on a11y violations
// 'off' - skip a11y checks entirely
test: "todo",
},
},
decorators: [ // INFO: this part is relevant to display the theme switcher button
withThemeByClassName<Renderer>({
themes: {
light: "",
dark: "dark",
},
defaultTheme: "light",
}),
],
// All components will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
// tags: ['autodocs'],
});
<html>
<head>
<style>
body {
background: var(--background);
}
:root {
--background: #fdfdfd;
}
:root:where(.dark, .dark *) {
--background: #1a1a1a;
}
</style>
</head>
<body></body>
</html> |
Beta Was this translation helpful? Give feedback.
-
|
@jceb I decided to have more control over the implementation and mimic some custom behaviour that I expect once the components are implemented, like keeping the saved theme on local storage and checking the system's current theme. This is how it looks:
here is the code: @import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));
:root {
--background: #ffffff;
--foreground: #111827;
}
:root:where(.dark, .dark *) {
--background: #111827;
--foreground: #ffffff;
}
body {
background: var(--background);
color: var(--foreground);
}import { Preview } from '@storybook/nextjs';
import { Decorator } from '@storybook/react';
import { useEffect } from 'react';
import '../src/app/globals.css';
// Custom decorator to handle theme based on localStorage and system preference
const withTheme: Decorator = (Story, context) => {
const theme = context.globals.theme;
useEffect(() => {
const applyTheme = () => {
let isDark: boolean;
if (theme === 'dark') {
isDark = true;
localStorage.theme = 'dark';
} else if (theme === 'light') {
isDark = false;
localStorage.theme = 'light';
} else {
// 'system' or undefined - respect system preference
localStorage.removeItem('theme');
isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
}
document.documentElement.classList.toggle('dark', isDark);
};
applyTheme();
// Listen for system preference changes when in system mode
if (!theme || theme === 'system') {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const handleMediaChange = () => applyTheme();
mediaQuery.addEventListener('change', handleMediaChange);
return () => mediaQuery.removeEventListener('change', handleMediaChange);
}
}, [theme]);
return Story();
};
const preview: Preview = {
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
globalTypes: {
theme: {
description: 'Global theme for components',
defaultValue: 'system',
toolbar: {
title: 'Theme',
icon: 'circlehollow',
items: [
{ value: 'light', icon: 'sun', title: 'Light' },
{ value: 'dark', icon: 'moon', title: 'Dark' },
{ value: 'system', icon: 'browser', title: 'System' },
],
dynamicTitle: true,
},
},
},
decorators: [
withTheme,
],
};
export default preview; |
Beta Was this translation helpful? Give feedback.
-
|
I tried a bunch of things in the thread. What finally worked for me (fairly simple)... was to:
<style>
.docs-story {
background-color: var(--background);
}
</style>This is specifically targeting the docs-story canvas element. That's it. Note I'm using shadcn. Hope this helps you. : ) |
Beta Was this translation helpful? Give feedback.






Uh oh!
There was an error while loading. Please reload this page.
-
Summary
I'm using Sveltekit and Tailwind, and followed the guide here to add the "theme" selector:
My question is, how can I make the background color of the canvas change to a dark tailwind class, when dark-mode is selected? Thanks 🙏
Additional information
No response
Create a reproduction
No response
Beta Was this translation helpful? Give feedback.
All reactions