Savvi Studio

Story Structure Reference

Last Updated: November 23, 2025

This document defines the standard structure and patterns for Storybook story files in the Savvi Studio project.


Table of Contents

  1. Basic Story Structure
  2. Meta Configuration
  3. Tags System
  4. Lifecycle Hooks
  5. Story-Level Overrides
  6. Best Practices

Basic Story Structure

Every story file should follow this standard structure:

import type { Meta, StoryObj } from '@storybook/react';
import { ComponentName } from './ComponentName';

const meta = {
  title: 'Category/SubCategory/ComponentName',
  component: ComponentName,
  parameters: {
    layout: 'padded', // or 'centered' | 'fullscreen'
  },
  tags: ['autodocs'],
} satisfies Meta<typeof ComponentName>;

export default meta;
type Story = StoryObj<typeof meta>;

// Stories
export const Default: Story = {
  args: {
    // component props
  },
};

export const Variant: Story = {
  args: {
    // different props
  },
};

Meta Configuration

Title Conventions

Use clear, hierarchical titles:

// ✅ Good - Clear hierarchy
title: 'Admin/Setup/TenantSetupForm'
title: 'Auth/LoginPage'
title: 'Common/Button'
title: 'Layout/Header'

// ❌ Avoid - Unclear hierarchy
title: 'TenantSetupForm'
title: 'Login'

Layout Options

parameters: {
  layout: 'padded',    // Default - adds padding around story
  layout: 'centered',  // Centers story in viewport
  layout: 'fullscreen' // No padding, full viewport
}

When to use each:

  • padded: Most components (forms, cards, tables)
  • centered: Small components (buttons, icons, badges)
  • fullscreen: Page-level components (layouts, pages)

Common Parameters

const meta = {
  component: MyComponent,
  parameters: {
    layout: 'padded',
    // Control documentation
    docs: {
      description: {
        component: 'A brief description of the component',
      },
    },
    // Design integration
    design: {
      type: 'figma',
      url: 'https://figma.com/...',
    },
  },
} satisfies Meta<typeof MyComponent>;

Tags System

Tags enable story organization, filtering, and special behaviors.

Standard Tags

const meta = {
  component: Button,
  tags: ['autodocs', 'stable'],
} satisfies Meta<typeof Button>;

Common tags:

  • autodocs - Auto-generate documentation
  • stable - Production-ready component
  • experimental - Work in progress
  • deprecated - Legacy component

Story-Level Tags

Override or extend component-level tags:

// Hide from dev sidebar
export const Experimental: Story = {
  tags: ['experimental', '!dev'],
};

// Mark as deprecated
export const OldPattern: Story = {
  tags: ['deprecated'],
};

Tag Configuration

Configure custom tags in .storybook/main.ts:

export default {
  tags: {
    experimental: {
      title: 'Experimental',
      description: 'Features under development',
      defaultFilterSelection: 'exclude',
    },
    stable: {
      title: 'Stable',
      description: 'Production-ready components',
    },
    deprecated: {
      title: 'Deprecated',
      description: 'Legacy components to be removed',
      defaultFilterSelection: 'exclude',
    },
  },
};

Lifecycle Hooks

Lifecycle hooks enable setup and teardown for stories.

Global Hooks

Configure in .storybook/preview.tsx:

import { Preview } from '@storybook/react';
import MockDate from 'mockdate';

const preview: Preview = {
  async beforeEach() {
    // Setup before each story
    MockDate.set('2024-01-01T00:00:00Z');
    localStorage.clear();
    
    // Return cleanup function
    return () => {
      MockDate.reset();
      localStorage.clear();
    };
  },
};

export default preview;

Story-Level Hooks

Override global hooks or add story-specific setup:

export const WithLocalStorage: Story = {
  async beforeEach() {
    // Story-specific setup
    localStorage.setItem('user', JSON.stringify({ id: '123' }));
    
    // Cleanup
    return () => {
      localStorage.removeItem('user');
    };
  },
};

Common Use Cases

Date/Time Mocking:

beforeEach() {
  MockDate.set('2024-01-01T12:00:00Z');
  return () => MockDate.reset();
}

localStorage/sessionStorage:

beforeEach() {
  localStorage.setItem('theme', 'dark');
  return () => localStorage.clear();
}

API Mocking State:

beforeEach() {
  // Reset MSW handlers to defaults
  return () => {
    // Cleanup handlers
  };
}

Story-Level Overrides

Override global settings for specific stories.

Background Override

export const OnDark: Story = {
  globals: {
    backgrounds: { value: 'dark' },
  },
};

export const OnLight: Story = {
  globals: {
    backgrounds: { value: 'light' },
  },
};

Theme Override

export const LightTheme: Story = {
  globals: {
    theme: 'light',
  },
};

export const DarkTheme: Story = {
  globals: {
    theme: 'dark',
  },
};

Viewport Override

export const Mobile: Story = {
  parameters: {
    viewport: {
      defaultViewport: 'mobile1',
    },
  },
};

export const Tablet: Story = {
  parameters: {
    viewport: {
      defaultViewport: 'tablet',
    },
  },
};

Best Practices

1. Consistent Naming

// ✅ Good - Clear, descriptive names
export const Default: Story = {};
export const WithError: Story = {};
export const Loading: Story = {};
export const Disabled: Story = {};

// ❌ Avoid - Vague names
export const Story1: Story = {};
export const Test: Story = {};
export const Example: Story = {};

2. Organize Stories Logically

// ✅ Good - Grouped by state/variant
export const Default: Story = {};
export const WithData: Story = {};
export const Loading: Story = {};
export const Error: Story = {};

// States
export const Disabled: Story = {};
export const ReadOnly: Story = {};

// Variants
export const Primary: Story = {};
export const Secondary: Story = {};

3. Use Args Composition

// ✅ Good - Reuse args
const baseArgs = {
  label: 'Submit',
  onClick: fn(),
};

export const Primary: Story = {
  args: {
    ...baseArgs,
    variant: 'primary',
  },
};

export const Secondary: Story = {
  args: {
    ...baseArgs,
    variant: 'secondary',
  },
};

4. Document Complex Props

const meta = {
  component: DataTable,
  argTypes: {
    data: {
      description: 'Array of row objects to display',
      table: {
        type: { summary: 'Array<Record<string, unknown>>' },
      },
    },
    onRowClick: {
      description: 'Callback fired when a row is clicked',
      table: {
        type: { summary: '(row: T) => void' },
      },
    },
  },
} satisfies Meta<typeof DataTable>;

5. Provide Realistic Data

// ✅ Good - Realistic mock data
export const WithUsers: Story = {
  args: {
    users: [
      { id: '1', name: 'Alice Johnson', email: 'alice@example.com' },
      { id: '2', name: 'Bob Smith', email: 'bob@example.com' },
    ],
  },
};

// ❌ Avoid - Minimal/unrealistic data
export const WithUsers: Story = {
  args: {
    users: [{ id: '1', name: 'A' }],
  },
};


For task-specific implementation details, see the Storybook integration task files in docs/storybook-integration/.