Skip to content

Tailwind Integration Handoff Document

Date: October 11, 2025
Branch: feat/tailwind-integration
Session Summary: Complete Tailwind v4 integration with ecosystem libraries, generic and division-specific component packages, two working web apps, and comprehensive documentation.

Version notice (2025-10-13): The active workspace standard is Tailwind CSS v3.4.10. Any mentions of Tailwind v4 in this historical document (e.g., @tailwindcss/postcss, @import "tailwindcss") should be ignored in favor of v3 patterns: CommonJS tailwind.config.js, darkMode: 'class', content: { relative: true, files: [...] }, PostCSS { tailwindcss: {}, autoprefixer: {} }, and CSS using @tailwind base; @tailwind components; @tailwind utilities;. See docs/tailwind-parity-checklist.md.


Successfully integrated Tailwind CSS v4 with a full ecosystem of supporting libraries. The large monolithic commit was refactored into 7 logical, focused commits for better reviewability.

feat(tooling): add app package policy enforcement
  • Created scripts/check-app-package-policies.mjs
  • Integrated into scripts/verify-repo.sh as AppPackagePolicy stage
  • Enforces centralized dependency management (no app-level deps/devDeps)
  • Files: 2 changed, 55 insertions
docs(tailwind): add comprehensive Tailwind v4 integration documentation
  • docs/tailwind-ecosystem-guide.md - Complete library usage guide
  • docs/TAILWIND_ECOSYSTEM_SUMMARY.md - Implementation summary
  • docs/tailwind-integration-guide.md - Step-by-step integration
  • docs/design-token-system.md - Design token architecture
  • docs/figma-tokens-studio-setup.md - Figma integration
  • docs/onboarding_guide.md - Updated with Web (Vite) specifics
  • docs/ANALYSIS_REPORT_2025-10-11.md - Technical analysis
  • Files: 7 changed, 2,452 insertions

Commit 5: 0c6d4924 - Bonjour Locker Integration

Section titled “Commit 5: 0c6d4924 - Bonjour Locker Integration”
feat(bonjour-locker-web): integrate Tailwind v4 with generic Hero and rotating taglines
  • PostCSS + Tailwind v4 configuration
  • Vite resolve aliases for React Native safeguards
  • Uses @cloudalt-frontend/ui-web (generic cross-division)
  • Custom useRotatingTagline hook with 5 privacy-focused taglines
  • Hero image: shared/assets/images/hero/hero-image.jpeg
  • Button showcase with all variants
  • Running at: http://localhost:4202
  • Files: 15 changed, 250 insertions

Commit 4: dab20284 - PinkGuest Integration

Section titled “Commit 4: dab20284 - PinkGuest Integration”
feat(pinkguest-web): integrate Tailwind v4 with Hero component
  • PostCSS + Tailwind v4 configuration
  • Extends packages/config/tailwind-preset.js
  • Vite resolve aliases (react-native → react-native-web)
  • optimizeDeps.exclude: [‘react-native’]
  • Uses @cloudalt-frontend/ui-stay-match (division-specific)
  • Shared assets directory structure created
  • Cleaned package.json per policy
  • Running at: http://localhost:4200
  • Files: 22 changed, 679 insertions

Commit 3: 0edb5288 - Division-Specific UI Package

Section titled “Commit 3: 0edb5288 - Division-Specific UI Package”
feat(ui-stay-match): create Stay Match division-specific web components
  • Created packages/ui-stay-match/
  • Hero with default gay couple beach image
  • Button with Stay Match brand styling
  • StayMatchNavigation component
  • TypeScript types and asset declarations
  • Purpose: Division-specific variants with customized defaults
  • Files: 15 changed, 743 insertions
feat(ui-web): create generic cross-division web component library
  • Created packages/ui-web/
  • Hero component with tailwind-merge for className merging
  • Button component with tailwind-variants (4 colors, 3 sizes, loading, disabled)
  • TypeScript types for component props
  • Added @cloudalt-frontend/ui-web path alias to tsconfig.base.json
  • Purpose: Generic components for prototyping and shared use across all divisions
  • Files: 12 changed, 607 insertions
feat(tailwind): add Tailwind v4 ecosystem dependencies and shared configuration
  • Tailwind CSS v4.1.14 with @tailwindcss/postcss plugin
  • Ecosystem libraries:
    • @headlessui/react (latest) - Unstyled, accessible UI components
    • tailwind-merge (latest) - Intelligent className merging
    • clsx (latest) - Conditional class construction
    • tailwind-variants (3.1.1) - Type-safe variant system
  • Shared preset: packages/config/tailwind-preset.js
  • Theme package updates: brands.ts, tokens.ts, types.ts
  • Files: 8 changed, 1,486 insertions
  1. PinkGuest Web App (localhost:4200)

    • Hero component rendering with default Stay Match beach image
    • Navigation with mobile toggle
    • Email signup form
    • Button showcase with all variants
    • Tailwind v4 styles applied correctly
    • Zero bundling issues
  2. Bonjour Locker Web App (localhost:4202)

    • Hero component with generic ui-web package
    • Rotating taglines (5 privacy-focused options)
    • Hero image from app-specific shared/assets
    • Button showcase demonstrating all variants
    • Clean separation from division-specific code
  3. Build System

    • Vite HMR working perfectly
    • React Native code excluded from web bundles
    • No type errors
    • Policy enforcement active

