Skip to content

04 — Storybook-First Development

Dev Guide — Savoy Signature Hotels
PRD refs: 05_Design_System_and_Theming.md, 07_Modules_and_Templates.md, 18_QA_Pipeline_and_Testing.md


Storybook is the primary development environment for the Savoy platform. Every UI component and CMS module is developed, tested, and validated in Storybook before integration with the Next.js application.


BenefitExplanation
IsolationDevelop components without CMS data, routing, or server dependencies
Multi-theme validationInstantly switch between 8 hotel themes
Responsive testingViewport controls for mobile/tablet/desktop
Visual regressionChromatic/Percy runs against stories, not the full app
Design reviewDesigners compare Figma link directly in Storybook
AI developmentClaude Code can validate components visually via screenshots

apps/storybook/
.storybook/
main.ts # Config: references packages/ui + packages/modules
preview.ts # Global decorators + theme setup
theme-decorator.tsx # Theme switcher for toolbar
package.json
tsconfig.json

Stories live alongside their components:

packages/modules/src/m05-hero-slider/HeroSlider.stories.tsx
packages/ui/src/Button/Button.stories.tsx

import type { Meta, StoryObj } from '@storybook/react';
import { ComponentName } from './ComponentName';
const meta: Meta<typeof ComponentName> = {
title: 'Category/ID — Component Name', // e.g., 'Modules/M08 — Card Grid'
component: ComponentName,
tags: ['autodocs'],
parameters: {
layout: 'fullscreen', // 'fullscreen' for modules, 'centered' for UI
design: {
type: 'figma',
url: 'https://www.figma.com/file/...', // REQUIRED: link to Figma design
},
},
};
export default meta;
type Story = StoryObj<typeof ComponentName>;
Component TypeTitle PatternExample
UI ComponentUI/{ComponentName}UI/Button
ModuleModules/{ID} — {Name}Modules/M05 — Hero Slider
WidgetWidgets/{Name}Widgets/BookingBar
TemplateTemplates/{Name}Templates/RoomDetail

Every module must include stories for:

VariantPurpose
DefaultStandard rendering with typical content
WithOptionalPropsAll optional props filled
MinimalContentMinimum viable content (edge case)
LongContentOverflow / text wrapping test
EmptyStateWhat happens with empty arrays or no content

Use realistic mock data that matches what the CMS provides:

// GOOD: realistic hotel content
const mockSlide = {
imageDesktop: { url: '/storybook/hero-pool-desktop.jpg', width: 1920, height: 1080 },
imageMobile: { url: '/storybook/hero-pool-mobile.jpg', width: 750, height: 1000 },
altText: 'Infinity pool overlooking the Atlantic Ocean',
caption: 'Infinity Pool at Savoy Palace',
};
// BAD: useless placeholder
const mockSlide = {
imageDesktop: { url: '/test.jpg', width: 100, height: 100 },
imageMobile: { url: '/test.jpg', width: 100, height: 100 },
altText: 'test',
};

The global theme decorator allows switching between all 8 themes:

apps/storybook/.storybook/preview.ts
import type { Preview } from '@storybook/react';
import { ThemeDecorator } from './theme-decorator';
const preview: Preview = {
decorators: [ThemeDecorator],
globalTypes: {
theme: {
description: 'Hotel theme',
toolbar: {
title: 'Theme',
icon: 'paintbrush',
items: [
{ value: 'savoy-palace', title: 'Savoy Palace' },
{ value: 'royal-savoy', title: 'Royal Savoy' },
{ value: 'saccharum', title: 'Saccharum' },
{ value: 'the-reserve', title: 'The Reserve' },
{ value: 'calheta-beach', title: 'Calheta Beach' },
{ value: 'gardens', title: 'Gardens' },
{ value: 'hotel-next', title: 'Hotel Next' },
{ value: 'savoy-signature', title: 'Savoy Signature (Group)' },
],
dynamicTitle: true,
},
},
},
};
export default preview;
apps/storybook/.storybook/theme-decorator.tsx
import React from 'react';
import '@savoy/themes/src/index.css';
export const ThemeDecorator = (Story: any, context: any) => {
const theme = context.globals.theme || 'savoy-palace';
return (
<div data-theme={theme}>
<Story />
</div>
);
};

Configure standard viewports:

// preview.ts parameters
parameters: {
viewport: {
viewports: {
mobile: { name: 'Mobile', styles: { width: '375px', height: '812px' } },
tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } },
desktop: { name: 'Desktop', styles: { width: '1440px', height: '900px' } },
},
},
},

Minimum viewport validation per module: Mobile (375px), Tablet (768px), Desktop (1440px)


