Storybook Component Workflow
Created: October 12, 2025
Purpose: Complete workflow for developing, documenting, and consuming components in our monorepo
Status: Production-ready reference guide
Version notice (2025-10-13): Tailwind is standardized at v3.4.10 across apps and Storybook. Use CommonJS
tailwind.config.jswithdarkMode: 'class'andcontent: { relative: true, files: [...] }. PostCSS should use{ tailwindcss: {}, autoprefixer: {} }and the CSS entry must include@tailwind base; @tailwind components; @tailwind utilities;(do not use@import "tailwindcss"or@source). Storybook’s PostCSS must explicitly point to its Tailwind config. Seedocs/tailwind-parity-checklist.md.
Table of Contents
Section titled “Table of Contents”- Overview
- Workflow: Component → Storybook → App
- Creating a New Component
- Adding Components to Storybook
- Using Components in Apps
- Best Practices
- Troubleshooting
- Quick Start Checklist
- PR Workflow & CI
- Static Publish (Optional)
Overview
Section titled “Overview”Our monorepo has a three-tier component architecture:
Component Libraries (packages/ui-*) ↓ Storybook (packages/storybook) ↓ Applications (apps/*)Component Library Structure
Section titled “Component Library Structure”packages/├── ui/ # Legacy/base components (being phased out)├── ui-web/ # Generic cross-division web components├── ui-stay-match/ # Stay Match division-specific components├── ui-guestroom/ # Guestroom division-specific components├── ui-bonjour-services/ # Bonjour Services division-specific components└── ... (other divisions)Philosophy
Section titled “Philosophy”- Develop in isolation - Build components in Storybook first
- Document as you build - Stories = documentation + examples
- Test interactively - Use Storybook controls and actions
- Import with confidence - Well-tested components ready for production
Quick Start Checklist
Section titled “Quick Start Checklist”Use this when adding a new component end-to-end.
- Create component in the right package
- Generic web →
packages/ui-web - Division-specific →
packages/ui-{division}(e.g.,ui-stay-match)
- Export it from the package index
- Update
packages/<pkg>/src/index.ts
- Add Storybook story
- Create
packages/storybook/stories/<Component>.stories.tsx - Use argTypes and multiple variants
- Run Storybook and verify
- Start:
yarn nx storybook storybook - Check: dark mode, controls, actions, responsive, a11y
- Use in an app and build
- Import in app code
- Build:
yarn nx build <app-name>
- Open a PR
- Include screenshots/GIF from Storybook
- Link to story path (e.g., ui-web/Card)
Workflow: Component → Storybook → App
Section titled “Workflow: Component → Storybook → App”High-Level Flow
Section titled “High-Level Flow”graph LR A[Create Component] --> B[Write TypeScript/JSX] B --> C[Export from Package] C --> D[Create Story] D --> E[Test in Storybook] E --> F{Works?} F -->|No| B F -->|Yes| G[Import in App] G --> H[Use in Production]Detailed Steps
Section titled “Detailed Steps”- Create Component in appropriate package
- Export from package index
- Create Story in Storybook
- Develop & Test in Storybook
- Import into app
- Deploy to production
Creating a New Component
Section titled “Creating a New Component”Step 1: Choose the Right Package
Section titled “Step 1: Choose the Right Package”Decision tree:
Is it web-only?├─ Yes: Is it division-specific?│ ├─ Yes → packages/ui-{division}/│ └─ No → packages/ui-web/└─ No: Is it cross-platform? └─ Yes → Create in appropriate package with .native.tsx variantExamples:
- Generic Button for all web apps →
packages/ui-web/ - Stay Match Hero component →
packages/ui-stay-match/ - Guestroom booking form →
packages/ui-guestroom/
Step 2: Create Component File Structure
Section titled “Step 2: Create Component File Structure”# Example: Creating a Card component in ui-webcd /Users/work-station/company/cloudalt-frontend
# Navigate to the packagecd packages/ui-web/src/components
# Create component directorymkdir Cardcd Card
# Create filestouch Card.tsxtouch Card.test.tsx # Optional but recommendedtouch index.tstouch README.md # Optional but helpfulStep 3: Write the Component
Section titled “Step 3: Write the Component”Template: Card.tsx
/** * Card Component * * A container component with optional header, footer, and actions. * * Features: * - Multiple variants (elevated, outlined, filled) * - Optional header and footer * - Clickable with hover effects * - Dark mode support */
import React from 'react';import { tv, type VariantProps } from 'tailwind-variants';import { twMerge } from 'tailwind-merge';
const card = tv({ base: 'rounded-lg transition-all duration-200', variants: { variant: { elevated: 'bg-white dark:bg-gray-800 shadow-md hover:shadow-lg', outlined: 'bg-transparent border-2 border-gray-300 dark:border-gray-600', filled: 'bg-gray-100 dark:bg-gray-700', }, padding: { none: 'p-0', sm: 'p-4', md: 'p-6', lg: 'p-8', }, clickable: { true: 'cursor-pointer hover:scale-[1.02]', false: '', }, }, defaultVariants: { variant: 'elevated', padding: 'md', clickable: false, },});
export type CardVariants = VariantProps<typeof card>;
export interface CardProps extends CardVariants { /** Card content */ children: React.ReactNode; /** Optional header content */ header?: React.ReactNode; /** Optional footer content */ footer?: React.ReactNode; /** Click handler (makes card clickable) */ onClick?: () => void; /** Custom class names */ className?: string;}
export const Card: React.FC<CardProps> = ({ children, header, footer, variant, padding, clickable, onClick, className,}) => { const isClickable = clickable || !!onClick;
return ( <div className={card({ variant, padding, clickable: isClickable, className: twMerge(className) })} onClick={onClick} role={isClickable ? 'button' : undefined} tabIndex={isClickable ? 0 : undefined} > {header && ( <div className="border-b border-gray-200 dark:border-gray-700 pb-4 mb-4"> {header} </div> )} <div>{children}</div> {footer && ( <div className="border-t border-gray-200 dark:border-gray-700 pt-4 mt-4"> {footer} </div> )} </div> );};Export: index.ts
export { Card } from './Card';export type { CardProps, CardVariants } from './Card';Step 4: Export from Package
Section titled “Step 4: Export from Package”Edit packages/ui-web/src/index.ts:
// Existing exportsexport * from './components/Hero';export * from './components/Button';
// Add new componentexport * from './components/Card';
export * from './types';Step 5: Verify Component Compiles
Section titled “Step 5: Verify Component Compiles”cd /Users/work-station/company/cloudalt-frontendyarn nx build ui-webAdding Components to Storybook
Section titled “Adding Components to Storybook”Step 1: Create Story File
Section titled “Step 1: Create Story File”cd /Users/work-station/company/cloudalt-frontend/packages/storybook/stories
# Option 1: Top-level storytouch Card.stories.tsx
# Option 2: Organize in subdirectorymkdir -p Componentstouch Components/Card.stories.tsxStep 2: Write the Story
Section titled “Step 2: Write the Story”Template: Card.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';import { Card } from '@cloudalt-frontend/ui-web';import React from 'react';
const meta: Meta<typeof Card> = { title: 'ui-web/Card', component: Card, parameters: { layout: 'centered', }, tags: ['autodocs'], argTypes: { onClick: { action: 'clicked' }, variant: { control: 'select', options: ['elevated', 'outlined', 'filled'], }, padding: { control: 'select', options: ['none', 'sm', 'md', 'lg'], }, },};
export default meta;type Story = StoryObj<typeof Card>;
// Basic storyexport const Default: Story = { args: { children: ( <div> <h3 className="text-xl font-bold mb-2">Card Title</h3> <p className="text-gray-600 dark:text-gray-400"> This is a basic card component with some content. </p> </div> ), },};
// With header and footerexport const WithHeaderAndFooter: Story = { args: { header: <h2 className="text-lg font-semibold">Card Header</h2>, children: ( <p className="text-gray-600 dark:text-gray-400"> Card content goes here. This card has both a header and footer. </p> ), footer: ( <div className="flex gap-2"> <button className="px-4 py-2 bg-primary-500 text-white rounded"> Action </button> <button className="px-4 py-2 bg-gray-200 text-gray-700 rounded"> Cancel </button> </div> ), },};
// Variantsexport const Elevated: Story = { args: { variant: 'elevated', children: <p>Elevated card with shadow</p>, },};
export const Outlined: Story = { args: { variant: 'outlined', children: <p>Outlined card with border</p>, },};
export const Filled: Story = { args: { variant: 'filled', children: <p>Filled card with background</p>, },};
// Clickableexport const Clickable: Story = { args: { clickable: true, children: ( <div> <h3 className="text-xl font-bold mb-2">Clickable Card</h3> <p className="text-gray-600 dark:text-gray-400"> Click me! I have hover effects. </p> </div> ), },};
// Padding variantsexport const NoPadding: Story = { args: { padding: 'none', children: ( <img src="https://via.placeholder.com/400x300" alt="Card content" className="w-full" /> ), },};Step 3: Start Storybook
Section titled “Step 3: Start Storybook”cd /Users/work-station/company/cloudalt-frontendyarn nx storybook storybookAccess: http://localhost:6006/
Note: The Storybook project name is storybook (see packages/storybook/project.json). Nx targets available:
yarn nx storybook storybook(dev)yarn nx build-storybook storybook(static export)
Step 4: Test Your Story
Section titled “Step 4: Test Your Story”Checklist:
- Component renders without errors
- All variants display correctly
- Controls panel updates props in real-time
- Actions panel logs events (clicks, etc.)
- Dark mode toggle works
- Responsive in different viewport sizes
- TypeScript types are correct (no red squiggles)
Step 5: Iterate and Refine
Section titled “Step 5: Iterate and Refine”If issues are found:
- Fix component in
packages/ui-web/src/components/Card/Card.tsx - Storybook will hot-reload automatically
- Re-test
- Repeat until perfect
Using Components in Apps
Section titled “Using Components in Apps”Step 1: Verify Component is Exported
Section titled “Step 1: Verify Component is Exported”# Check the package exportscat packages/ui-web/src/index.ts
# Should include:# export * from './components/Card';Step 2: Import in App
Section titled “Step 2: Import in App”Example: Using Card in apps/stay_match/pinkguest/web/src/App.tsx
import React from 'react';import { Card, Button } from '@cloudalt-frontend/ui-web';
function App() { return ( <div className="p-8"> <Card variant="elevated" header={<h1 className="text-2xl font-bold">Welcome to PinkGuest</h1>} footer={ <Button color="primary" onClick={() => console.log('Get Started!')}> Get Started </Button> } > <p className="text-gray-600 dark:text-gray-400"> Find your perfect stay with PinkGuest - the LGBTQ+ friendly accommodation platform. </p> </Card> </div> );}
export default App;Step 3: Verify Imports Work
Section titled “Step 3: Verify Imports Work”cd /Users/work-station/company/cloudalt-frontend
# Development serveryarn nx serve pinkguest-web
# Or production buildyarn nx build pinkguest-webStep 4: Deploy
Section titled “Step 4: Deploy”Once verified, deploy as normal through your CI/CD pipeline.
Best Practices
Section titled “Best Practices”Component Development
Section titled “Component Development”- Use TypeScript - Full type safety for props
- Use tailwind-variants - Type-safe variants
- Support dark mode - Include
dark:classes - Document thoroughly - JSDoc comments on all exports
- Test edge cases - Empty states, loading states, errors
- Follow naming conventions - PascalCase for components
- Export types - Export both component and prop types
- Use semantic HTML - Proper accessibility
❌ DON’T
Section titled “❌ DON’T”- Inline large JSX - Extract to separate components
- Hardcode values - Use props or design tokens
- Skip TypeScript - No
anytypes - Forget dark mode - Must work in both themes
- Over-engineer - Keep it simple and focused
- Create dependencies - Components should be independent
Storybook Stories
Section titled “Storybook Stories”- Create multiple stories - Show all variants
- Use meaningful names -
Default,WithHeader,Clickable - Add argTypes - Enable interactive controls
- Add actions - Log events for testing
- Test edge cases - Empty, loading, error states
- Use real-ish data - Not just “Lorem ipsum”
- Document props - Use JSDoc or MDX docs
❌ DON’T
Section titled “❌ DON’T”- One story only - Show multiple use cases
- Skip controls - Make stories interactive
- Use production data - Mock data only
- Forget dark mode - Test in both themes
- Ignore accessibility - Use a11y addon
Package Organization
Section titled “Package Organization”Generic Components → ui-web
Section titled “Generic Components → ui-web”When to use:
- Component is useful across multiple divisions
- No division-specific branding
- Reusable patterns (buttons, inputs, cards)
Examples:
- Button, Input, Select, Checkbox
- Card, Modal, Dialog, Drawer
- Layout components (Container, Grid, Flex)
Division-Specific → ui-{division}
Section titled “Division-Specific → ui-{division}”When to use:
- Component has division-specific branding
- Uses division-specific assets
- Custom business logic for that division
Examples:
- Stay Match Hero with specific imagery
- Guestroom booking flow components
- Bonjour Services dashboard widgets
Troubleshooting
Section titled “Troubleshooting”Component Not Found in Storybook
Section titled “Component Not Found in Storybook”Problem: Import error in story file
Solution:
# 1. Verify component is exportedcat packages/ui-web/src/index.ts
# 2. Rebuild the packageyarn nx build ui-web
# 3. Restart Storybook# Kill the process (Ctrl+C)yarn nx storybook storybookTypes Not Working
Section titled “Types Not Working”Problem: TypeScript errors or no autocomplete
Solution:
# Rebuild TypeScript declarationsyarn nx build ui-web
# Restart VS Code TypeScript server# CMD+Shift+P → "TypeScript: Restart TS Server"Styles Not Applying
Section titled “Styles Not Applying”Problem: Tailwind classes not working
Checklist:
- Is component path in
tailwind.config.jscontent array? - Is Tailwind CSS imported in preview.ts?
- Did you use standard Tailwind classes (not custom)?
- Is dark mode class on parent element?
Fix for missing content path:
export default { presets: [require('../config/tailwind-preset.js')], content: [ './stories/**/*.{js,jsx,ts,tsx,mdx}', '../ui-web/src/**/*.{js,jsx,ts,tsx}', '../ui-stay-match/src/**/*.{js,jsx,ts,tsx}', // Add your new package here '../ui-guestroom/src/**/*.{js,jsx,ts,tsx}', ],};Tip: if you add a new UI package (e.g., ui-pride-city), append its src glob to the content array to ensure Tailwind picks up its classes in Storybook.
Story Shows Error
Section titled “Story Shows Error”Problem: Component renders with error in Storybook
Debug steps:
- Check browser console for errors
- Verify all required props are provided in story args
- Check component implementation for bugs
- Verify imports are correct
- Try simplifying the story to minimal example
Hot Reload Not Working
Section titled “Hot Reload Not Working”Problem: Changes not reflected in Storybook
Solutions:
# Clear Storybook cacherm -rf node_modules/.cache/storybook
# Clear Nx cacheyarn nx reset
# Restart Storybookyarn nx storybook storybookImport Not Working in App
Section titled “Import Not Working in App”Problem: Can’t import component in app
Checklist:
- Component exported from package index?
- Package built successfully?
- App has package as dependency? (Should be automatic in monorepo)
- TypeScript can find the types?
Verify:
# Check if package buildsyarn nx build ui-web
# Check if app can buildyarn nx build pinkguest-webQuick Reference Commands
Section titled “Quick Reference Commands”Component Development
Section titled “Component Development”# Navigate to workspace rootcd /Users/work-station/company/cloudalt-frontend
# Create component (example)mkdir -p packages/ui-web/src/components/NewComponenttouch packages/ui-web/src/components/NewComponent/{index.ts,NewComponent.tsx}
# Build packageyarn nx build ui-webStorybook
Section titled “Storybook”# Start Storybook dev serveryarn nx storybook storybook
# Build static Storybookyarn nx build-storybook storybook
# Clear cacherm -rf node_modules/.cache/storybookApp Development
Section titled “App Development”# Run app dev serveryarn nx serve pinkguest-web
# Build app for productionyarn nx build pinkguest-web
# Test appyarn nx test pinkguest-webNx Utilities
Section titled “Nx Utilities”# See affected projectsyarn nx affected:apps
# Run command on affected projectsyarn nx affected --target=build
# Graph dependenciesyarn nx graphPR Workflow & CI
Section titled “PR Workflow & CI”Follow this lightweight process for component changes:
- Branching
- Create a feature branch from your current base (e.g.,
feat/<component-name>)
- Local verification
- Run Storybook and validate all variants
- Add or update stories as needed
- Commit hygiene
- Commit component files + stories together
- Include screenshots/GIFs in PR description
- CI expectations
- Static build of Storybook:
yarn nx build-storybook storybook - App build (at least one consumer app):
yarn nx build <app-name>
- Review
- Link to Storybook story paths in PR (e.g., ui-web/Card)
- Address feedback with follow-up commits
- Merge & cleanup
- Squash & merge if appropriate
- Delete feature branch after merge
Static Publish (Optional)
Section titled “Static Publish (Optional)”Generate a static Storybook for sharing or hosting:
yarn nx build-storybook storybookYou can host dist/storybook on any static hosting (e.g., GitHub Pages, Netlify, Vercel, S3). Add a CI step to publish on merges to a main branch if desired.
Example: Complete Workflow
Section titled “Example: Complete Workflow”Scenario: Create an Alert Component
Section titled “Scenario: Create an Alert Component”Step 1: Create component
cd /Users/work-station/company/cloudalt-frontendmkdir -p packages/ui-web/src/components/Alertcd packages/ui-web/src/components/AlertStep 2: Write component (Alert.tsx)
import React from 'react';import { tv, type VariantProps } from 'tailwind-variants';
const alert = tv({ base: 'p-4 rounded-lg border', variants: { variant: { info: 'bg-blue-50 border-blue-200 text-blue-800 dark:bg-blue-900 dark:border-blue-700 dark:text-blue-200', success: 'bg-green-50 border-green-200 text-green-800 dark:bg-green-900 dark:border-green-700 dark:text-green-200', warning: 'bg-yellow-50 border-yellow-200 text-yellow-800 dark:bg-yellow-900 dark:border-yellow-700 dark:text-yellow-200', error: 'bg-red-50 border-red-200 text-red-800 dark:bg-red-900 dark:border-red-700 dark:text-red-200', }, }, defaultVariants: { variant: 'info', },});
export type AlertVariants = VariantProps<typeof alert>;
export interface AlertProps extends AlertVariants { children: React.ReactNode; title?: string; onClose?: () => void;}
export const Alert: React.FC<AlertProps> = ({ children, title, variant, onClose }) => { return ( <div className={alert({ variant })}> {title && <div className="font-bold mb-1">{title}</div>} <div>{children}</div> {onClose && ( <button onClick={onClose} className="ml-auto">✕</button> )} </div> );};Step 3: Export component
export { Alert } from './Alert';export type { AlertProps, AlertVariants } from './Alert';
// packages/ui-web/src/index.tsexport * from './components/Alert';Step 4: Create story
import type { Meta, StoryObj } from '@storybook/react';import { Alert } from '@cloudalt-frontend/ui-web';
const meta: Meta<typeof Alert> = { title: 'ui-web/Alert', component: Alert, tags: ['autodocs'],};
export default meta;type Story = StoryObj<typeof Alert>;
export const Info: Story = { args: { variant: 'info', title: 'Information', children: 'This is an info alert.', },};
export const Success: Story = { args: { variant: 'success', title: 'Success!', children: 'Your changes have been saved.', },};
export const Warning: Story = { args: { variant: 'warning', title: 'Warning', children: 'Please review before proceeding.', },};
export const Error: Story = { args: { variant: 'error', title: 'Error', children: 'Something went wrong.', },};Step 5: Test in Storybook
yarn nx storybook storybook# Open http://localhost:6006/# Navigate to ui-web/Alert# Test all variantsStep 6: Use in app
import { Alert } from '@cloudalt-frontend/ui-web';
function Dashboard() { return ( <div className="p-8"> <Alert variant="success" title="Welcome back!"> You have 3 new messages. </Alert> </div> );}Done! ✅
Summary
Section titled “Summary”This workflow ensures:
- ✅ Consistent development - Same process for all components
- ✅ Quality components - Tested in isolation before production
- ✅ Living documentation - Storybook as single source of truth
- ✅ Fast development - Reuse proven components
- ✅ Type safety - Full TypeScript support
- ✅ Dark mode support - Built-in from the start
Remember: Component → Storybook → App. Always develop in isolation first!
Document: STORYBOOK_WORKFLOW.md
Last Updated: October 12, 2025
Maintained by: CloudAlt Engineering Team