Issue: PinkGuest Hero component not displaying
Root Cause: ui-stay-match Hero had default image file but wasn’t using it as fallback
Solution Applied:

  • Added import: import defaultHeroImage from './hero-13-gay-couple-visiting-beach.jpeg';
  • Updated backgroundUrl logic to use default when no prop provided
  • Asset type declarations already in place

Files Modified:

  • packages/ui-stay-match/src/components/Hero/Hero.tsx

Status: Working in dev server via HMR, needs to be committed

  1. Commit Recent Fix

    • Stage and commit the ui-stay-match Hero default image fix
    • Should be commit 8 in the sequence
  2. Production Build Verification

    • Run yarn nx build pinkguest-web
    • Run yarn nx build bonjour_locker-web
    • Verify no React Native code in bundles
    • Check bundle sizes reasonable
    • Confirm TypeScript compilation succeeds
  3. Push to Remote

    • git push origin feat/tailwind-integration
    • All 8 commits (7 existing + 1 new fix)
  4. Create Pull Request

    • Reference commit messages
    • Highlight architecture decisions
    • Note breaking changes (new package structure)
  5. Replicate to Other Brands

    • OrangeGuest web app
    • PurpleGuest web app
    • RainbowHost web app
    • Other Stay Match division brands
  6. 🆕 Storybook Integration (Next Major Milestone - See Chapter 2)

    • Initialize Storybook package with React-Vite
    • Configure Tailwind v4 + PostCSS
    • Add Vite resolve aliases
    • Write component stories (Hero, Button)
    • Estimated Time: 2-3 hours

Generic Components (ui-web):

  • packages/ui-web/src/components/Hero/Hero.tsx
  • packages/ui-web/src/components/Button/Button.tsx
  • packages/ui-web/src/types/index.ts

Division-Specific Components (ui-stay-match):

  • packages/ui-stay-match/src/components/Hero/Hero.tsx
  • packages/ui-stay-match/src/components/Button/Button.tsx
  • packages/ui-stay-match/src/components/Hero/hero-13-gay-couple-visiting-beach.jpeg

Web Apps:

  • apps/stay_match/pinkguest/web/ - Port 4200, uses ui-stay-match
  • apps/bonjour_services/bonjour_locker/web/ - Port 4202, uses ui-web

Configuration:

  • packages/config/tailwind-preset.js - Shared Tailwind preset
  • packages/theme/src/ - Design tokens and brand definitions

Documentation:

  • docs/tailwind-ecosystem-guide.md - Main integration guide
  • docs/onboarding_guide.md - Developer onboarding (updated)
  • docs/TAILWIND_ECOSYSTEM_SUMMARY.md - Implementation summary

Scripts:

  • scripts/check-app-package-policies.mjs - Policy enforcement
  • scripts/verify-repo.sh - CI/verification pipeline

With Tailwind components established, Storybook becomes essential for:

  1. Component Documentation - Living examples of Hero, Button, and future components
  2. Variant Testing - Visualize all Button colors/sizes, Hero variants in isolation
  3. Design System Management - Maintain consistency across divisions
  4. Developer Experience - Develop components without running full apps
  5. Cross-Division Visibility - Teams see ui-web vs ui-stay-match differences
  6. Preventing Chaos - Without a component manager, we’ll quickly lose control

Installed Dependencies (already in root package.json):

  • @nx/storybook: 20.8.0
  • @storybook/addon-essentials: ^8.6.14
  • @storybook/addon-interactions: ^8.6.14
  • @storybook/addon-onboarding: ^8.6.14
  • @storybook/blocks: ^8.6.14
  • @storybook/react: ^8.6.14
  • @storybook/react-vite: ^8.6.14
  • @storybook/test: ^8.6.14
  • storybook: ^8.6.14

Existing Package: packages/storybook/ (minimal, only package.json)

Section titled “Option A: Single Monorepo Storybook (RECOMMENDED)”
packages/storybook/
├── .storybook/
│ ├── main.ts # Stories from all ui-* packages
│ ├── preview.ts # Tailwind CSS, global decorators
│ └── manager.ts # Storybook UI customization
├── stories/
│ ├── Introduction.mdx # Architecture guide
│ └── Tailwind.mdx # Tailwind usage guide
└── project.json # Nx targets (storybook, build-storybook)

Pros:

  • Single source of truth for all components
  • Easy to compare ui-web vs ui-stay-match
  • Shared Tailwind configuration
  • One dev server to manage

Cons:

  • Slightly longer load times (all packages at once)
packages/ui-web/.storybook/
packages/ui-stay-match/.storybook/
packages/ui-bonjour-services/.storybook/ (future)

Pros:

  • Faster isolated development
  • Package-specific customization

