Storybook Global Decorators: Architecture Note on Consistent Context Wrapping
An architecture note on using Storybook's global decorators array to wrap every story with shared context providers, covering requirements, minimal design, trust boundaries, operational checks, failure modes, and triggers for redesign.
06 Nov 2025, 11:06 UTC

Requirements
Teams adopt a global decorator when every story must share the same runtime context—Redux store, theme provider, router, or feature‑flag wrapper—without repeating boilerplate in each .stories.tsx file. The requirement is a single, version‑controlled wrapper that:
- Applies to all stories in the preview iframe.
- Remains pure (no async side effects) so Storybook’s isolated rendering model stays deterministic.
- Can be typed and tree‑shaken in Storybook v7+.
Minimal Design
Storybook v7 replaces the legacy addDecorator call with a top‑level decorators array in .storybook/main.ts (or main.js). The smallest viable configuration wraps each story with a React context provider:
// .storybook/main.ts
import type { StorybookConfig } from '@storybook/react-vite';
import { ThemeProvider } from 'styled-components';
import { theme } from '../src/theme';
const config: StorybookConfig = {
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
addons: [],
decorators: [
(Story) => (
<ThemeProvider theme={theme}>
<Story />
</ThemeProvider>
),
],
};
export default config;
The decorator is a pure function that receives the Story component and returns JSX. No imports of browser‑only APIs (e.g., localStorage) appear here; if they are needed, polyfill them in .storybook/preview.ts.
Trust and Data Boundaries
The global decorator sits at the preview iframe boundary. It receives only the story component and the global globalTypes (e.g., theme, locale) that Storybook injects. It must not:
- Mutate global state (Redux store, singleton services) because stories run sequentially in the same iframe.
- Perform network requests or timers; those break snapshot isolation and cause flaky visual tests.
- Depend on
windowobjects that are absent in the headless test runner (e.g.,matchMedia) unless explicitly polyfilled.
Because the decorator is shared, a mis‑configured provider (wrong theme object, missing store reducer) becomes a single point of failure that hides component‑level errors across the entire catalog.
Operational Checks
- Start Storybook – run
npm run storybookfrom the repository root (requires Node ≥ 18, write access tonode_modules). Verify the UI loads without console errors. - Inspect the DOM – open the browser dev tools, select the story iframe, and confirm the theme class (e.g.,
data-theme="dark") appears on the root element. This proves the decorator executed. - Run snapshot tests – execute
npm run test-storybook(ornpx storybook test) in CI. The command runs the same preview iframe under Playwright; snapshots must match the decorated output. A mismatch indicates the decorator altered rendering unexpectedly. - Bundle size audit – run
npm run build-storybook -- --statsand inspectstats.htmlfor the decorator’s contribution. If the decorator tree adds > 150 KB gzipped, consider lazy‑loading heavy providers.
Failure Modes
| Symptom | Root Cause | Mitigation |
|---|---|---|
| All stories render blank or throw “Provider not found” | Decorator returns null or forgets to render <Story /> | Enforce a lint rule: decorator must spread Story (e.g., eslint-plugin-storybook rule decorator-returns-story). |
| Snapshots diverge between runs | Decorator mutates a shared Redux store or uses Date.now() | Make decorator pure; reset store in preview.ts via beforeEach hook. |
| Storybook startup > 30 s | Decorator imports large UI library (e.g., full MUI) for every story | Split heavy providers into per‑story decorators or use dynamic import() inside the decorator. |
| Tests fail in headless CI but pass locally | Decorator accesses window.matchMedia without polyfill | Add polyfill in .storybook/preview.ts or guard with typeof window !== 'undefined'. |
When to Change the Design
- Multiple distinct contexts – If a subset of stories needs a different router or auth state, replace the single global decorator with per‑story or per‑component decorators, or use
globalTypes+decoratorsthat read the active global type. - Performance regression – Bundle analysis shows the decorator tree dominates the preview bundle; migrate heavy providers to lazy decorators.
- Framework upgrade – Storybook v8 may introduce a new
decoratorsAPI (e.g., async decorators). Re‑evaluate purity constraints at that point. - Team policy shift – Adoption of a new state‑management library that discourages global providers; move wrapping logic into individual stories for explicitness.
Limitations & Verification
The global decorator pattern guarantees consistency but cannot express story‑specific overrides without additional globalTypes or per‑story decorators. Verify the current setup by:
- Running
npm run storybookand confirming the theme class on the iframe root. - Executing
npm run test-storybookand ensuring zero snapshot diffs. - Checking
stats.htmlfor decorator bundle impact.
If any check fails, treat the decorator as a breaking change candidate and follow the failure‑mode mitigations above.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.