Decorators Reference
Last Updated: November 23, 2025
This document covers decorator patterns for wrapping stories with context providers, authentication states, and other wrappers.
Table of Contents
- Decorator Basics
- Authentication Decorators
- Provider Decorators
- Decorator Composition
- Global vs Story-Level Decorators
- Best Practices
Decorator Basics
Decorators wrap stories with additional functionality or context.
Basic Decorator Pattern
import type { Decorator } from '@storybook/react';
const withPadding: Decorator = (Story) => (
<div style={{ padding: '20px' }}>
<Story />
</div>
);
export const Example: Story = {
decorators: [withPadding],
};
Multiple Decorators
Decorators are applied from inside out (last to first in array):
const withPadding: Decorator = (Story) => (
<div style={{ padding: '20px' }}>
<Story />
</div>
);
const withBorder: Decorator = (Story) => (
<div style={{ border: '1px solid red' }}>
<Story />
</div>
);
export const Example: Story = {
decorators: [
withPadding, // Applied first (innermost)
withBorder, // Applied second (outermost)
],
};
Authentication Decorators
MockAuthProvider Decorator
Wrap stories with authentication context:
import { MockAuthProvider, AuthStates } from '@/tests/mocks/providers/MockAuthProvider';
const withAuth: Decorator = (Story) => (
<MockAuthProvider {...AuthStates.authenticated}>
<Story />
</MockAuthProvider>
);
export const Protected: Story = {
decorators: [withAuth],
};
Predefined Auth States
// Authenticated user
const withAuth: Decorator = (Story) => (
<MockAuthProvider {...AuthStates.authenticated}>
<Story />
</MockAuthProvider>
);
// Admin user
const withAdmin: Decorator = (Story) => (
<MockAuthProvider {...AuthStates.admin}>
<Story />
</MockAuthProvider>
);
// Unauthenticated
const withNoAuth: Decorator = (Story) => (
<MockAuthProvider {...AuthStates.unauthenticated}>
<Story />
</MockAuthProvider>
);
Custom Auth States
const withCustomAuth: Decorator = (Story) => (
<MockAuthProvider
isAuthenticated={true}
user={{
id: 'custom-user-123',
email: 'custom@example.com',
name: 'Custom User',
role: 'manager',
}}
>
<Story />
</MockAuthProvider>
);
Provider Decorators
React Query Provider
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const withQueryClient: Decorator = (Story) => {
const queryClient = new QueryClient({
defaultOptions: {
queries: { retry: false },
},
});
return (
<QueryClientProvider client={queryClient}>
<Story />
</QueryClientProvider>
);
};
Theme Provider
import { ThemeProvider } from '@/providers/ThemeProvider';
const withTheme: Decorator = (Story) => (
<ThemeProvider defaultTheme="light">
<Story />
</ThemeProvider>
);
Router Provider
import { BrowserRouter } from 'react-router-dom';
const withRouter: Decorator = (Story) => (
<BrowserRouter>
<Story />
</BrowserRouter>
);
Multiple Providers
const withProviders: Decorator = (Story) => (
<QueryClientProvider client={queryClient}>
<ThemeProvider>
<MockAuthProvider {...AuthStates.authenticated}>
<BrowserRouter>
<Story />
</BrowserRouter>
</MockAuthProvider>
</ThemeProvider>
</QueryClientProvider>
);
Decorator Composition
Reusable Decorator Factory
Create decorators that accept configuration:
const withAuthState = (state: typeof AuthStates[keyof typeof AuthStates]): Decorator =>
(Story) => (
<MockAuthProvider {...state}>
<Story />
</MockAuthProvider>
);
// Usage
export const AsAdmin: Story = {
decorators: [withAuthState(AuthStates.admin)],
};
export const AsUser: Story = {
decorators: [withAuthState(AuthStates.authenticated)],
};
Conditional Decorators
const withConditionalAuth = (requireAuth: boolean): Decorator =>
(Story) => {
if (!requireAuth) {
return <Story />;
}
return (
<MockAuthProvider {...AuthStates.authenticated}>
<Story />
</MockAuthProvider>
);
};
Composing Decorators
const composeDecorators = (...decorators: Decorator[]): Decorator =>
(Story) => decorators.reduceRight(
(acc, decorator) => () => decorator(acc),
Story
)();
const authAndTheme = composeDecorators(withAuth, withTheme);
export const Example: Story = {
decorators: [authAndTheme],
};
Global vs Story-Level Decorators
Global Decorators
Configure in .storybook/preview.tsx:
import type { Preview } from '@storybook/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient();
const preview: Preview = {
decorators: [
(Story) => (
<QueryClientProvider client={queryClient}>
<Story />
</QueryClientProvider>
),
],
};
export default preview;
Component-Level Decorators
Apply to all stories in a file:
const meta = {
component: AdminPanel,
decorators: [
(Story) => (
<MockAuthProvider {...AuthStates.admin}>
<Story />
</MockAuthProvider>
),
],
} satisfies Meta<typeof AdminPanel>;
Story-Level Decorators
Apply to individual stories:
export const WithError: Story = {
decorators: [
(Story) => (
<div data-testid="error-boundary">
<Story />
</div>
),
],
};
Decorator Priority
Decorators are applied in this order (inside to outside):
- Global decorators (preview.tsx)
- Component-level decorators (meta)
- Story-level decorators (individual story)
// Applied order: Story → Component → Global
Best Practices
1. Extract Reusable Decorators
// ✅ Good - Reusable decorator
// tests/mocks/decorators/withAuth.ts
export const withAuth: Decorator = (Story) => (
<MockAuthProvider {...AuthStates.authenticated}>
<Story />
</MockAuthProvider>
);
// Use in stories
import { withAuth } from '@/tests/mocks/decorators/withAuth';
export const Protected: Story = {
decorators: [withAuth],
};
2. Use Factory Functions for Configuration
// ✅ Good - Configurable decorator
export const withAuth = (state = AuthStates.authenticated): Decorator =>
(Story) => (
<MockAuthProvider {...state}>
<Story />
</MockAuthProvider>
);
// Usage
decorators: [withAuth(AuthStates.admin)]
3. Document Decorator Purpose
/**
* Wraps story with authenticated user context
* Provides user: { id: 'user-123', email: 'user@example.com' }
*/
const withAuth: Decorator = (Story) => (
<MockAuthProvider {...AuthStates.authenticated}>
<Story />
</MockAuthProvider>
);
4. Keep Decorators Focused
// ✅ Good - Single responsibility
const withAuth: Decorator = /* auth only */;
const withTheme: Decorator = /* theme only */;
const withRouter: Decorator = /* router only */;
// Use together
decorators: [withAuth, withTheme, withRouter]
// ❌ Avoid - Multiple concerns
const withEverything: Decorator = /* auth + theme + router + ... */;
5. Handle Cleanup
const withQueryClient: Decorator = (Story) => {
const queryClient = new QueryClient();
// Cleanup after unmount
useEffect(() => {
return () => {
queryClient.clear();
};
}, []);
return (
<QueryClientProvider client={queryClient}>
<Story />
</QueryClientProvider>
);
};
6. Type Decorators Properly
import type { Decorator } from '@storybook/react';
// ✅ Good - Properly typed
export const withAuth: Decorator = (Story) => (
<MockAuthProvider {...AuthStates.authenticated}>
<Story />
</MockAuthProvider>
);
// ❌ Avoid - Untyped
export const withAuth = (Story) => /* ... */;
7. Use Context for Shared State
const withSharedState: Decorator = (Story) => {
const [state, setState] = useState();
return (
<SharedContext.Provider value={{ state, setState }}>
<Story />
</SharedContext.Provider>
);
};
Common Patterns
Authentication Variants
// Default authenticated
export const Default: Story = {
decorators: [withAuth],
};
// Admin user
export const AsAdmin: Story = {
decorators: [withAuth(AuthStates.admin)],
};
// Unauthenticated
export const Unauthenticated: Story = {
decorators: [withAuth(AuthStates.unauthenticated)],
};
Dark Mode Toggle
export const LightMode: Story = {
decorators: [
(Story) => (
<ThemeProvider defaultTheme="light">
<Story />
</ThemeProvider>
),
],
};
export const DarkMode: Story = {
decorators: [
(Story) => (
<ThemeProvider defaultTheme="dark">
<Story />
</ThemeProvider>
),
],
};
With Mock Data Context
const withMockData: Decorator = (Story) => (
<MockDataContext.Provider
value={{
users: mockUsers,
organizations: mockOrganizations,
}}
>
<Story />
</MockDataContext.Provider>
);
export const WithData: Story = {
decorators: [withMockData],
};
Responsive Container
const withMobileView: Decorator = (Story) => (
<div style={{ maxWidth: '375px', margin: '0 auto' }}>
<Story />
</div>
);
export const Mobile: Story = {
decorators: [withMobileView],
};
Error Boundary
const withErrorBoundary: Decorator = (Story) => (
<ErrorBoundary fallback={<div>Error occurred</div>}>
<Story />
</ErrorBoundary>
);
export const WithErrorHandling: Story = {
decorators: [withErrorBoundary],
};
Advanced Patterns
Async Decorator
const withAsyncData: Decorator = (Story) => {
const [data, setData] = useState(null);
useEffect(() => {
fetchData().then(setData);
}, []);
if (!data) return <div>Loading...</div>;
return (
<DataContext.Provider value={data}>
<Story />
</DataContext.Provider>
);
};
Parameterized Decorator
const withCustomStyles = (styles: React.CSSProperties): Decorator =>
(Story) => (
<div style={styles}>
<Story />
</div>
);
export const CustomLayout: Story = {
decorators: [
withCustomStyles({
maxWidth: '800px',
margin: '0 auto',
padding: '20px',
}),
],
};
Conditional Rendering
const withFeatureFlag = (flag: string): Decorator =>
(Story) => {
const isEnabled = checkFeatureFlag(flag);
if (!isEnabled) {
return <div>Feature disabled</div>;
}
return <Story />;
};
Related Documentation
- Story Structure - Story file structure and configuration
- Testing Patterns - Testing Library and interaction patterns
- MSW Handlers - API mocking patterns
- Helpers - Reusable story utilities
For task-specific implementation details, see the Storybook integration task files in docs/storybook-integration/.