Cons:

  • Harder to compare across packages
  • Multiple dev servers
  • Configuration duplication
  1. Hero.stories.tsx

    • Default variant
    • With/without navigation
    • Dark mode toggle
    • Custom className override demo
    • Custom background image
    • Email form interaction
  2. Button.stories.tsx

    • All color variants (primary, secondary, outline, ghost)
    • All size variants (sm, md, lg)
    • Loading state
    • Disabled state
    • Full width variant
    • With icons (future)
  1. Hero.stories.tsx

    • Default with Stay Match beach image
    • PinkGuest brand styling
    • OrangeGuest colors (future)
    • Custom image override
  2. Button.stories.tsx

    • Stay Match brand colors
    • Size variants
    • States demonstration
import type { StorybookConfig } from '@storybook/react-vite';
const config: StorybookConfig = {
stories: [
'../stories/**/*.mdx',
'../stories/**/*.stories.@(js|jsx|ts|tsx)',
'../../ui-web/src/**/*.stories.@(js|jsx|ts|tsx)',
'../../ui-stay-match/src/**/*.stories.@(js|jsx|ts|tsx)',
],
addons: [
'@storybook/addon-essentials',
'@storybook/addon-interactions',
],
framework: {
name: '@storybook/react-vite',
options: {},
},
viteFinal: async (config) => {
// Add Tailwind v4 PostCSS plugin
// Add resolve aliases for React Native
// Add optimizeDeps configuration
return config;
},
};
export default config;
import type { Preview } from '@storybook/react';
import '../styles/tailwind.css'; // Import Tailwind v4
const preview: Preview = {
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
},
globalTypes: {
darkMode: {
description: 'Toggle dark mode',
defaultValue: 'light',
toolbar: {
title: 'Dark Mode',
icon: 'circle',
items: ['light', 'dark'],
},
},
},
};
export default preview;

Estimated Time: 2-3 hours for complete setup

Step 1: Initialize Storybook Package (~15 min)

Section titled “Step 1: Initialize Storybook Package (~15 min)”
Terminal window
cd packages/storybook
# Option A: Manual initialization
npx storybook@latest init --type react_vite --skip-install
# Option B: Use existing Storybook CLI
yarn storybook init

Expected Output:

  • .storybook/main.ts - Main configuration
  • .storybook/preview.ts - Global decorators and parameters
  • stories/ - Example stories (can delete)

Step 2: Configure Nx Integration (~10 min)

Section titled “Step 2: Configure Nx Integration (~10 min)”

Create/update packages/storybook/project.json:

{
"name": "storybook",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "packages/storybook",
"projectType": "library",
"tags": ["type:tooling", "scope:shared"],
"targets": {
"storybook": {
"executor": "@nx/storybook:storybook",
"options": {
"port": 6006,
"configDir": "packages/storybook/.storybook"
},
"configurations": {
"ci": {
"quiet": true
}
}
},
"build-storybook": {
"executor": "@nx/storybook:build",
"outputs": ["{options.outputDir}"],
"options": {
"outputDir": "dist/storybook",
"configDir": "packages/storybook/.storybook"
},
"configurations": {
"ci": {
"quiet": true
}
}
}
}
}

Step 3: Configure Tailwind v4 Support (~15 min)

Section titled “Step 3: Configure Tailwind v4 Support (~15 min)”

A. Create Tailwind CSS file: packages/storybook/styles/tailwind.css

@import "tailwindcss";

B. Create PostCSS config: packages/storybook/.storybook/postcss.config.js

module.exports = {
plugins: {
'@tailwindcss/postcss': {},
autoprefixer: {},
},
};

C. Create Tailwind config: packages/storybook/.storybook/tailwind.config.js

const sharedPreset = require('../../../packages/config/tailwind-preset.js');
module.exports = {
presets: [sharedPreset],
content: [
'../stories/**/*.{js,jsx,ts,tsx,mdx}',
'../../ui-web/src/**/*.{js,jsx,ts,tsx}',
'../../ui-stay-match/src/**/*.{js,jsx,ts,tsx}',
],
};

packages/storybook/.storybook/main.ts:

import type { StorybookConfig } from '@storybook/react-vite';
import { mergeConfig } from 'vite';
const config: StorybookConfig = {
stories: [
'../stories/**/*.mdx',
'../stories/**/*.stories.@(js|jsx|ts|tsx)',
'../../ui-web/src/**/*.stories.@(js|jsx|ts|tsx)',
'../../ui-stay-match/src/**/*.stories.@(js|jsx|ts|tsx)',
],
addons: [
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'@storybook/addon-links',
],
framework: {
name: '@storybook/react-vite',
options: {},
},
docs: {
autodocs: 'tag',
},
viteFinal: async (config) => {
return mergeConfig(config, {
resolve: {
alias: {
// React Native safeguards (same as web apps)
'react-native$': 'react-native-web',
'react-native-svg': 'react-native-svg-web',
'@cloudalt-frontend/ui': '@cloudalt-frontend/ui/index.web',
},
},
optimizeDeps: {
exclude: ['react-native'],
},
css: {
postcss: './packages/storybook/.storybook/postcss.config.js',
},
});
},
};
export default config;

