Skip to content

Storybook

CruzJS ships with a fully configured Storybook for the @cruzjs/ui component library. Every component has stories covering all variants, sizes, states, and responsive viewports.

Start the development server with hot reload:

Terminal window
pnpm --filter @cruzjs/ui storybook

Storybook opens at http://localhost:6006.

Stories are organized by category in the sidebar:

CategoryComponentsExamples
Primitives11Alert, Avatar, Badge, Card, Spinner, Tooltip
Forms & Inputs20Input, Select, DatePicker, ColorPicker, RichTextEditor
Layout8AppShell, Sidebar, Navbar, PageShell, SettingsLayout
Overlays & Feedback11Modal, Drawer, CommandPalette, Notification
Navigation7Tabs, Accordion, Stepper, Pagination
Data Display17DataTable, Timeline, StatsGrid, Tree, DragAndDropList
Application Blocks22LoginBlock, OrgSwitcher, TeamRoster, PricingCards
Marketing Blocks14HeroSection, FeatureGrid, TestimonialCarousel, Footer
Documentation Blocks6DocSidebar, CodeBlock, ExamplePreview, Changelog

Test responsive behavior with built-in viewport presets:

PresetWidthHeight
Mobile375px812px
Tablet768px1024px
Desktop1280px800px

Select viewports from the toolbar to see how components adapt. Components that use useIsMobile() will switch to their mobile variants (bottom sheets, full-screen overlays, larger touch targets) when the Mobile viewport is active.

Switch between background presets to test component appearance on different surfaces:

BackgroundValue
Light#ffffff
Dark#1e293b
Subtle#f8fafc

The @storybook/addon-a11y addon is pre-configured. It runs automated WCAG accessibility checks on every story and surfaces violations in the a11y panel. Check this panel when building or modifying components to catch:

  • Missing ARIA roles or labels
  • Insufficient color contrast
  • Missing keyboard interaction
  • Focus order issues

Every story exposes component props via the Controls panel. Edit props interactively to explore all combinations of variants, sizes, and states without writing code.

Stories live next to their components:

packages/ui/src/components/
Modal/
Modal.tsx
Modal.stories.tsx
Modal.test.tsx
Button/
Button.tsx
Button.stories.tsx
Button.test.tsx

If you build custom components in your application, you can add stories for them following the same pattern.

src/components/ProjectCard/ProjectCard.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { ProjectCard } from './ProjectCard';
const meta: Meta<typeof ProjectCard> = {
title: 'Application/ProjectCard',
component: ProjectCard,
tags: ['autodocs'],
};
export default meta;
type Story = StoryObj<typeof ProjectCard>;
export const Default: Story = {
args: {
name: 'My Project',
description: 'A sample project for testing.',
status: 'active',
},
};
export const Archived: Story = {
args: {
name: 'Old Project',
description: 'This project is archived.',
status: 'archived',
},
};

Test responsive behavior by rendering the component in different viewports:

export const Mobile: Story = {
args: { name: 'My Project', status: 'active' },
parameters: {
viewport: { defaultViewport: 'mobile' },
},
};
export const Tablet: Story = {
args: { name: 'My Project', status: 'active' },
parameters: {
viewport: { defaultViewport: 'tablet' },
},
};

Wrap stories in layout containers or providers:

export const InSidebar: Story = {
decorators: [
(Story) => (
<div style={{ width: 300, padding: 16 }}>
<Story />
</div>
),
],
args: {
name: 'Sidebar Project',
status: 'active',
},
};

Capture and log user interactions:

import { fn } from '@storybook/test';
export const WithActions: Story = {
args: {
name: 'My Project',
onEdit: fn(),
onDelete: fn(),
},
};

The Storybook configuration lives in packages/ui/.storybook/:

import type { StorybookConfig } from '@storybook/react-vite';
const config: StorybookConfig = {
stories: ['../src/**/*.stories.@(ts|tsx)'],
addons: [
'@storybook/addon-essentials',
'@storybook/addon-a11y',
],
framework: {
name: '@storybook/react-vite',
options: {},
},
};
export default config;
import type { Preview } from '@storybook/react';
import '../../../apps/web/src/index.css';
const preview: Preview = {
parameters: {
controls: { matchers: { color: /(background|color)$/i, date: /Date$/i } },
viewport: {
viewports: {
mobile: { name: 'Mobile', styles: { width: '375px', height: '812px' } },
tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } },
desktop: { name: 'Desktop', styles: { width: '1280px', height: '800px' } },
},
},
backgrounds: {
values: [
{ name: 'light', value: '#ffffff' },
{ name: 'dark', value: '#1e293b' },
{ name: 'subtle', value: '#f8fafc' },
],
},
},
};
export default preview;

The preview imports apps/web/src/index.css so all CSS variables from the design system are available. Components render identically in Storybook and in the application.

Build a static HTML site for hosting or sharing:

Terminal window
pnpm --filter @cruzjs/ui storybook:build

The output is written to packages/ui/storybook-static/. Deploy it to any static hosting provider (Cloudflare Pages, Vercel, Netlify, S3) to share with your team or stakeholders.