belajarkoding Platform belajar web development Indonesia. Artikel, cheat sheets, roadmap, dan code challenges untuk developer Indonesia.
© 2026 BelajarKoding. All rights reserved.
Bagian dari ekosistem Galih Pratama
Storybook Cheat Sheet Referensi cepat Storybook untuk component-driven development. Stories format, args, decorators, addons, testing, dan composition. Perfect buat developer yang bangun design system.
TypeScript 9 min read 1.725 kata
Silakan
login atau
daftar untuk membaca cheat sheet ini.
Baca Cheat Sheet Lengkap Login atau daftar akun gratis untuk membaca cheat sheet ini.
Storybook Cheat Sheet - BelajarKoding | BelajarKoding
# Instalasi
# Setup Storybook
# Init di project yang udah ada (React, Vue, Svelte, Angular, dll)
npx storybook@latest init
# Install manual
npm install storybook @storybook/react @storybook/react-vite --dev
npx storybook init
Setelah init, Storybook bakal bikin:
.storybook/
main.ts # Konfigurasi Storybook
preview.ts # Global decorators dan parameters
src/stories/ # Contoh stories # Dev mode (hot reload)
npm run storybook
# Build static storybook (untuk deploy)
npm run build-storybook
# Dengan custom port
npx storybook dev -p 6007
# CI mode (tanpa auto-open browser)
npx storybook dev --ci -p 6007 // src/components/Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react' ;
import { Button } from './Button' ;
const
# Render Function (Dynamic Stories)export const Template : Story = {
render : ( args ) => ({
component: Button,
props: args,
}),
};
// Story dengan custom render
export
export const Playground : Story = {
args: {
variant: 'primary' ,
size: 'md' ,
children: 'Playground' ,
disabled: false ,
loading: false ,
},
# ArgTypes: Control Configurationconst meta = {
argTypes: {
// Text input
label: { control: 'text' },
// Number input
count: { control: 'number'
# Component-level DecoratorsWrap story dengan markup atau provider tambahan.
// Global decorator untuk semua stories
const meta = {
decorators: [
( Story ) => (
< div style = {{ padding: '20px' , background: '#f0f0f0' }}>
<
# Global Decorators (preview.ts)// .storybook/preview.ts
import type { Preview } from '@storybook/react' ;
const preview : Preview = {
decorators: [
( Story ) => (
<
// Per-story parameters
export const Canvas : Story = {
parameters: {
layout: 'fullscreen' ,
backgrounds: { default: 'dark' },
viewport: { defaultViewport: 'mobile1' },
},
};
// .storybook/main.ts
import type { StorybookConfig } from '@storybook/react-vite' ;
const config : StorybookConfig = {
stories: [
'../src/**/*.mdx' ,
// Actions (event logging)
import type { Meta } from '@storybook/react' ;
const meta : Meta = {
# Play Functions (Interaction Tests)import { expect } from '@storybook/test' ;
import { within, userEvent } from '@storybook/test' ;
export const LoginForm : Story = {
# Install
npm install @storybook/test-runner --dev
# Run tests (headless browser)
npm run test-storybook
# Dengan specific stories
npx test-storybook --url "http://localhost:6007" \
--storyFilter "Components/Button/*"
# Watch mode
npx test-storybook --watch
# Component Testing dengan Vitest/Jest// Import story sebagai test case
import { composeStories } from '@storybook/react' ;
import { render, screen } from '@testing-library/react' ;
import * as stories from './Button.stories' ;
const
// Title format dengan / untuk grouping
const meta = {
title: 'Design System/Forms/Input' , // Group > Subgroup > Component
component: Input,
};
// Atau dot notation
const meta = {
title: 'Components.Input' , // Flat hierarchy dengan dot separator
component: Input,
}; # Multiple Components per File// Group related components
export default {
title: 'Components/Form Controls' ,
} as Meta ;
export const TextInput = {
render : () => < TextInput />,
};
export const
# Page Stories (Full Pages)export default {
title: 'Pages/Login' ,
component: LoginPage,
parameters: {
layout: 'fullscreen' ,
viewport: { defaultViewport: 'desktop' },
},
} as Meta < typeof LoginPage>;
export const
# Composition (Microfrontends)// .storybook/main.ts
// Combine multiple Storybooks dari berbagai repo
const config = {
refs: {
designSystem: {
title: 'Design System' ,
url: 'https://design-system.example.com/storybook' ,
},
adminPanel: {
title: 'Admin Panel' ,
url: 'https://admin.example.com/storybook'
# Build static files
npm run build-storybook
# Output: ./storybook-static/
# Deploy ke Vercel
npx vercel ./storybook-static --prod
# Deploy ke Netlify
npx netlify deploy --dir=./storybook-static --prod
# Deploy ke Chromatic (visual regression)
npx chromatic --project-token=YOUR_TOKEN
# Chromatic (Visual Testing)# Install
npm install chromatic --dev
# Publish storybook + run visual tests
npx chromatic
# CI integration
# GitHub Actions:
# - name: Publish to Chromatic
# run: npx chromatic --project-token=${{ secrets.CHROMATIC_TOKEN }}
Story : Snapshot dari component dalam state tertentu. Unit terkecil di Storybook.
CSF (Component Story Format) : Format standar nulis stories sebagai ES modules. Versi 3 adalah terbaru.
Args : Input props untuk component dalam story. Bisa di-edit di UI Storybook.
ArgTypes : Metadata tentang args: tipe control, options, validasi.
Decorator : Wrapper yang bungkus story dengan context tambahan (provider, layout, theme).
Play Function : Function yang otomatis interact dengan component untuk testing. Pakai Testing Library.
Addon : Plugin yang nambah fungsionalitas Storybook (a11y, coverage, viewport, dll).
Canvas : Area render di mana component live. Interaktif.
Docs : Halaman dokumentasi auto-generated dari stories dan component source.
Composition : Gabung multiple Storybook dari berbagai project jadi satu unified UI.
Visual Regression Testing : Bandingkan screenshot component dari commit ke commit. Chromatic, Percy.
Coverage : Metric berapa banyak component/code yang tercakup oleh stories.
meta
:
Meta
<
typeof
Button>
=
{
title: 'Components/Button' ,
component: Button,
parameters: {
layout: 'centered' ,
},
tags: [ 'autodocs' ],
argTypes: {
variant: {
control: 'select' ,
options: [ 'primary' , 'secondary' , 'danger' , 'ghost' ],
},
size: {
control: 'select' ,
options: [ 'sm' , 'md' , 'lg' ],
},
disabled: { control: 'boolean' },
onClick: { action: 'clicked' },
},
};
export default meta;
type Story = StoryObj < typeof meta>;
// Default story
export const Primary : Story = {
args: {
variant: 'primary' ,
size: 'md' ,
children: 'Click Me' ,
},
};
// Dengan args berbeda
export const Secondary : Story = {
args: {
... Primary.args,
variant: 'secondary' ,
children: 'Secondary' ,
},
};
export const Disabled : Story = {
args: {
... Primary.args,
disabled: true ,
children: 'Disabled' ,
},
};
// Large size
export const Large : Story = {
args: {
... Primary.args,
size: 'lg' ,
children: 'Large Button' ,
},
};
const
WithIcon
:
Story
=
{
render : () => (
< Button variant = "primary" >
< svg width = "16" height = "16" fill = "currentColor" >
< path d = "M8 0a8 8 0 100 16A8 8 0 008 0z" />
</ svg >
Save
</ Button >
),
};
// Multiple stories dari satu template
const Template : StoryFn < typeof Button> = ( args ) => < Button { ... args} />;
export const Primary = Template. bind ({});
Primary.args = { variant: 'primary' , children: 'Primary' };
export const Secondary = Template. bind ({});
Secondary.args = { variant: 'secondary' , children: 'Secondary' };
};
// Args composition
const baseArgs = {
children: 'Button' ,
size: 'md' as const ,
};
export const Primary : Story = { args: { ... baseArgs, variant: 'primary' } };
export const Danger : Story = { args: { ... baseArgs, variant: 'danger' } };
, min:
0
, max:
100
, step:
5
},
// Boolean
show: { control: 'boolean' },
// Select dropdown
color: {
control: 'select' ,
options: [ 'red' , 'green' , 'blue' , 'yellow' ],
},
// Radio buttons
layout: {
control: 'radio' ,
options: [ 'horizontal' , 'vertical' ],
},
// Inline radio
align: {
control: 'inline-radio' ,
options: [ 'left' , 'center' , 'right' ],
},
// Range slider
opacity: {
control: 'range' ,
min: 0 ,
max: 1 ,
step: 0.1 ,
},
// Object/JSON
style: { control: 'object' },
// Date picker
date: { control: 'date' },
// Color picker
backgroundColor: { control: 'color' },
// File upload
image: { control: 'file' , accept: 'image/*' },
// Custom control
customProp: {
control: {
type: 'select' ,
labels: { 0 : 'Small' , 1 : 'Medium' , 2 : 'Large' },
},
options: [ 0 , 1 , 2 ],
mapping: { 0 : 'sm' , 1 : 'md' , 2 : 'lg' },
},
},
};
Story
/>
</ div >
),
],
};
// Per-story decorator
export const DarkMode : Story = {
decorators: [
( Story ) => (
< div className = "dark-theme" >
< Story />
</ div >
),
],
};
// Provider decorator (Redux, Theme, Router, dll)
const meta = {
decorators: [
( Story ) => (
< ThemeProvider theme = {darkTheme}>
< BrowserRouter >
< Story />
</ BrowserRouter >
</ ThemeProvider >
),
],
};
div
style
=
{{ fontFamily:
'Inter, sans-serif'
}}>
< Story />
</ div >
),
],
parameters: {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: / (background | color) $ / i ,
date: / Date $ / i ,
},
},
backgrounds: {
default: 'light' ,
values: [
{ name: 'light' , value: '#ffffff' },
{ name: 'dark' , value: '#1a1a1a' },
{ name: 'brand' , value: '#6366f1' },
],
},
},
};
export default preview;
// Common parameters
const meta = {
parameters: {
layout: 'centered' , // 'centered' | 'fullscreen' | 'padded'
docs: {
description: {
component: 'Reusable button component with variants.' ,
},
},
design: {
type: 'figma' ,
url: 'https://www.figma.com/file/abc123/Button' ,
},
},
};
'../src/**/*.stories.@(ts|tsx)'
,
],
addons: [
'@storybook/addon-links' ,
'@storybook/addon-essentials' ,
'@storybook/addon-interactions' ,
'@storybook/addon-a11y' ,
'@storybook/addon-coverage' ,
],
framework: {
name: '@storybook/react-vite' ,
options: {},
},
docs: {
autodocs: 'tag' , // 'tag' | true | false
defaultName: 'Documentation' ,
},
staticDirs: [ '../public' ],
typescript: {
check: false ,
reactDocgen: 'react-docgen-typescript' ,
},
viteFinal : async ( config ) => {
// Custom Vite config
config.resolve.alias = {
... config.resolve.alias,
'@' : '/src' ,
};
return config;
},
};
export default config;
parameters: {
actions: {
argTypesRegex: '^on[A-Z].*' , // auto-detect onClick, onChange, dll
},
},
};
// Manual action
import { action } from '@storybook/addon-actions' ;
export const Demo : Story = {
args: {
onClick: action ( 'button-clicked' ),
onChange: action ( 'text-changed' ),
},
};
// Viewport addon (responsive testing)
const meta = {
parameters: {
viewport: {
viewports: {
iPhone: { name: 'iPhone' , styles: { width: '375px' , height: '812px' } },
iPad: { name: 'iPad' , styles: { width: '768px' , height: '1024px' } },
desktop: { name: 'Desktop' , styles: { width: '1920px' , height: '1080px' } },
},
defaultViewport: 'iPhone' ,
},
},
};
// Measure & Outline (CSS debugging)
// Aktif otomatis dengan addon-essentials
// Tampil di toolbar addon
// Accessibility (a11y) testing
const meta = {
parameters: {
a11y: {
element: '#storybook-root' ,
config: {
rules: [
{ id: 'color-contrast' , enabled: true },
],
},
options: {},
manual: false ,
},
},
};
play
:
async
({
canvasElement
})
=>
{
const canvas = within (canvasElement);
// Interact dengan component
await userEvent. type (canvas. getByPlaceholderText ( 'Email' ), 'test@test.com' );
await userEvent. type (canvas. getByPlaceholderText ( 'Password' ), 'password123' );
await userEvent. click (canvas. getByRole ( 'button' , { name: 'Login' }));
// Assert
await expect (canvas. getByText ( 'Welcome!' )). toBeVisible ();
},
};
// Async play dengan delay
export const WithDelay : Story = {
play : async ({ canvasElement }) => {
const canvas = within (canvasElement);
await userEvent. click (canvas. getByText ( 'Load' ));
await new Promise ( r => setTimeout (r, 500 ));
await expect (canvas. getByText ( 'Loaded' )). toBeInTheDocument ();
},
};
# Generate coverage
npx test-storybook --coverage
{
Primary
,
Disabled
}
=
composeStories
(stories);
test ( 'renders primary button' , () => {
render (< Primary />);
expect (screen. getByText ( 'Click Me' )). toBeInTheDocument ();
});
test ( 'disabled button is disabled' , () => {
render (< Disabled />);
expect (screen. getByRole ( 'button' )). toBeDisabled ();
});
// Run play functions
test ( 'runs play function' , async () => {
const { Primary } = composeStories (stories);
const { container } = render (< Primary />);
await Primary. play ?.({ canvasElement: container });
});
Select
=
{
render : () => < Select />,
};
export const Checkbox = {
render : () => < Checkbox />,
};
Default
:
Story
=
{};
export const WithError : Story = {
args: {
error: 'Invalid credentials' ,
},
};
,
},
},
};
# Deploy ke GitHub Pages
# .github/workflows/storybook.yml