packages/storybook/.storybook/preview.ts:

import type { Preview } from '@storybook/react';
import '../styles/tailwind.css';
const preview: Preview = {
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
backgrounds: {
default: 'light',
values: [
{ name: 'light', value: '#ffffff' },
{ name: 'dark', value: '#1a1a1a' },
{ name: 'gray', value: '#f3f4f6' },
],
},
},
globalTypes: {
darkMode: {
description: 'Toggle dark mode',
defaultValue: 'light',
toolbar: {
title: 'Dark Mode',
icon: 'circlehollow',
items: [
{ value: 'light', icon: 'sun', title: 'Light' },
{ value: 'dark', icon: 'moon', title: 'Dark' },
],
dynamicTitle: true,
},
},
},
decorators: [
(Story, context) => {
const darkMode = context.globals.darkMode === 'dark';
return (
<div className={darkMode ? 'dark' : ''}>
<div className="min-h-screen bg-white dark:bg-gray-900">
<Story />
</div>
</div>
);
},
],
};
export default preview;

Step 6: Create Introduction Stories (~20 min)

Section titled “Step 6: Create Introduction Stories (~20 min)”

A. Architecture Introduction: packages/storybook/stories/Introduction.mdx

import { Meta } from '@storybook/blocks';
<Meta title="Introduction/Architecture" />
# CloudAlt Component Library
Welcome to the CloudAlt monorepo component library! This Storybook showcases
all web components built with Tailwind CSS v4 and the Tailwind ecosystem.
## Package Architecture
### Generic Components (`ui-web`)
Located in `packages/ui-web/`, these components are:
- **Cross-division**: Used by all divisions (Stay Match, Bonjour Services, etc.)
- **Prototyping-first**: No brand defaults, fully customizable
- **Headless styling**: Accept className prop with tailwind-merge support
**When to use**: Prototypes, shared tools, apps needing full customization
### Division-Specific Components (`ui-stay-match`)
Located in `packages/ui-stay-match/`, these components are:
- **Branded by default**: Stay Match colors, imagery, and styling
- **Quick to deploy**: Minimal props needed for brand-consistent apps
- **Override-friendly**: Still accept className overrides
**When to use**: Stay Match division apps (PinkGuest, OrangeGuest, etc.)
## Tailwind Ecosystem
We use four essential libraries:
1. **@headlessui/react** - Accessible, unstyled UI primitives
2. **tailwind-merge** - Intelligent className conflict resolution
3. **clsx** - Conditional className construction
4. **tailwind-variants** - Type-safe component variants
See the "Tailwind Ecosystem" section for detailed usage.

B. Tailwind Usage Guide: packages/storybook/stories/Tailwind.mdx

