Use this skill when the user asks about "Storybook with Tauri", "Storybook with Electron", "desktop app components", "Tauri IPC mocking", "Electron limitations", mentions "cross-platform development", "native APIs in Storybook", or wants to develop Storybook stories for Tauri or Electron applications. This skill provides platform-specific guidance and architectural patterns for multi-platform component development.
/plugin marketplace add flight505/storybook-assistant-plugin/plugin install flight505-storybook-assistant@flight505/storybook-assistant-pluginThis skill inherits all available tools. When active, it can use any tool Claude has access to.
electron/limitations.mdtauri/setup_guide.mdDevelop Storybook components for desktop applications (Tauri and Electron) with platform-specific guidance, IPC mocking patterns, and architectural best practices for maximum testability.
This skill provides comprehensive documentation on integrating Storybook with Tauri (full support) and Electron (partial support with workarounds).
Understand support levels for each platform:
Complete guidance for Tauri applications:
Limitations documentation and workarounds:
Platform-agnostic component patterns:
Tauri and Storybook work excellently together:
Run Tauri and Storybook in parallel:
# Terminal 1: Tauri development server
npm run tauri dev
# Runs on http://localhost:5173
# Terminal 2: Storybook
npm run storybook
# Runs on http://localhost:6006
# Terminal 3: Tests in watch mode
npm run test:watch
Best Practice: Dependency Injection
Keep components Tauri-agnostic by injecting IPC functionality:
// ✅ Good: Testable component
interface ApiClient {
readFile: (path: string) => Promise<string>;
writeFile: (path: string, content: string) => Promise<void>;
}
function FileEditor({ apiClient }: { apiClient: ApiClient }) {
const [content, setContent] = useState('');
const handleSave = async () => {
await apiClient.writeFile('file.txt', content);
};
return <textarea value={content} onChange={(e) => setContent(e.target.value)} />;
}
// In Tauri app: Inject real API
<FileEditor apiClient={tauriApiClient} />
// In Storybook: Inject mock API
<FileEditor apiClient={mockApiClient} />
Create mock providers for Tauri API calls:
// .storybook/tauri-mocks.ts
export const mockTauriApi = {
invoke: async (cmd: string, args?: any) => {
switch (cmd) {
case 'read_file':
return 'Mock file content';
case 'write_file':
return { success: true };
default:
return null;
}
},
};
// In story
export const WithTauriAPI: Story = {
decorators: [
(Story) => {
if (typeof window !== 'undefined') {
window.__TAURI__ = mockTauriApi;
}
return <Story />;
},
],
};
Recommended structure for Tauri + Storybook:
tauri-app/
├── src/
│ ├── components/ # UI components (testable)
│ │ ├── Button.tsx
│ │ ├── Button.stories.tsx
│ │ ├── FileEditor.tsx
│ │ └── FileEditor.stories.tsx
│ ├── api/
│ │ └── tauri.ts # Tauri IPC abstraction
│ └── main.tsx
├── src-tauri/ # Rust backend
│ ├── src/
│ └── tauri.conf.json
├── .storybook/
│ ├── main.ts
│ ├── preview.ts
│ └── tauri-mocks.ts # IPC mocks
└── package.json
Iframe Incompatibility:
ipcRenderer) are not available in iframe contextelectron-renderer target creates externals that failImpact:
electron imports won't workComponents without Electron dependencies work perfectly:
// ✅ Works perfectly
function Button({ variant, onClick }) {
return (
<button className={`btn-${variant}`} onClick={onClick}>
Click Me
</button>
);
}
UI libraries work without issues:
// ✅ Works - Material UI, shadcn/ui, etc.
import { Card } from '@mui/material';
function ProfileCard({ name, bio }) {
return <Card>{/* UI content */}</Card>;
}
Redux, Zustand, Context API all work:
// ✅ Works
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
// ❌ Won't work in Storybook
import { ipcRenderer } from 'electron';
function FileReader() {
const readFile = () => {
ipcRenderer.invoke('read-file', '/path/to/file');
};
// This will fail in Storybook iframe
}
Separate concerns to maximize testability:
// ✅ Presentational Component (testable in Storybook)
interface FileListProps {
files: string[];
isLoading: boolean;
error?: string;
onRefresh: () => void;
}
function FileList({ files, isLoading, error, onRefresh }: FileListProps) {
if (isLoading) return <Spinner />;
if (error) return <ErrorMessage message={error} />;
return (
<div>
<button onClick={onRefresh}>Refresh</button>
<ul>
{files.map(file => <li key={file}>{file}</li>)}
</ul>
</div>
);
}
// ❌ Container Component (not testable in Storybook)
function FileListContainer() {
const [files, setFiles] = useState<string[]>([]);
const [loading, setLoading] = useState(false);
const loadFiles = async () => {
setLoading(true);
const result = await window.api.readDir('/path');
setFiles(result);
setLoading(false);
};
return <FileList files={files} isLoading={loading} onRefresh={loadFiles} />;
}
In Storybook, test the presentational component:
export const Default: Story = {
args: {
files: ['file1.txt', 'file2.txt', 'file3.txt'],
isLoading: false,
onRefresh: fn(),
},
};
export const Loading: Story = {
args: {
files: [],
isLoading: true,
onRefresh: fn(),
},
};
export const Error: Story = {
args: {
files: [],
isLoading: false,
error: 'Failed to load files',
onRefresh: fn(),
},
};
Use multiple testing approaches:
# Storybook: UI component testing
npm run storybook
# Vitest: Unit tests
npm run test
# Playwright: E2E tests with Electron
npm run test:e2e
Create platform-agnostic API clients:
// api/client.ts
export interface FileSystemClient {
readFile: (path: string) => Promise<string>;
writeFile: (path: string, content: string) => Promise<void>;
listFiles: (dir: string) => Promise<string[]>;
}
// api/tauri-client.ts
export const tauriClient: FileSystemClient = {
readFile: (path) => invoke('read_file', { path }),
writeFile: (path, content) => invoke('write_file', { path, content }),
listFiles: (dir) => invoke('list_files', { dir }),
};
// api/electron-client.ts
export const electronClient: FileSystemClient = {
readFile: (path) => window.api.readFile(path),
writeFile: (path, content) => window.api.writeFile(path, content),
listFiles: (dir) => window.api.listFiles(dir),
};
// api/mock-client.ts (for Storybook)
export const mockClient: FileSystemClient = {
readFile: async () => 'Mock content',
writeFile: async () => {},
listFiles: async () => ['file1.txt', 'file2.txt'],
};
Use React Context to inject platform APIs:
// contexts/PlatformContext.tsx
const PlatformContext = createContext<FileSystemClient>(mockClient);
export function PlatformProvider({ client, children }) {
return (
<PlatformContext.Provider value={client}>
{children}
</PlatformContext.Provider>
);
}
export function usePlatform() {
return useContext(PlatformContext);
}
// In component
function FileViewer() {
const platform = usePlatform();
const [content, setContent] = useState('');
useEffect(() => {
platform.readFile('file.txt').then(setContent);
}, []);
return <pre>{content}</pre>;
}
In Storybook:
export const Default: Story = {
decorators: [
(Story) => (
<PlatformProvider client={mockClient}>
<Story />
</PlatformProvider>
),
],
};
Check for platform capabilities:
function isElectron() {
return typeof window !== 'undefined' && window.api !== undefined;
}
function isTauri() {
return typeof window !== 'undefined' && window.__TAURI__ !== undefined;
}
function useFileSystem() {
if (isTauri()) return tauriClient;
if (isElectron()) return electronClient;
return mockClient; // Fallback for Storybook/web
}
Add to .storybook/preview.ts:
import { mockTauriApi } from './tauri-mocks';
export const decorators = [
(Story) => {
if (typeof window !== 'undefined') {
window.__TAURI__ = mockTauriApi;
}
return <Story />;
},
];
Add to .storybook/preview.ts:
import { mockElectronApi } from './electron-mocks';
export const decorators = [
(Story) => {
if (typeof window !== 'undefined') {
window.api = mockElectronApi;
}
return <Story />;
},
];
tauri/setup_guide.md - Complete Tauri + Storybook integration guideelectron/limitations.md - Known issues and workarounds| Platform | Support Level | Storybook Works | IPC Testable | Recommended Pattern |
|---|---|---|---|---|
| Web | 100% | ✅ Yes | N/A | Standard components |
| Tauri | 100% | ✅ Yes | ✅ Yes (mocking) | Dependency injection |
| Electron | ~60% | ⚠️ Partial | ❌ No (E2E only) | Container/presentational |
Key Takeaway: