Storybook is a frontend workshop for building UI components and pages in isolation. Thousands of teams use it for UI development, testing, and documentation. It's open source and free.
31.61m
Installs per month
2282
Contributors
Implement components and pages without needing to fuss with data, APIs, or business logic.
Start trialRender components in key states that are tricky to reproduce in an app. Then save those states as stories to revisit during development, testing, and QA.
Addons extend and customize your UI development workflow. There are hundreds of addons that help you build UI faster, document component libraries, and integrate with other tools.
Once you finish developing UI components in isolation, drop them into your app. You'll have confidence that the components are hardened to support every possible edge case.
Storybook is incrementally adoptable and integrates with industry-standard tools. That means your team doesn't have to change their workflow.
Stories capture the “known good” states of UI components. They're a pragmatic, reproducible way to keep track of UI edge cases. Reuse stories to power automated tests
Storybook brings together UI, examples, and documentation in one place. That helps your team adopt existing UI patterns.
Stories show how UIs actually work not just a static design of how they're supposed to work. That keeps everyone aligned on what's currently in production.
Publish Storybook as a website for stakeholders to reference. Your team can check that the UI looks right without touching code.
Embed stories to showcase your work to teammates and the developer community. Works with the oEmbed standard.
Embed stories to showcase your work to teammates and the developer community. Works with the oEmbed standard.
import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { act } from 'react-dom/test-utils'; import { composeStories } from '@storybook/testing-react'; import * as stories from './UserCard.stories'; // Compile the "MissingProfileImage" story const { MissingProfileImage } = composeStories(stories); let container = null; beforeEach(() => { container = document.createElement('div'); document.body.appendChild(container); }); afterEach(() => { unmountComponentAtNode(container); container.remove(); container = null; }); it('renders a fallback profile image', () => { // Render the story act(() => { render(<MissingProfileImage />, container); }); // Verify the DOM structure expect(container.querySelector('img').getAttribute('src')) .toEqual( '/images/user-fallback.png' ); });
import { screen, render } from '@testing-library/react'; import { composeStories } from '@storybook/testing-react'; import { drag } from './test-utils'; import * as stories from './RangePicker.stories'; // Compile the "DefaultTimeFrame" story const { DefaultTimeFrame } = composeStories(stories); it('can adjust time range using sliders', async () => { // Render the story render(<DefaultTimeFrame />); // Execute the drag action const [ leftThumb, rightThumb, ] = await screen.findAllByRole('slider'); await drag(leftThumb, { delta: { x: -40, y: 0 } }); await drag(rightThumb, { delta: { x: 60, y: 0 } }); // Verify the time range is updated expect(leftThumb.ariaValueNow).toBe(15); expect(rightThumb.ariaValueNow).toBe(90); });
import * as React from 'react'; import { composeStories } from '@storybook/testing-react'; import { mount } from '@cypress/react'; import * as stories from './SearchInput.stories'; // Compile the "Primary" story const { Primary } = composeStories(stories); it('Should empty the search field', () => { // Render the story mount(<Primary />); // Run the test cy.get('.clear').click(); cy.get('input').then((input) => { expect(input.val()).to.be.empty; }); });
import { render, screen, fireEvent, } from '@testing-library/angular'; import { composeStory, createMountableStoryComponent, } from '@storybook/testing-angular'; import Meta, { Default as DefaultStory, } from './delete-customer.stories'; // Compile the "Default" story const Default = composeStory(DefaultStory, Meta); describe('Delete Customer Dialog', () => { it('should open a dialog', async () => { const { component, ngModule } = createMountableStoryComponent( Default({}, {} as any) ); // Render the story await render(component, { imports: [ngModule] }); // Run the test await fireEvent.click( screen.getByRole('button', { name: 'Delete Customer' }) ); expect( await screen.findByText('Are you sure?') ).toBeInTheDocument(); }); });
Add Storybook as a CI step to automate the UI development workflow. That helps you and your team ship faster with less manual work.
Got Questions?
Find Answers Below!
Our Most Frequently Asked Questions