import { Meta } from '@storybook/blocks';
<Meta title="Introduction/Tailwind Ecosystem" />
# Tailwind Ecosystem Guide
## tailwind-merge
Use for components that accept className prop:
\`\`\`tsx
import { twMerge } from 'tailwind-merge';
function Card({ className, children }) {
return (
<div className={twMerge('p-4 rounded-lg bg-white', className)}>
{children}
</div>
);
}
// User can override: <Card className="p-8 bg-gray-100" />
// Result: p-8 rounded-lg bg-gray-100 (p-4 and bg-white replaced)
\`\`\`
## clsx
Use for conditional classes:
\`\`\`tsx
import clsx from 'clsx';
function Button({ variant, loading, children }) {
return (
<button className={clsx(
'px-4 py-2 rounded',
{
'bg-blue-500': variant === 'primary',
'bg-gray-500': variant === 'secondary',
'opacity-50 cursor-not-allowed': loading,
}
)}>
{children}
</button>
);
}
\`\`\`
## tailwind-variants
Use for type-safe variant systems:
\`\`\`tsx
import { tv } from 'tailwind-variants';
const button = tv({
base: 'font-semibold rounded transition-colors',
variants: {
color: {
primary: 'bg-pink-600 hover:bg-pink-700 text-white',
secondary: 'bg-gray-600 hover:bg-gray-700 text-white',
},
size: {
sm: 'text-sm px-3 py-1.5',
md: 'text-base px-4 py-2',
lg: 'text-lg px-6 py-3',
},
},
});
function Button({ color, size, children }) {
return <button className={button({ color, size })}>{children}</button>;
}
\`\`\`
## @headlessui/react
Use for accessible UI patterns:
\`\`\`tsx
import { Menu } from '@headlessui/react';
function Dropdown() {
return (
<Menu>
<Menu.Button className="px-4 py-2 bg-blue-500 text-white rounded">
Options
</Menu.Button>
<Menu.Items className="mt-2 bg-white shadow-lg rounded">
<Menu.Item>
{({ active }) => (
<a className={active ? 'bg-blue-100' : ''}>Account</a>
)}
</Menu.Item>
</Menu.Items>
</Menu>
);
}
\`\`\`

A. Generic Hero Stories: packages/ui-web/src/components/Hero/Hero.stories.tsx

import type { Meta, StoryObj } from '@storybook/react';
import { Hero } from './Hero';
const meta = {
title: 'Generic/Hero',
component: Hero,
parameters: {
layout: 'fullscreen',
},
tags: ['autodocs'],
} satisfies Meta<typeof Hero>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
title: 'Welcome to Our Platform',
description: 'Your journey starts here. Connect with amazing people around the world.',
ctaText: 'Get Started',
onCtaPress: () => console.log('CTA clicked'),
backgroundImage: 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4',
showNavigation: true,
},
};
export const WithoutNavigation: Story = {
args: {
...Default.args,
showNavigation: false,
},
};
export const DarkMode: Story = {
args: {
...Default.args,
darkMode: true,
},
};
export const WithSecondaryButton: Story = {
args: {
...Default.args,
secondaryCtaText: 'Learn More',
onSecondaryCtaPress: () => console.log('Secondary clicked'),
},
};
export const CustomBackground: Story = {
args: {
...Default.args,
backgroundImage: 'https://images.unsplash.com/photo-1469474968028-56623f02e42e',
title: 'Explore Nature',
description: 'Discover breathtaking destinations and create unforgettable memories.',
},
};

B. Generic Button Stories: packages/ui-web/src/components/Button/Button.stories.tsx

import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';
const meta = {
title: 'Generic/Button',
component: Button,
tags: ['autodocs'],
argTypes: {
color: {
control: 'select',
options: ['primary', 'secondary', 'outline', 'ghost'],
},
size: {
control: 'select',
options: ['sm', 'md', 'lg'],
},
},
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Primary: Story = {
args: {
color: 'primary',
children: 'Primary Button',
},
};
export const Secondary: Story = {
args: {
color: 'secondary',
children: 'Secondary Button',
},
};
export const Outline: Story = {
args: {
color: 'outline',
children: 'Outline Button',
},
};
export const Ghost: Story = {
args: {
color: 'ghost',
children: 'Ghost Button',
},
};
export const Small: Story = {
args: {
size: 'sm',
children: 'Small Button',
},
};
export const Large: Story = {
args: {
size: 'lg',
children: 'Large Button',
},
};
export const Loading: Story = {
args: {
loading: true,
children: 'Loading...',
},
};
export const Disabled: Story = {
args: {
disabled: true,
children: 'Disabled Button',
},
};
export const FullWidth: Story = {
args: {
fullWidth: true,
children: 'Full Width Button',
},
};
export const AllVariants: Story = {
render: () => (
<div className="space-y-4 p-6">
<h3 className="text-lg font-semibold mb-4">All Button Variants</h3>
<div>
<h4 className="text-sm font-medium mb-2">Colors</h4>
<div className="flex gap-2">
<Button color="primary">Primary</Button>
<Button color="secondary">Secondary</Button>
<Button color="outline">Outline</Button>
<Button color="ghost">Ghost</Button>
</div>
</div>
<div>
<h4 className="text-sm font-medium mb-2">Sizes</h4>
<div className="flex items-center gap-2">
<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
<Button size="lg">Large</Button>
</div>
</div>
<div>
<h4 className="text-sm font-medium mb-2">States</h4>
<div className="flex gap-2">
<Button loading>Loading</Button>
<Button disabled>Disabled</Button>
</div>
</div>
</div>
),
};

C. Stay Match Hero Stories: packages/ui-stay-match/src/components/Hero/Hero.stories.tsx

import type { Meta, StoryObj } from '@storybook/react';
import { Hero } from './Hero';
const meta = {
title: 'Stay Match/Hero',
component: Hero,
parameters: {
layout: 'fullscreen',
},
tags: ['autodocs'],
} satisfies Meta<typeof Hero>;
export default meta;
type Story = StoryObj<typeof meta>;
export const DefaultPinkGuest: Story = {
args: {
title: 'Welcome to PinkGuest',
description: 'Your inclusive community for LGBTQ+ friendly stays.',
ctaText: 'Get Started',
onCtaPress: () => console.log('CTA clicked'),
showNavigation: true,
// Uses default gay couple beach image
},
};
export const OrangeGuestBrand: Story = {
args: {
title: 'Welcome to OrangeGuest',
description: 'Discover unique stays in vibrant destinations.',
ctaText: 'Explore Now',
onCtaPress: () => console.log('CTA clicked'),
showNavigation: true,
},
};
export const WithCustomImage: Story = {
args: {
title: 'Custom Hero',
description: 'Override the default image with your own.',
ctaText: 'Learn More',
onCtaPress: () => console.log('CTA clicked'),
backgroundImage: 'https://images.unsplash.com/photo-1527856263669-12c3a0af2aa6',
},
};
Terminal window
# Start Storybook dev server
yarn nx storybook storybook
# Opens at http://localhost:6006
# Build static Storybook
yarn nx build-storybook storybook
# Outputs to dist/storybook
# Test stories render correctly
# - Check all Hero variants
# - Check all Button variants
# - Toggle dark mode
# - Verify Tailwind styles apply
# - Check no console errors

Step 9: Add to Verification Pipeline (~5 min)

Section titled “Step 9: Add to Verification Pipeline (~5 min)”

Update scripts/verify-repo.sh:

Terminal window
# Add after existing stages
echo "📚 Stage 12: Storybook Build"
if yarn nx build-storybook storybook > /dev/null 2>&1; then
echo " ✅ Storybook builds successfully"
else
echo " ❌ Storybook build failed"
exit 1
fi

Issue: Tailwind styles not applying
Fix: Verify postcss.config.js path in viteFinal(), restart Storybook

Issue: React Native imports causing errors
Fix: Check Vite resolve aliases match web app pattern

Issue: Stories not discovered
Fix: Verify stories glob pattern in main.ts includes correct paths

Issue: Dark mode toggle not working
Fix: Check decorator in preview.ts applies dark class to wrapper div

  • Chromatic Integration - Visual regression testing
  • Accessibility Testing - @storybook/addon-a11y
  • Figma Integration - storybook-addon-designs
  • Design Tokens Documentation - Stories for color, spacing, typography
  • Interaction Testing - @storybook/test with play functions
  • Mobile Preview - Viewport addon for responsive testing

Chapter 3: Architecture Decisions & Rationale

Section titled “Chapter 3: Architecture Decisions & Rationale”

Decision: Create separate ui-web (generic) and ui-\{division\} (specific) packages

Rationale:

  1. Prototyping Speed - Generic components allow quick app scaffolding
  2. Brand Identity - Division-specific packages maintain unique brand styling
  3. Code Reuse - Generic components shared across all divisions
  4. Flexibility - Apps choose appropriate package for their needs
  5. Clear Ownership - Division teams own their ui-{division} packages

When to Use Each:

  • ui-web: Cross-division prototypes, shared tooling, Bonjour Services apps
  • ui-stay-match: All Stay Match division apps (PinkGuest, OrangeGuest, etc.)
  • ui-{division}: Future division-specific packages as needed
Apps (pinkguest-web, bonjour-locker-web)
Division Packages (ui-stay-match) OR Generic (ui-web)
Shared Packages (config, theme)
Root Dependencies (tailwind, react, etc.)

Decision: Use Tailwind v4 with @tailwindcss/postcss plugin, not Tailwind CLI

Rationale:

  1. Vite Integration - Seamless integration with Vite’s PostCSS pipeline
  2. HMR Support - Instant style updates during development
  3. Build Optimization - Automatic purging and minification
  4. Future-Proof - Tailwind v4 is the modern approach
  5. Monorepo Friendly - Shared preset works across all apps

Configuration Pattern:

// postcss.config.js (per app)
module.exports = {
plugins: {
'@tailwindcss/postcss': {},
autoprefixer: {},
},
};
// tailwind.config.js (per app)
module.exports = {
presets: [require('../../packages/config/tailwind-preset.js')],
content: [
'./src/**/*.{js,ts,jsx,tsx}',
'../../packages/ui-web/src/**/*.{js,ts,jsx,tsx}',
],
};

Decision: Create packages/config/tailwind-preset.js extended by all apps

Rationale:

  1. Design Consistency - All apps use same design tokens
  2. Single Source of Truth - Update colors/spacing in one place
  3. Brand Variants - Preset includes all division brand colors
  4. Easy Updates - Change preset, all apps update
  5. Plugin Management - Shared plugins (forms, typography, etc.)

Decision: Install @headlessui/react, tailwind-merge, clsx, tailwind-variants

Rationale:

  1. @headlessui/react (Tier 1 Essential)

    • Unstyled, accessible components (Dropdown, Modal, Tabs)
    • WAI-ARIA compliant out of the box
    • Full keyboard navigation
    • Tailwind-first design
    • React Native parallel: React Navigation, custom modals
  2. tailwind-merge (Tier 1 Essential)

    • Intelligent className merging
    • Prevents conflicts (e.g., p-4 + p-6 = p-6, not both)
    • Critical for component libraries accepting className prop
    • React Native parallel: StyleSheet.compose (but smarter)
  3. clsx (Tier 1 Essential)

    • Conditional className construction
    • Clean syntax: clsx({ 'text-red': error, 'text-green': success })
    • Tiny bundle size (228 bytes)
    • React Native parallel: Inline style conditionals
  4. tailwind-variants (Tier 1 Essential)

    • Type-safe variant system
    • Compile-time safety for color/size props
    • IntelliSense support
    • Compound variants (size + color combinations)
    • React Native parallel: Custom variant hooks, but with types

Not Installed (Yet):

  • @tailwindcss/forms - Wait until forms package needed
  • @tailwindcss/typography - Wait until content/blog features
  • tailwindcss-animate - Wait until complex animations needed

Problem: React Native code bundling in web apps causes:

  • White page / blank screen
  • “window is not defined” errors
  • 10+ MB bundle sizes
  • Failed builds

Solution: Vite resolve aliases + optimizeDeps configuration

// vite.config.ts (every web app)
export default defineConfig({
resolve: {
alias: {
'react-native$': 'react-native-web',
'react-native-svg': 'react-native-svg-web',
'@cloudalt-frontend/ui': '@cloudalt-frontend/ui/index.web',
},
},
optimizeDeps: {
exclude: ['react-native'],
},
});

Why This Works:

  1. Alias Interception - Rewrites React Native imports to web equivalents
  2. Pre-bundling Prevention - exclude: [‘react-native’] stops Vite from pre-processing
  3. Selective Mapping - Only maps imports that could leak into web builds
  4. Zero Runtime Cost - Pure build-time transformation

Lessons Learned:

  • Must use $ in react-native$ to match exact import (not react-native-web itself)
  • Must exclude react-native from optimizeDeps (pre-bundling breaks it)
  • Must clean .nx/cache after adding aliases

Decision: All dependencies at monorepo root, no app package.json deps/devDeps

Rationale:

  1. Version Consistency - One version of React across all apps
  2. Dependency Deduplication - Smaller total node_modules
  3. Update Simplicity - Update once, affects all apps
  4. Build Performance - Shared deps cached by Nx
  5. Policy Enforcement - Automated via check-app-package-policies.mjs

Enforcement:

Terminal window
# Runs in CI and verify-repo.sh
node scripts/check-app-package-policies.mjs

Exceptions (None Currently):

  • App-specific dependencies currently not allowed
  • If needed, document in policy script

Design Decision: Flexible image prop supporting multiple formats

backgroundImage?: string | number | { uri: string };

Rationale:

  • string - Web URLs, imported assets
  • number - React Native require() format
  • { uri: string } - Remote images, dynamic URLs
  • Cross-platform compatibility

Division-Specific Difference:

  • ui-web: backgroundImage required (no default)
  • ui-stay-match: backgroundImage optional (has default beach image)

Design Decision: Use tailwind-variants with type-safe props

const button = tv({
base: 'font-semibold rounded transition-colors',
variants: {
color: {
primary: 'bg-pink-600 hover:bg-pink-700',
secondary: 'bg-gray-600 hover:bg-gray-700',
outline: 'border-2 border-pink-600 hover:bg-pink-50',
ghost: 'hover:bg-gray-100',
},
size: {
sm: 'text-sm px-3 py-1.5',
md: 'text-base px-4 py-2',
lg: 'text-lg px-6 py-3',
},
},
});

Rationale:

  • Type safety prevents invalid props
  • IntelliSense shows available variants
  • Easy to add new variants
  • Compound variants support future needs
  • Clear separation of concerns

Chapter 4: Unresolved Items & Next Actions

Section titled “Chapter 4: Unresolved Items & Next Actions”

Status: Code working via HMR, not committed
Files Changed: packages/ui-stay-match/src/components/Hero/Hero.tsx
Action Required:

Terminal window
git add packages/ui-stay-match/src/components/Hero/Hero.tsx
git commit -m "fix(ui-stay-match): add default hero image fallback
- Import hero-13-gay-couple-visiting-beach.jpeg as default
- Use default image when backgroundImage prop not provided
- Maintains division-specific branding for Stay Match apps"

Status: Not tested
Risk: High - builds could fail despite dev working
Action Required:

Terminal window
# Test both web apps
yarn nx build pinkguest-web
yarn nx build bonjour_locker-web
# Check output
ls -lh dist/apps/stay_match/pinkguest/web/
ls -lh dist/apps/bonjour_services/bonjour_locker/web/
# Verify no React Native in bundles
grep -r "react-native" dist/apps/stay_match/pinkguest/web/assets/
# Expected: No matches or only react-native-web references

Success Criteria:

  • Both builds complete without errors
  • Bundle sizes reasonable (< 1MB main chunk)
  • No React Native code in output
  • TypeScript compilation succeeds
  • All chunks have proper hashes

Status: Ready after build verification
Action Required:

Terminal window
git push origin feat/tailwind-integration
# Expected: 8 commits pushed
# 7 original + 1 hero fix

Status: Blocked by push
Content Required:

  • Title: feat: Integrate Tailwind v4 with ecosystem libraries and component packages
  • Link to TAILWIND_INTEGRATION_HANDOFF.md
  • Highlight breaking changes (new package structure)
  • Request reviews from division leads
  • Note Storybook is next milestone

Apps to Update:

  • apps/stay_match/orangeguest/web/
  • apps/stay_match/purpleguest/web/
  • apps/stay_match/rainbowhost/web/
  • apps/stay_match/staymatch_app/web/

Files to Create/Update Per App:

{app}/web/
├── postcss.config.js (copy from pinkguest)
├── tailwind.config.js (update content paths)
├── vite.config.ts (add aliases)
├── src/styles.css (add @tailwind directives)
├── src/app/app.tsx (import Hero from ui-stay-match)
└── project.json (verify serve/build targets)
{app}/shared/assets/images/hero/
└── hero-image.jpeg (brand-specific image)

Automation Opportunity: Create script to scaffold Tailwind setup for new apps

Future Packages to Create:

  • packages/ui-bonjour-services/ - For Bonjour division apps
  • packages/ui-guestroom/ - For Guestroom division apps
  • packages/ui-homestay/ - For Homestay division apps
  • etc.

When to Create: When division has 2+ web apps needing shared components

Pattern to Follow: Copy ui-stay-match structure, update branding

Not Yet Implemented:

  • Custom @theme directives
  • Component-level @utility
  • @layer organization
  • Advanced @variants

When Needed: When design system matures and requires custom utilities

Potential Improvements:

  • Analyze bundle sizes with @nx/rollup or vite-bundle-visualizer
  • Tree-shake unused Tailwind utilities (already automatic)
  • Split chunks for better caching
  • Lazy load Hero images

Measure First: Use Lighthouse, WebPageTest before optimizing

Current Status: No automated tests for components
Future Tests Needed:

  • Component unit tests (Vitest + Testing Library)
  • Visual regression tests (Chromatic + Storybook)
  • Accessibility tests (jest-axe)
  • E2E tests (Playwright for web apps)

Start With: Storybook interaction tests using @storybook/test

✅ White page issue - RESOLVED (Vite aliases)
✅ Hero not displaying - RESOLVED (default image import)
✅ Type errors - RESOLVED (proper declarations)
✅ Build failures - RESOLVED (optimizeDeps configuration)

  1. Storybook Architecture: ✅ Confirmed single monorepo Storybook (see Chapter 2 Step-by-Step guide)
  2. Component Priorities: Which components to add next after Hero/Button? (Form inputs, Cards, Modals?)
  3. Design Tokens: Should we formalize tokens in Storybook docs before or after Figma integration?
  4. Mobile Web: Any responsive design concerns for mobile web?
  5. Dark Mode: Implement dark mode system-wide now or wait for Storybook dark mode testing?
  6. Accessibility: Priority level for WCAG compliance? (Storybook a11y addon ready)
  7. Visual Regression: Use Chromatic (Storybook’s official tool) or alternatives?
  8. Component Scope: Should Storybook include React Native components or only web?

Note: The original development roadmap from earlier in the conversation is now beyond working memory. Key priorities have been extracted and documented here, but the full roadmap may need to be referenced separately.

Known Priorities from Roadmap:

  1. ✅ Tailwind v4 integration (COMPLETE)
  2. 🔄 Storybook integration (NEXT)
  3. ⏳ Component library expansion
  4. ⏳ Design system documentation
  5. ⏳ Testing infrastructure
  6. ⏳ CI/CD pipeline enhancements
Terminal window
# Development
yarn nx serve pinkguest-web # Port 4200
yarn nx serve bonjour_locker-web # Port 4202
# Building
yarn nx build pinkguest-web
yarn nx build bonjour_locker-web
# Testing
yarn nx test pinkguest-web
yarn nx lint pinkguest-web
# Verification
./scripts/verify-repo.sh
node scripts/check-app-package-policies.mjs
# 🆕 Storybook (after setup)
yarn nx storybook storybook # Port 6006
yarn nx build-storybook storybook # Static build to dist/storybook
yarn nx test-storybook storybook # Run interaction tests (future)
# Git
git status --short
git log --oneline -10
git push origin feat/tailwind-integration
{
"devDependencies": {
"@headlessui/react": "latest",
"tailwind-merge": "latest",
"clsx": "latest",
"tailwind-variants": "3.1.1",
"tailwindcss": "4.1.14",
"@tailwindcss/postcss": "4.1.14",
"autoprefixer": "10.4.21"
}
}
{
"compilerOptions": {
"paths": {
"@cloudalt-frontend/ui-web": ["packages/ui-web/src/index.ts"],
"@cloudalt-frontend/ui-stay-match": ["packages/ui-stay-match/src/index.ts"]
}
}
}

  • Read Chapter 1 for current status
  • Read Chapter 2 for Storybook plan
  • Review Chapter 3 for architectural context
  • Check Chapter 4 for immediate actions
  • Run git status to see uncommitted changes
  • Verify both dev servers still running (ports 4200, 4202)
  • Check if production builds pass
  • Read commit history: git log --oneline -10
  • All 7 (soon 8) commits on feat/tailwind-integration branch
  • PinkGuest showing Hero with beach image at localhost:4200
  • Bonjour Locker showing Hero with rotating taglines at localhost:4202
  • No console errors in browser
  • HMR working for both apps
  • Policy enforcement active (no app-level dependencies)
  1. packages/ui-web/src/components/Hero/Hero.tsx - Generic Hero
  2. packages/ui-stay-match/src/components/Hero/Hero.tsx - Division Hero (has uncommitted fix)
  3. packages/config/tailwind-preset.js - Shared preset
  4. apps/stay_match/pinkguest/web/vite.config.ts - Vite aliases pattern
  5. docs/tailwind-ecosystem-guide.md - Library usage guide

Total Work Completed: 7 commits, 2 working web apps, 2 component packages, comprehensive documentation
Next Milestone: Storybook integration
Estimated Next Session Duration: 2-3 hours for full Storybook setup
Branch Status: Ready to push after production build verification

Questions? Reference specific chapters above or check git commit messages for detailed changes.