Storybook 暗色主题配置指南:为 Docs 与整个 UI 应用深色主题
【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybook
本指南聚焦于 Storybook 主题系统中的 Docs 主题配置,核心场景是在.storybook/preview.js中通过parameters.docs.theme为文档页(Docs)指定暗色主题,同时兼顾管理器(Manager UI)的全局主题设置与自定义主题的创建方法。读完本文,你将掌握内置主题(light/dark/normal)的选择机制、Docs 与主 UI 独立主题化的工作方式,并能基于storybook/theming的create()API 生成符合团队品牌的自定义主题。
主题系统概览:内置主题与独立作用域
Storybook 使用一套轻量的 theming API(storybook/theming),主题系统分为两个相互独立的层面:
- Manager(管理器 UI):侧边栏、工具栏、面板等界面外壳,通过
.storybook/manager.js中的addons.setConfig({ theme })配置。 - Docs(文档页):基于 MDX/CSF 生成的组件文档,通过
.storybook/preview.js中的parameters.docs.theme配置。
Storybook 内置了三套可直接使用的主题——light、dark以及跟随系统偏好(preferred color scheme)的normal。除非显式指定,管理器默认使用normal主题;而 Docs 的默认主题始终是 light,无论主 UI 当前处于何种主题。这意味着即使你把整个 Storybook 界面切换成了暗色,文档页仍会以亮色渲染,需要单独配置。
从源码看,内置主题的定义集中在 code/core/src/theming/create.ts,themes对象由三部分组成:
export const themes: Themes = { ...themesBase, // light 与 dark normal: themesBase[preferredColorScheme], // 跟随系统偏好 };其中preferredColorScheme由getPreferredColorScheme()在运行时解析,因此normal主题在不同操作系统/浏览器设置下会解析为light或dark。该模块还同时导出了create()函数,用于基于内置主题创建自定义主题(详见后文)。
为 Docs 配置暗色主题
要让 Docs 文档页使用暗色主题,需要在预览侧(preview)的parameters.docs中设置theme字段,将其指向storybook/theming导出的themes.dark。
CSF 3 写法(通用 renderer)
在.storybook/preview.js中:
import { themes } from 'storybook/theming'; export default { parameters: { docs: { theme: themes.dark, }, }, };对应的 TypeScript 版本(.storybook/preview.ts):
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. import type { Preview } from '@storybook/your-framework'; import { themes } from 'storybook/theming'; const preview: Preview = { parameters: { docs: { theme: themes.dark, }, }, }; export default preview;注意:@storybook/your-framework是占位符,实际应替换为项目所用的框架包,例如@storybook/react-vite、@storybook/nextjs、@storybook/vue3-vite等。
CSF Next 🧪 写法(definePreview)
CSF Next 实验性语法通过definePreview()组织配置,需要显式引入并注册@storybook/addon-docs。React 框架(.storybook/preview.tsx):
// Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite) import { definePreview } from '@storybook/your-framework'; import addonDocs from '@storybook/addon-docs'; import { themes } from 'storybook/theming'; export default definePreview({ addons: [addonDocs()], parameters: { docs: { theme: themes.dark, }, }, });JS 版本(.storybook/preview.jsx):
// Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite) import { definePreview } from '@storybook/your-framework'; import addonDocs from '@storybook/addon-docs'; import { themes } from 'storybook/theming'; export default definePreview({ addons: [addonDocs()], parameters: { docs: { theme: themes.dark, }, }, });Vue 框架(.storybook/preview.ts):
import { definePreview } from '@storybook/vue3-vite'; import addonDocs from '@storybook/addon-docs'; import { themes } from 'storybook/theming'; export default definePreview({ addons: [addonDocs()], parameters: { docs: { theme: themes.dark, }, }, });import { definePreview } from '@storybook/vue3-vite'; import addonDocs from '@storybook/addon-docs'; import { themes } from 'storybook/theming'; export default definePreview({ addons: [addonDocs()], parameters: { docs: { theme: themes.dark, }, }, });Angular 框架(.storybook/preview.ts):
import { definePreview } from '@storybook/angular'; import addonDocs from '@storybook/addon-docs'; import { themes } from 'storybook/theming'; export default definePreview({ addons: [addonDocs()], parameters: { docs: { theme: themes.dark, }, }, });Web Components 框架(.storybook/preview.ts):
import { definePreview } from '@storybook/web-components-vite'; import addonDocs from '@storybook/addon-docs'; import { themes } from 'storybook/theming'; export default definePreview({ addons: [addonDocs()], parameters: { docs: { theme: themes.dark, }, }, });import { definePreview } from '@storybook/web-components-vite'; import addonDocs from '@storybook/addon-docs'; import { themes } from 'storybook/theming'; export default definePreview({ addons: [addonDocs()], parameters: { docs: { theme: themes.dark, }, }, });为整个 Storybook UI 应用暗色主题
如果你希望侧边栏、工具栏、面板等 Manager UI 也整体变为暗色,则需在.storybook/manager.js中通过addons.setConfig()设置主题(相关代码片段见 docs/_snippets/storybook-manager-dark-theme.md):
import { addons } from 'storybook/manager-api'; import { themes } from 'storybook/theming'; addons.setConfig({ theme: themes.dark, });Manager 与 Docs 的配合关系
主 UI 与 Docs 使用同一套主题系统,但彼此独立渲染、互不继承。假设你已在manager.js中配置了暗色主题,若想让 Docs 保持一致,仍需按上一节的方式在preview.js中单独指定parameters.docs.theme。两处配置叠加后,整个 Storybook 才会呈现统一的暗色观感。
需要强调的是:设置主题时应当传入完整的主题对象——主题是整体替换而非合并。因此不要只传一个{ base: 'dark' }的部分对象到setConfig或docs.theme,除非你明确使用create()生成完整主题。
深入源码:dark 主题的构成与 create() 创建自定义主题
内置 dark 主题变量
themes.dark本质上是定义在 code/core/src/theming/themes/dark.ts 中的一组ThemeVars,主要包括:
- 品牌色:
colorPrimary: '#FF4785'(珊瑚红)、colorSecondary: '#479DFF' - 界面背景:
appBg: '#1B1C1D'、appContentBg: '#222325'、appHoverBg: '#233952'、appBorderColor: 'hsl(0 0% 100% / 0.1)'、appBorderRadius: 4 - 字体:
fontBase、fontCode继承自全局排版基础变量 - 文字颜色:
textColor: '#C9CCCF'、textInverseColor: '#1B1C1D'、textMutedColor: '#95999D' - 工具栏:
barTextColor: '#95999D'、barHoverColor: '#70B3FF'、barSelectedColor: '#479DFF'、barBg: '#222325' - 表单控件:
buttonBg、booleanBg、inputBg、inputBorder、inputTextColor等
light主题定义于同目录的light.ts,结构一致、取值相反。Docs 暗色主题的渲染正是由这些变量驱动的(Docs 容器与各 Block 组件均通过 emotion 从主题中取色,相关实现可参见 code/addons/docs/src 下的 Blocks 组件)。
用 create() 生成自定义主题
内置主题无法满足品牌需求时,可在.storybook目录下新建YourTheme.js,基于create()派生新主题:
import { create } from 'storybook/theming'; export default create({ base: 'light', // Brand brandTitle: 'My custom Storybook', brandUrl: 'https://example.com', brandImage: 'https://storybook.js.org/images/placeholders/350x150.png', brandTarget: '_self', // Colors colorPrimary: '#3AEB97', colorSecondary: '#70C3FF', // UI appBg: 'linear-gradient(to bottom right, #ffb1b1, #ffd6a5)', appContentBg: 'linear-gradient(to bottom right, #f9f4f1, #fce9d8)', appBorderColor: 'grey', appBorderRadius: 4, // Fonts fontBase: '"Open Sans", sans-serif', fontCode: 'monospace', // Text colors textColor: 'black', textInverseColor: 'rgba(255,255,255,0.9)', textMutedColor: 'grey', });create()的合并逻辑见 code/core/src/theming/create.ts:先继承preferredColorScheme对应主题,再叠加vars.base指定的内置主题,最后合并用户传入的vars,并自动保证barSelectedColor回退到colorSecondary。其中base属性是必填的,其余变量均可选。
之后在manager.js中引入即可生效:
import { addons } from 'storybook/manager-api'; import yourTheme from './YourTheme'; addons.setConfig({ theme: yourTheme, });storybook/theming使用 TypeScript 编写,类型定义随包发布,TypeScript 用户可以借助类型提示构建合法主题。
进阶定制:CSS 逃生舱与 MDX 组件覆盖
主题 API 刻意保持精简。若需要更细粒度的样式控制,可借助以下逃生舱(均为高级用法,使用时需自行承担风险,因为 Storybook 内部 HTML 结构可能在版本迭代中变化):
- Storybook UI 的样式:在
.storybook/manager-head.html中插入<style>标签; - Docs 的样式:在
.storybook/preview-head.html中插入<style>标签。
另外,使用 MDX 编写文档时,还可以通过parameters.docs.components覆盖 Markdown 渲染的组件(例如自定义code块的渲染器,甚至替换内置的<Canvas />Block)。这是 Storybook 官方未正式支持的进阶能力,但为特殊场景提供了极大的灵活性。
小结与最佳实践
为 Storybook 应用暗色主题时,记住两条关键结论:
- Docs 与 Manager 独立主题化:
themes.dark只作用于你配置它的那一侧。想全站暗色,需同时在manager.js(addons.setConfig)和preview.js(parameters.docs.theme)两处配置; - 主题是替换而非合并:始终传入由内置
themes或create()生成的完整主题对象;base必填。
从 code/core/src/theming/create.ts 的源码可以确认,normal主题是跟随系统配色偏好的动态解析结果,而light/dark是稳定的内置常量——这使 Storybook 既能开箱即用地切换深浅色,也支持基于create()的深度品牌定制。完整的主题配置说明还可参考 docs/configure/user-interface/theming.mdx。
【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybook
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考