AddonPurpose
@storybook/addon-essentialsControls, actions, viewport, docs
@storybook/addon-a11yAccessibility panel (axe-core in-browser)
storybook-addon-designsEmbed Figma frames for side-by-side comparison
@storybook/testInteraction testing within stories
Pixel Perfect (custom)Visual regression + Figma fidelity testing panel

The a11y addon runs axe-core on every story. Zero violations allowed before merging.

The Pixel Perfect addon is a custom panel that validates rendered stories against Figma baselines (initial development) and regression snapshots (ongoing). Every module MUST pass visual tests before merging. See section 11 below.


  1. Read the Figma spec (from PRD or story’s design.url parameter)
  2. Create the story file with realistic mock data
  3. Implement the component
  4. Validate: does Storybook rendering match Figma?
  5. Switch themes: verify the component adapts correctly
  6. Check a11y panel: fix any violations
  7. Run Pixel Perfect visual tests (pnpm --filter storybook visual:test)
  8. Iterate until diff % is within threshold (view diffs in Pixel Perfect panel)
  9. Promote regression baselines when Figma validation passes
  1. Run pnpm storybook from root
  2. Navigate to the component in the sidebar
  3. Use Controls panel to test prop combinations
  4. Use Theme toolbar to cycle through themes
  5. Use Viewport toolbar for responsive behavior
  6. Check the a11y panel for violations
  7. Click the Figma link in Design tab to compare
  8. Open Pixel Perfect tab to see visual test results and diff overlays
  9. Run pnpm --filter storybook visual:test to capture and compare screenshots

Place in apps/storybook/public/storybook/:

hero-pool-desktop.jpg (1920x1080)
hero-pool-mobile.jpg (750x1000)
room-desktop.jpg (800x600)
room-mobile.jpg (400x300)
restaurant-desktop.jpg (800x600)
restaurant-mobile.jpg (400x300)
gallery-01-desktop.jpg (1200x800)
gallery-01-mobile.jpg (600x400)

11. Pixel Perfect Visual Testing (MANDATORY)

Section titled “11. Pixel Perfect Visual Testing (MANDATORY)”

Every module MUST pass Pixel Perfect visual tests. This is an absolute rule — no module is merged without visual validation.

When building a new module, compare the rendered story against the Figma design:

  1. Fetch Figma baselines — Use Figma MCP get_screenshot to capture desktop (1440x900) and mobile (375x812) screenshots of the design
  2. Save baselinesapps/storybook/__figma_baselines__/{storyId}/desktop-1440x900.png and mobile-375x812.png
  3. Register mapping — Add story ID → Figma node in .storybook/visual-testing/figma-mapping.ts
  4. Run testspnpm --filter storybook visual:test (Storybook must be running on port 6006)
  5. Review — Open Pixel Perfect panel, view diff overlay (red pixels = differences)
  6. Iterate — Adjust CSS, re-run tests until diff is within threshold (default: 0.5%)

After initial approval, subsequent changes are tested against regression baselines:

  1. Run testspnpm --filter storybook visual:test
  2. If failing — Open Pixel Perfect panel, compare current screenshot with baseline
  3. Intentional change — Update baseline via panel (“Baseline” button) or pnpm --filter storybook visual:update
  4. Unintentional change — Fix the regression before merging

The panel shows:

  • KPI bar: Pass Rate, Passed, Failed, Figma count, Regression count, Average Diff %
  • Results table: story name, viewport, comparison type, diff %, commit hash
  • Actions: View screenshot, view Figma golden, view diff overlay, promote baseline, copy CLI command
DirectoryPurposeGit tracked
__figma_baselines__/Figma golden imagesYes
__visual_snapshots__/Regression baselinesYes
__visual_diffs__/Diff overlaysNo
Terminal window
pnpm --filter storybook visual:test # Run all visual tests
pnpm --filter storybook visual:test -- --story {id} # Test specific story
pnpm --filter storybook visual:update # Update all baselines
pnpm --filter storybook visual:status # Check baseline status

Storybook is deployed to Cloudflare Pages (not Azure). The CI/CD pipeline builds Storybook in the CI stage and deploys via wrangler pages deploy in the CD stage.

EnvironmentURLHostingTrigger
DEVsavoy-dev-storybook.wycreative.comCloudflare Pages (savoy-storybook-dev)PR merge to deploy/dev
STAGEsavoy-stage-storybook.wycreative.comCloudflare PagesPR merge to deploy/stage

Storybook is NOT deployed to QA or PROD — internal development tool only.

Auth gate: DEV/STAGE Storybook is protected by the Dev Auth Gate. A Cloudflare Pages Function (apps/storybook/functions/_middleware.js) checks the __dev_gate cookie before serving content. Users must log in on the Next.js site first — the cookie is shared across *.wycreative.com.