Storybook: разработка и документация UI-компонентов
Storybook — инструмент для изолированной разработки, тестирования и документирования UI-компонентов. Настройка, stories, addons,interaction testing и публикация.
Что такое Storybook
Storybook — это инструмент для разработки UI-компонентов в изоляции. Вы можете создавать, тестировать и документировать компоненты без необходимости запускать всё приложение.
Зачем Storybook:
- Изолированная разработка — не нужно настраивать всю страницу, чтобы отладить кнопку
- Визуальная документация — все компоненты с примерами в одном месте
- Дизайн-система — единая библиотека компонентов для команды
- Визуальное тестирование — скриншот-тесты для отслеживания изменений
- Interaction testing — тестирование кликов, ввода, навигации прямо в Storybook
Storybook поддерживает React, Vue, Angular, Svelte, Web Components и другие фреймворки.
Установка
С нуля
npx storybook@latest init
Команда:
- Установит
@storybook/*пакеты - Создаст папку
.storybook/с конфигурацией - Создаст папку
src/stories/с примерами - Добавит скрипты в
package.json
В существующий проект (Vite + Vue)
npx storybook@latest init --builder=vite
Скрипты
{
"scripts": {
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
}
}
npm run storybook # Запустить dev-сервер (http://localhost:6006)
npm run build-storybook # Собрать статическую версию
Структура проекта
my-app/
.storybook/
main.ts # Основная конфигурация
preview.ts # Глобальный декоратор и параметры
src/
components/
Button.vue
Card.vue
Modal.vue
stories/
Button.stories.ts # Stories для Button
Card.stories.ts
composables/
useTheme.ts
stories/
useTheme.stories.ts
Конфигурация
main.ts
import type { StorybookConfig } from '@storybook/vue3-vite'
const config: StorybookConfig = {
stories: ['../src/**/*.stories.@(js|ts|mdx)'],
addons: [
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'@storybook/addon-a11y',
],
framework: {
name: '@storybook/vue3-vite',
options: {},
},
docs: {
autodocs: 'tag',
},
}
export default config
preview.ts
import type { Preview } from '@storybook/vue3'
const preview: Preview = {
parameters: {
backgrounds: {
default: 'light',
values: [
{ name: 'light', value: '#ffffff' },
{ name: 'dark', value: '#1a1a2e' },
],
},
controls: {
matchers: { color: /(background|color)$/i, date: /Date$/ },
},
},
decorators: [
(story) => ({
components: { story },
template: '<div style="padding: 1rem"><story /></div>',
}),
],
}
export default preview
Написание Stories
Story (история) — это одно состояние компонента. Кнопка может иметь stories: default, hover, disabled, loading.
Vue-компонент
<!-- src/components/BaseButton.vue -->
<script setup lang="ts">
interface Props {
label: string
variant?: 'primary' | 'secondary' | 'danger'
size?: 'sm' | 'md' | 'lg'
disabled?: boolean
loading?: boolean
}
withDefaults(defineProps<Props>(), {
variant: 'primary',
size: 'md',
disabled: false,
loading: false,
})
defineEmits<{ click: [] }>()
</script>
<template>
<button
:class="['btn', `btn-${variant}`, `btn-${size}`]"
:disabled="disabled || loading"
@click="$emit('click')"
>
<span v-if="loading" class="spinner" />
{{ label }}
</button>
</template>
Story-файл
// src/stories/BaseButton.stories.ts
import type { Meta, StoryObj } from '@storybook/vue3'
import BaseButton from '../components/BaseButton.vue'
type Story = StoryObj<typeof BaseButton>
const meta: Meta<typeof BaseButton> = {
title: 'Components/BaseButton',
component: BaseButton,
tags: ['autodocs'],
argTypes: {
variant: {
control: 'select',
options: ['primary', 'secondary', 'danger'],
},
size: {
control: 'select',
options: ['sm', 'md', 'lg'],
},
onClick: { action: 'clicked' },
},
args: {
label: 'Нажми меня',
variant: 'primary',
size: 'md',
},
}
export default meta
export const Primary: Story = {
args: {
label: 'Primary Button',
variant: 'primary',
},
}
export const Secondary: Story = {
args: {
label: 'Secondary Button',
variant: 'secondary',
},
}
export const Danger: Story = {
args: {
label: 'Delete',
variant: 'danger',
},
}
export const Small: Story = {
args: {
label: 'Small',
size: 'sm',
},
}
export const Large: Story = {
args: {
label: 'Large Button',
size: 'lg',
},
}
export const Disabled: Story = {
args: {
label: 'Disabled',
disabled: true,
},
}
export const Loading: Story = {
args: {
label: 'Loading...',
loading: true,
},
}
Stories для composables
// src/stories/useCounter.stories.ts
import type { Meta, StoryObj } from '@storybook/vue3'
import { ref } from 'vue'
function useCounter(initial = 0) {
const count = ref(initial)
const increment = () => { count.value++ }
const decrement = () => { count.value-- }
const reset = () => { count.value = initial }
return { count, increment, decrement, reset }
}
const meta: Meta = {
title: 'Composables/useCounter',
}
export default meta
export const Default: StoryObj = {
render: () => ({
setup() {
const { count, increment, decrement, reset } = useCounter()
return { count, increment, decrement, reset }
},
template: `
<div>
<p>Count: {{ count }}</p>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="reset">Reset</button>
</div>
`,
}),
}
CSF 3.0 (Component Story Format)
Современный формат — компактный, типобезопасный:
import type { Meta, StoryObj } from '@storybook/vue3'
import UserCard from '../components/UserCard.vue'
const meta: Meta<typeof UserCard> = {
title: 'Components/UserCard',
component: UserCard,
tags: ['autodocs'],
args: {
name: 'Анна Иванова',
role: 'Frontend Developer',
avatar: 'https://i.pravatar.cc/150?img=1',
},
}
export default meta
type Story = StoryObj<typeof UserCard>
export const Default: Story = {}
export const WithoutAvatar: Story = {
args: { avatar: undefined },
}
export const LongName: Story = {
args: { name: 'Очень Длинное Имя Для Проверки Переполнения Содержимого' },
}
Addons (Плагины)
@storybook/addon-essentials
Включает 6 плагинов одним пакетом:
- Controls — изменение props в реальном времени (панель справа)
- Actions — логирование событий (клики, эмиты)
- Viewport — переключение размеров экрана (mobile, tablet, desktop)
- Backgrounds — переключение цвета фона
- Toolbars — глобальные переключатели (тема, язык)
- Measure & Outline — инспектирование отступов и размеров
@storybook/addon-interactions
Тестирование взаимодействий (клики, ввод) прямо в Storybook:
npm install -D @storybook/addon-interactions @storybook/test
import type { Meta, StoryObj } from '@storybook/vue3'
import { userEvent, within, expect } from '@storybook/test'
import SearchForm from '../components/SearchForm.vue'
const meta: Meta<typeof SearchForm> = {
title: 'Components/SearchForm',
component: SearchForm,
}
export default meta
export const SearchInteraction: StoryObj<typeof SearchForm> = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement)
const input = canvas.getByPlaceholderText('Поиск...')
await userEvent.type(input, 'Vue.js')
const button = canvas.getByRole('button', { name: /найти/i })
await userEvent.click(button)
const result = canvas.getByText('Результаты для: Vue.js')
await expect(result).toBeInTheDocument()
},
}
@storybook/addon-a11y
Проверка доступности (accessibility):
npm install -D @storybook/addon-a11y
// .storybook/main.ts
addons: ['@storybook/addon-a11y'],
Добавляет панель Accessibility, которая показывает нарушения WCAG: недостаточный контраст, отсутствующие aria-метки, неправильная семантика.
Decorators
Декораторы оборачивают story в дополнительный контекст (провайдеры, стили, роутер).
Глобальный декоратор
// .storybook/preview.ts
import { theme } from '../src/theme'
export const decorators = [
(story) => ({
components: { story },
template: `
<div :style="{ fontFamily: theme.fontFamily, padding: '2rem' }">
<story />
</div>
`,
setup() {
return { theme }
},
}),
]
Декоратор для конкретной story
export const InModal: Story = {
decorators: [
(story) => ({
components: { story },
template: `
<div style="position: fixed; inset: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center;">
<story />
</div>
`,
}),
],
}
Autodocs
При tags: ['autodocs'] Storybook автоматически генерирует страницу документации для компонента:
- Описание (из JSDoc-комментариев)
- Таблица Props (типы, значения по умолчанию)
- Таблица Events
- Все stories в виде превью
- Исходный код
Можно добавить описание:
const meta: Meta<typeof Button> = {
title: 'Components/Button',
component: Button,
tags: ['autodocs'],
parameters: {
docs: {
description: {
component: 'Базовая кнопка с поддержкой вариантов, размеров и состояния загрузки.',
},
},
},
}
Публикация
Статическая сборка
npm run build-storybook
Результат — папка storybook-static/ с HTML/CSS/JS. Можно развернуть на любом хостинге.
Chromatic
Chromatic (от команды Storybook) — сервис для публикации и визуального тестирования:
npx chromatic --project-token=<your-token>
Для каждого PR Chromatic:
- Собирает Storybook
- Делает скриншоты всех stories
- Сравнивает с предыдущей версией
- Показывает визуальные различия
Vercel / Netlify
# Vercel
npx vercel storybook-static/
# Netlify
npx netlify deploy --dir=storybook-static --prod
Интеграция с дизайном
Design Addon
Позволяет встроить Figma-макет прямо в Storybook:
npm install -D @storybook/addon-designs
// .storybook/main.ts
addons: ['@storybook/addon-designs'],
export const Default: Story = {
parameters: {
design: {
type: 'figma',
url: 'https://www.figma.com/file/abc123?node-id=1-100',
},
},
}
Кнопка «Design» в панели покажет Figma-макет рядом с компонентом.
Практика: создание дизайн-системы
src/
components/
ui/
BaseButton.vue
BaseInput.vue
BaseCard.vue
BaseModal.vue
BaseBadge.vue
BaseAvatar.vue
BaseTooltip.vue
layout/
AppHeader.vue
AppSidebar.vue
AppFooter.vue
stories/
ui/
BaseButton.stories.ts
BaseInput.stories.ts
BaseCard.stories.ts
...
layout/
AppHeader.stories.ts
...
Каждый компонент имеет:
- Stories со всеми вариантами
- Autodocs для документации
- Interaction tests для поведения
- A11y-проверку
Storybook становится единой документацией и витриной дизайн-системы.
Итог
- Storybook — среда для разработки, тестирования и документирования UI-компонентов
- Stories — состояния компонента (default, disabled, loading, error)
- Autodocs — автоматическая документация с таблицей Props и Events
- Addons расширяют возможности: Controls, Interactions, A11y, Viewport
- Interaction testing — тестирование кликов и ввода прямо в Storybook
- Подходит для создания дизайн-систем и библиотек компонентов
- Работает с Vue, React, Angular, Svelte