Determinism
Storybook addon that seeds Math.random and crypto for deterministic stories, snapshots, and visual regression tests.
Storybook Addon Determinism
A Storybook addon that seeds the non-time entropy sources a component reads at render — Math.random(), crypto.randomUUID(), and crypto.getRandomValues() — so stories render the same pixels every time. Built for deterministic snapshots and visual regression tests of UI whose output would otherwise be random: shuffled layouts, placeholder/skeleton widths, avatar colors, demo chart data, generated ids/keys.
Need to freeze the clock (
Date,setTimeout,setInterval,requestAnimationFrame,performance)? That is a separate concern — usestorybook-addon-mock-date. The two compose cleanly.
Requirements
- Storybook
^10.0.0 - Renderer-agnostic — ships a preview decorator that works with any Storybook framework (React, Vue, Svelte, etc.)
Installation
npm install --save-dev storybook-addon-determinism
// .storybook/main.ts
import type { StorybookConfig } from '@storybook/your-framework';
const config: StorybookConfig = {
addons: [
'storybook-addon-determinism', // 👈 register the addon
],
};
export default config;
Usage
Enable determinism via the determinism parameter at the story, meta, or preview level (precedence: story > meta > preview).
// Button.stories.ts
export const Default: Story = {
parameters: {
determinism: true, // seed every source with the default seed (1)
},
};
// Pick a seed
export const Variant: Story = {
parameters: {
determinism: { seed: 42 },
},
};
// Selectively enable sources
export const OnlyRandom: Story = {
parameters: {
determinism: { seed: 42, random: true, crypto: false },
},
};
// Opt a story out
export const Live: Story = {
parameters: {
determinism: false,
},
};
Set a default for every story in .storybook/preview.ts:
const preview: Preview = {
parameters: {
determinism: true,
},
};
The seeded sources are re-applied on every render, so each story starts from the same sequence regardless of what earlier stories consumed. Math.random, crypto.randomUUID, and crypto.getRandomValues each draw from an independent stream, so one source minting values never shifts another's output.
Global seed override
The decorator also reads a determinism global. When it is set to a seed number it forces every source on with that seed, overriding the determinism parameter — so the full precedence is global > story > meta > preview. This is an escape hatch for driving the seed without touching parameters (from Storybook globals, or a future toolbar):
// per story
export const Replayed: Story = {
globals: { determinism: 7 },
};
// or for the whole preview
const preview: Preview = {
initialGlobals: { determinism: 7 },
};
Set it back to undefined to fall back to the determinism parameter.
What gets seeded
| Source | Replaced with |
|---|---|
Math.random() |
a seeded PRNG (mulberry32) |
crypto.randomUUID() |
a deterministic RFC 4122 v4 UUID from the seed (covers nothing else; uuid v4 libraries that call getRandomValues are covered below) |
crypto.getRandomValues() |
seeded bytes (covers uuid v4, nanoid, etc.) |
The originals are restored as soon as a story opts out (determinism: false), and every render swaps in a fresh patch — so seeded values never leak between stories.
What does not get seeded
This addon only touches the JS entropy APIs above. Out of scope:
- The clock —
Date/ timers /requestAnimationFrame/performance. Usestorybook-addon-mock-date(they compose). - CSS animations/transitions, the Web Animations API — disable those separately for stable snapshots.
Intllocale / timezone — number/date/currency formatting. There is no reliable runtime setter (timezone in particular is only dependable via theTZenv var before process start), so it is intentionally left out.- Network — mock with MSW or similar.
License
MIT