Skip to content

Commit 3a66e87

Browse files
authored
Merge pull request #145 from argos-ci/storybook-modes
Document storybook modes
2 parents eff29ec + 717bccb commit 3a66e87

File tree

2 files changed

+308
-21
lines changed

2 files changed

+308
-21
lines changed

docs/guides/storybook-story-modes.mdx

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
---
2+
slug: /storybook-story-modes
3+
title: Storybook story modes
4+
---
5+
6+
# Storybook story modes for testing themes, viewports, locales and more
7+
8+
Argos can capture multiple versions of your stories by applying different “modes,” which are essentially combinations of global Storybook settings (such as theme, viewport, locale, etc.). With modes, you can automatically generate a separate snapshot for each unique configuration.
9+
10+
:::note
11+
12+
If you already have `parameters.chromatic.modes`, Argos will handle those settings by default. Prefer `parameters.argos.modes` in new work.
13+
14+
:::
15+
16+
## What are modes?
17+
18+
A mode is a preset that configures various Storybook globals. For instance, you can have a “dark” mode for your UI theme, a “mobile” mode for smaller screens, or a combined “dark-mobile-spanish” mode that configures multiple globals at once.
19+
20+
**Key features of modes:**
21+
22+
- Each mode is named (e.g., "dark desktop" or "mobile").
23+
- Each mode sets specific values for the Storybook globals (e.g., viewport size, background color, locale).
24+
- Argos creates a separate visual baseline for each mode name.
25+
26+
## Setting up globals & addons
27+
28+
Before you define any modes, make sure you’ve configured the relevant Storybook addons in your .storybook/preview.js (or .storybook/preview.ts) file. Examples include:
29+
30+
- [`@storybook/addon-viewport`](https://www.npmjs.com/package/@storybook/addon-viewport) for screen sizes
31+
- [`@storybook/addon-themes`](https://www.npmjs.com/package/@storybook/addon-themes) for light/dark themes
32+
- [`@storybook/addon-backgrounds`](https://www.npmjs.com/package/@storybook/addon-backgrounds) for backgrounds
33+
- [`storybook-i18n`](https://www.npmjs.com/package/storybook-i18n) for locales
34+
35+
These addons utilize Storybook “globals” and “decorators” under the hood. Argos modes simply manipulate those globals at test time to generate multiple snapshots of the same story.
36+
37+
```ts
38+
// .storybook/preview.js
39+
import { withThemeByClassName } from "@storybook/addon-themes";
40+
import "../src/styles.css";
41+
42+
const preview = {
43+
parameters: {
44+
viewport: {
45+
viewports: {
46+
compact: {
47+
name: "Compact",
48+
styles: { width: "600px", height: "900px" },
49+
},
50+
widescreen: {
51+
name: "Widescreen",
52+
styles: { width: "1440px", height: "900px" },
53+
},
54+
},
55+
},
56+
backgrounds: {
57+
values: [
58+
{ name: "Light", value: "#ffffff" },
59+
{ name: "Dark", value: "#1A1A1A" },
60+
],
61+
},
62+
},
63+
decorators: [
64+
withThemeByClassName({
65+
themes: {
66+
light: "light",
67+
dark: "dark",
68+
},
69+
defaultTheme: "light",
70+
}),
71+
],
72+
};
73+
74+
export default preview;
75+
```
76+
77+
## Defining modes
78+
79+
Create a `.storybook/modes.js` (or `.ts`) file that exports an object where each key is a mode name and each value is a set of overrides for the Storybook globals. For example:
80+
81+
```ts
82+
// .storybook/modes.js
83+
84+
export const allModes = {
85+
dark: {
86+
backgrounds: { value: "#1A1A1A" },
87+
theme: "dark",
88+
},
89+
mobile: {
90+
viewport: "compact",
91+
},
92+
"dark widescreen": {
93+
backgrounds: { value: "#1A1A1A" },
94+
theme: "dark",
95+
viewport: "widescreen",
96+
},
97+
"light mobile": {
98+
backgrounds: { value: "#ffffff" },
99+
theme: "light",
100+
viewport: "compact",
101+
},
102+
};
103+
```
104+
105+
Each object can include as many or as few globals as you need. If a mode doesn’t specify a particular global, that global simply won’t be changed in that mode.
106+
107+
## Applying modes
108+
109+
Attach modes to any level of your Storybook: globally in `.storybook/preview.js`, at the component (default export) level, or in an individual story’s parameters. Argos merges all modes defined up the chain.
110+
111+
### Basic usage in a story file
112+
113+
```ts
114+
// src/components/ProductCard/ProductCard.stories.js
115+
116+
import { ProductCard } from "./ProductCard";
117+
import { allModes } from "../../../.storybook/modes";
118+
119+
export default {
120+
title: "Components/ProductCard",
121+
component: ProductCard,
122+
parameters: {
123+
// Use Argos for new projects; Chromatic is recognized too
124+
argos: {
125+
modes: {
126+
mobile: allModes.mobile,
127+
dark: allModes.dark,
128+
},
129+
},
130+
},
131+
};
132+
133+
// Story #1
134+
export const DefaultView = {
135+
args: {
136+
productName: "Coffee Beans",
137+
price: 9.99,
138+
},
139+
};
140+
141+
// Story #2
142+
export const SoldOutView = {
143+
args: {
144+
productName: "Coffee Beans",
145+
price: 9.99,
146+
isSoldOut: true,
147+
},
148+
};
149+
```
150+
151+
In this example, Argos will generate two snapshots for each story (DefaultView and SoldOutView): one in “mobile” mode and another in “dark” mode.
152+
153+
## Combining modes from multiple levels
154+
155+
You can add modes in your .storybook/preview.js at the project level, then define additional modes in a story file. Argos “stacks” these modes, creating a snapshot for every combination.
156+
157+
### Project-level modes
158+
159+
```ts
160+
// .storybook/preview.js
161+
import { allModes } from "./modes";
162+
163+
const preview = {
164+
parameters: {
165+
argos: {
166+
modes: {
167+
"light mobile": allModes["light mobile"],
168+
},
169+
},
170+
},
171+
};
172+
173+
export default preview;
174+
```
175+
176+
### Component-level modes
177+
178+
```ts
179+
// ProductCard.stories.js
180+
import { ProductCard } from "./ProductCard";
181+
import { allModes } from "../../../.storybook/modes";
182+
183+
export default {
184+
title: "Components/ProductCard",
185+
component: ProductCard,
186+
parameters: {
187+
argos: {
188+
modes: {
189+
"dark widescreen": allModes["dark widescreen"],
190+
},
191+
},
192+
},
193+
};
194+
195+
// Single story for brevity
196+
export const Basic = {
197+
args: {
198+
/* ... */
199+
},
200+
};
201+
```
202+
203+
When Argos runs, it will generate snapshots for each mode defined at the project level and the component level. So for Basic, you get “light mobile” (from `preview.js`) plus “dark widescreen” (from the component’s parameter).
204+
205+
## Excluding or disabling modes
206+
207+
Sometimes you want to turn off a certain higher-level mode for a specific story. You can do this by passing a disable property:
208+
209+
```ts
210+
// ProductCard.stories.js
211+
export const SpecialCard = {
212+
args: {
213+
/* ... */
214+
},
215+
parameters: {
216+
argos: {
217+
modes: {
218+
"light mobile": { disable: true }, // turns off this inherited mode
219+
},
220+
},
221+
},
222+
};
223+
```
224+
225+
That story will now ignore light mobile mode but still apply any other inherited modes.
226+
227+
## Working with baselines
228+
229+
Each mode name corresponds to a separate baseline in Argos. If you rename a mode, it’s treated as entirely new. If you alter the internals of a mode (like changing the viewport from “compact” to “ultra-compact”) without renaming it, Argos still compares the new screenshot against the old baselines for that mode name.
230+
231+
:::note
232+
233+
If you have an original single baseline from before you introduced modes, and you want to keep it around, just add a mode like "baseline" that reproduces the same environment as the original story. That way, your old baseline is preserved while you experiment with new modes.
234+
235+
:::
236+
237+
## FAQ
238+
239+
<details>
240+
<summary>
241+
Can modes be applied if I'm still using `parameters.chromatic`?
242+
</summary>
243+
244+
Yes. Argos reads your `chromatic.modes` settings if present. However,
245+
for new users or updated setups, prefer using `argos.modes` to avoid
246+
any confusion in the future.
247+
248+
</details>
249+
250+
<details>
251+
<summary>Do all Storybook addons work with Argos modes?</summary>
252+
253+
Any addon that leverages Storybook globals should work, including [@storybook/addon-themes](https://storybook.js.org/addons/@storybook/addon-themes),
254+
[@storybook/addon-viewport](https://storybook.js.org/addons/@storybook/addon-viewport),
255+
[@storybook/addon-backgrounds](https://storybook.js.org/addons/@storybook/addon-backgrounds),
256+
or [storybook-i18n](https://storybook.js.org/addons/storybook-i18n). Modes just
257+
provide different values for those globals.
258+
259+
</details>
260+
261+
<details>
262+
<summary>What happens if I delete or rename a mode?</summary>
263+
264+
If you remove a mode from your code, Argos will stop capturing new snapshots for
265+
that mode, and its baseline history won’t be updated anymore. Renaming a mode effectively
266+
creates a new baseline, much like renaming a story.
267+
268+
</details>
269+
270+
Enjoy your multi-mode visual tests! By setting up modes for dark vs. light, mobile vs. desktop, and everything in between, you can verify all key variants of your UI without writing extra stories.

src/css/custom.css

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,19 @@
2222
--ifm-color-primary-lighter: theme(colors.violet.7);
2323
--ifm-color-primary-lightest: theme(colors.violet.6);
2424

25-
--ifm-color-info: theme(colors.sky.9);
26-
--ifm-color-info-dark: theme(colors.sky.10);
27-
--ifm-color-info-darker: theme(colors.sky.11);
28-
--ifm-color-info-darkest: theme(colors.sky.12);
29-
--ifm-color-info-light: theme(colors.sky.8);
30-
--ifm-color-info-lighter: theme(colors.sky.7);
31-
--ifm-color-info-lightest: theme(colors.sky.6);
25+
--ifm-color-info: theme(colors.mauve.9);
26+
--ifm-color-info-dark: theme(colors.mauve.8);
27+
--ifm-color-info-darker: theme(colors.mauve.11);
28+
--ifm-color-info-darkest: theme(colors.mauve.12);
29+
--ifm-color-info-light: theme(colors.mauve.8);
30+
--ifm-color-info-lighter: theme(colors.mauve.7);
31+
--ifm-color-info-lightest: theme(colors.mauve.6);
3232

3333
--ifm-background-color: theme(colors.mauve.1);
3434
--ifm-toc-border-color: theme(colors.mauve.6);
3535

3636
--ifm-spacing-horizontal: 2rem;
37+
--ifm-code-background: theme(colors.mauve.2);
3738
}
3839

3940
html[data-theme="dark"] {
@@ -45,18 +46,22 @@ html[data-theme="dark"] {
4546
--ifm-color-primary-lighter: theme(colors.violet.7);
4647
--ifm-color-primary-lightest: theme(colors.violet.6);
4748

48-
--ifm-color-info: theme(colors.sky.9);
49-
--ifm-color-info-dark: theme(colors.sky.10);
50-
--ifm-color-info-darker: theme(colors.sky.11);
51-
--ifm-color-info-darkest: theme(colors.sky.12);
52-
--ifm-color-info-light: theme(colors.sky.8);
53-
--ifm-color-info-lighter: theme(colors.sky.7);
54-
--ifm-color-info-lightest: theme(colors.sky.6);
49+
--ifm-color-info: theme(colors.mauve.9);
50+
--ifm-color-info-dark: theme(colors.mauve.10);
51+
--ifm-color-info-darker: theme(colors.mauve.11);
52+
--ifm-color-info-darkest: theme(colors.mauve.12);
53+
--ifm-color-info-light: theme(colors.mauve.8);
54+
--ifm-color-info-lighter: theme(colors.mauve.7);
55+
--ifm-color-info-lightest: theme(colors.mauve.6);
5556

5657
--ifm-background-color: theme(colors.mauve.1);
5758
--ifm-toc-border-color: theme(colors.mauve.6);
5859
}
5960

61+
.alert {
62+
--ifm-code-background: theme(colors.mauve.2);
63+
}
64+
6065
/* Main layout */
6166
.navbar__inner {
6267
max-width: 1420px;
@@ -171,24 +176,32 @@ nav.menu {
171176
@apply w-4 h-4 align-middle;
172177
}
173178

174-
/* Alert */
179+
/* Alert and details */
180+
175181
.alert.alert {
176-
@apply flex shadow-none px-4 py-3 border bg-transparent border-l-4 border-alert;
182+
@apply shadow-none px-4 py-3 border bg-transparent border-alert;
183+
}
184+
185+
div.alert.alert {
186+
@apply flex bg-transparent border-l-4;
177187
}
178188

179-
.alert.alert p {
189+
div .alert.alert p {
180190
@apply first:mt-0 last:mb-0;
181191
}
182192

183-
.alert.alert--secondary::before {
193+
div .alert.alert--secondary::before {
184194
content: "⚑";
185195
@apply text-xl mr-3;
186196
}
187197

188-
.alert > div:first-child {
198+
div .alert > div:first-child {
189199
display: none;
190200
}
191201

202+
.alert.alert--info {
203+
}
204+
192205
.alert.alert--warning {
193206
@apply border-amber-8 text-sm text-amber-12 bg-amber-1;
194207

@@ -202,12 +215,16 @@ nav.menu {
202215
}
203216
}
204217

205-
.alert p {
218+
summary {
219+
display: block;
220+
}
221+
222+
div.alert p {
206223
@apply m-0;
207224
}
208225

209226
/* Sidebar */
210-
..theme-doc-sidebar-menu.theme-doc-sidebar-menu
227+
.theme-doc-sidebar-menu.theme-doc-sidebar-menu
211228
.menu__list-item-collapsible
212229
.menu__link {
213230
@apply text text-sm;

0 commit comments

Comments
 (0)