Skip to main content

Storybook 8 vs Ladle vs Histoire

·PkgPulse Team
0

TL;DR

Storybook 8 remains the standard for React component development — 30M+ downloads/week, mature ecosystem, test runner, Chromatic integration. Ladle is 10-50x faster to start (pure Vite, no custom webpack), making it compelling for large codebases where Storybook startup was painful. Histoire is Vite-native for Vue 3 — the Storybook equivalent for the Vue ecosystem. For most teams: Storybook 8 with Vite builder. For small/medium React projects prioritizing speed: Ladle.

Key Takeaways

  • Storybook 8: Vite-based by default (finally), 30M downloads/week, full ecosystem
  • Ladle: Pure Vite, no config needed, 10-50x faster startup, limited addon ecosystem
  • Histoire: Vue 3 focused, excellent Vite integration, Nuxt support
  • Speed: Ladle (1-2s start) vs Storybook 8 Vite (5-15s start) vs Storybook 8 Webpack (30s+)
  • Addons: Storybook has 1000+ addons; Ladle/Histoire have minimal ecosystems
  • Migration: Easy to migrate from Storybook to Ladle (same CSF format)

Downloads

PackageWeekly DownloadsTrend
@storybook/react~3M→ Stable
storybook~5M↑ Growing
@ladle/react~100K↑ Growing
histoire~30K↑ Growing (Vue)

Storybook 8: The Standard

npx storybook@latest init
# Storybook 8 uses Vite by default for React projects
// stories/Button.stories.tsx — Component Story Format (CSF3):
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from '../components/Button';
import { userEvent, within, expect } from '@storybook/test';

const meta: Meta<typeof Button> = {
  title: 'UI/Button',
  component: Button,
  tags: ['autodocs'],  // Auto-generate docs from props
  parameters: {
    layout: 'centered',  // Center in canvas
    backgrounds: {
      default: 'light',
    },
  },
  argTypes: {
    variant: {
      control: 'select',
      options: ['default', 'outline', 'destructive'],
    },
    size: {
      control: 'radio',
      options: ['sm', 'default', 'lg'],
    },
    onClick: { action: 'clicked' },
  },
};
export default meta;

type Story = StoryObj<typeof Button>;

export const Default: Story = {
  args: {
    children: 'Button',
    variant: 'default',
  },
};

export const AllVariants: Story = {
  render: () => (
    <div className="flex gap-2">
      <Button variant="default">Default</Button>
      <Button variant="outline">Outline</Button>
      <Button variant="destructive">Delete</Button>
    </div>
  ),
};

// Interaction test (runs in test runner):
export const ClickInteraction: Story = {
  args: { children: 'Click Me', onClick: fn() },
  play: async ({ canvasElement, args }) => {
    const canvas = within(canvasElement);
    await userEvent.click(canvas.getByRole('button'));
    await expect(args.onClick).toHaveBeenCalled();
  },
};
# Storybook 8 commands:
npx storybook dev -p 6006        # Start dev server
npx storybook build               # Static build for deployment
npx test-storybook                # Run interaction tests
npx chromatic --project-token=... # Visual regression testing

Storybook 8 New Features

Storybook 8 improvements:
  → Vite as default bundler (was webpack — 3-10x faster)
  → @storybook/test replaces testing-library as built-in
  → Vitest integration for unit + component tests
  → Portable stories (use stories in Vitest)
  → Improved TypeScript types
  → Story decorators with TypeScript inference

Ladle: Vite-Native Speed

npm install -D @ladle/react
# Zero config! Works with any Vite project.
Ladle file structure — same as Storybook!
  → Uses CSF (Component Story Format)
  → Files: *.stories.tsx
  → Same Meta and StoryObj types
// Ladle uses the exact same story format as Storybook:
// button.stories.tsx — compatible with both!

import type { StoryDefault, Story } from '@ladle/react';

export default {
  title: 'UI/Button',
} satisfies StoryDefault;

export const Default: Story = () => <Button>Click me</Button>;

