Recommended Pattern: Decorator Driven by Toolbar Global
Storybook runs stories in a real browser iframe, so Date and Intl.DateTimeFormat reflect the host machine. The only reliable way to mock a time zone is to wrap stories in a decorator that installs a shim (e.g., timezone-mock or @sinonjs/fake-timers) based on a global value.
Implementation Steps
- Define a global type in
.storybook/preview.ts:
export const globalTypes = {
timezone: {
name: 'Timezone',
description: 'Timezone for date formatting',
defaultValue: 'UTC',
toolbar: {
icon: 'globe',
items: ['UTC', 'America/New_York', 'Asia/Tokyo'],
showName: true,
},
},
};
- Create a decorator that reads
globals.timezone and activates the shim before rendering:
import { timezoneMock } from 'timezone-mock';
export const decorators = [
(Story, context) => {
const tz = context.globals.timezone;
timezoneMock.register(tz);
return ;
},
];
- In CI, set the container TZ so any Node‑side rendering matches:
# GitHub Actions example
env:
TZ: UTC
Storybook 8 Changes
Storybook 8 (released 2024) added no API for time‑zone or locale overrides. The addon system still centers on args, globals, and viewports. The pattern above works the same in v7 and v8.
Toolbar Selector Limitations
A toolbar item can change the timezone global, but Storybook does not automatically reconfigure Intl. The decorator must listen for the global change and re‑apply the shim. The first render after a toolbar switch may briefly show the host time zone before the story re‑renders.
Verification Checklist
- Add a story that prints
new Date().toString() and new Intl.DateTimeFormat(undefined, { timeZoneName: 'short' }).format(new Date()).
- Open Storybook, switch the toolbar selector, and confirm the strings update without a full page reload.
- Run the story in CI with
TZ=UTC and compare output to a local UTC run.
- If using visual regression (Chromatic, Percy), capture snapshots for each time‑zone value.
One Diagnostic Detail Needed
Which UI framework (React, Vue, Svelte, etc.) and test runner (Jest, Vitest, none) are you using? The fake‑timer setup differs slightly (e.g., jest-date-mock for Jest, vi.useFakeTimers() for Vitest) and affects snapshot determinism.