export const Variants: Story = () => (
  <div className="flex gap-2">
    <Button variant="default">Default</Button>
    <Button variant="outline">Outline</Button>
    <Button variant="destructive">Delete</Button>
  </div>
);
// package.json — add Ladle scripts:
{
  "scripts": {
    "ladle": "ladle serve",    // Dev server
    "ladle:build": "ladle build"  // Static export
  }
}

Ladle Speed Comparison

Project: 150 components, 450+ stories

Storybook 8 (Vite builder):
  Cold start: 12s
  Hot reload: 200ms

Ladle:
  Cold start: 1.8s  (6.7x faster)
  Hot reload: 80ms  (2.5x faster)

For a large enterprise codebase (500 components):
  Storybook 8 Vite: 30-45s cold start
  Ladle: 3-5s cold start
  Storybook 8 Webpack: 2-4 minutes cold start

Ladle Limitations

Ladle missing vs Storybook:
  ❌ No addon ecosystem (no a11y, viewport, backgrounds addon)
  ❌ No interaction tests (no play() functions)
  ❌ No Chromatic visual regression
  ❌ No test runner
  ❌ No MDX docs pages
  ❌ Less active development

Ladle use case:
  → Dev environment only (fast iteration)
  → Pair with Vitest/Playwright for testing
  → Teams who found Storybook too slow

Histoire: Vue 3 Native

npm install -D histoire @histoire/plugin-vue
# For Nuxt:
npm install -D histoire @histoire/plugin-nuxt
// histoire.config.ts:
import { defineConfig } from 'histoire';
import { HstVue } from '@histoire/plugin-vue';

export default defineConfig({
  plugins: [HstVue()],
  setupFile: './src/histoire.setup.ts',
  theme: {
    title: 'My Component Library',
    logo: { light: '/logo.svg', dark: '/logo-dark.svg' },
  },
});
<!-- Button.story.vue — Histoire story format: -->
<script setup lang="ts">
import { reactive } from 'vue';
import Button from './Button.vue';

const state = reactive({
  variant: 'default' as 'default' | 'outline' | 'destructive',
  label: 'Click me',
  disabled: false,
});
</script>

<template>
  <Story title="UI/Button">
    <Variant title="Default">
      <Button :variant="state.variant" :disabled="state.disabled">
        {{ state.label }}
      </Button>
    </Variant>
    
    <Variant title="All Variants">
      <div class="flex gap-2">
        <Button variant="default">Default</Button>
        <Button variant="outline">Outline</Button>
        <Button variant="destructive">Destructive</Button>
      </div>
    </Variant>
    
    <template #controls>
      <HstSelect v-model="state.variant" :options="['default','outline','destructive']"
        title="Variant" />
      <HstText v-model="state.label" title="Label" />
      <HstCheckbox v-model="state.disabled" title="Disabled" />
    </template>
  </Story>
</template>

Feature Comparison

Storybook 8LadleHistoire
FrameworkReact, Vue, Svelte, etc.React onlyVue 3 focused
Startup speedMedium (Vite)FastFast (Vite)
Addons✅ 1000+❌ Minimal❌ Limited
Interaction testsplay()
Chromatic
A11y checks✅ Addon
AutodocsPartial
Portable stories✅ (v8)
Vue support@storybook/vue3✅ Native

Decision Guide

Use Storybook 8 if:
  → Design system or component library (documentation matters)
  → Need interaction tests + Chromatic visual regression
  → Team already knows Storybook
  → Multiple frameworks in one project

Use Ladle if:
  → React only project
  → Storybook startup time is a pain point
  → Dev environment usage (no need for test runner)
  → Migrating from Storybook but want speed improvement

Use Histoire if:
  → Vue 3 or Nuxt project
  → Want Vite-native Vue component development
  → More visual control panel options than Storybook Vue

Compare Storybook, Ladle, and Histoire download trends on PkgPulse.

The 2026 JavaScript Stack Cheatsheet

One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.