Story
Display components with controls.
Callout
Introduction
Fumadocs Story displays and documents components. It is a simple, docs-focused alternative to Storybook, mainly designed for component libraries.
If you are new to the concept of Story, this is a short explanation from Storybook docs:
A story captures the rendered state of a UI component. It's an object with annotations that describe the component's behavior and appearance given a set of arguments.
When to use it over Storybook?
Fumadocs Story is not a replacement for Storybook. We still recommend Storybook for proper UI testing.
Installation
npm i @fumadocs/storyAdd the Tailwind CSS preset:
@import '@fumadocs/story/css/preset.css';Add the Vite plugin:
import { defineConfig } from 'vite';
import story from '@fumadocs/story/vite';
export default defineConfig({
plugins: [story()],
});Create a Story
Create a story factory to store your global settings:
import { defineStoryFactory } from '@fumadocs/story/vite/client';
export const { defineStory } = defineStoryFactory();Now create your first story:
import { defineStory } from '@/lib/story';
import { MyComponent } from './my-component';
export const story = defineStory({
Component: MyComponent,
args: {
// default props (recommended)
initial: {},
},
});The .story suffix is required
The plugin only transforms files matching *.story.{js,jsx,ts,tsx}. Change it with the filter option of the Vite plugin.
Render a Story
Render the story in your MDX files:
import { story } from '@/components/my-component.story';
<story.WithControl />Configurations
Variants
You can provide multiple variants of the component with args:
import { defineStory } from '@/lib/story';
import { Callout } from '@/components/callout';
export const story = defineStory({
Component: Callout,
args: [
{
variant: 'Default',
initial: {
title: 'This is a Callout',
},
},
{
variant: 'Warning',
initial: {
title: 'This is a Callout',
},
// fixed values for props
fixed: {
type: 'warning',
},
},
],
});controls
Controls are generated from the component props. Specify the control nodes to replace them:
import { defineStory } from '@/lib/story';
import { GraphView } from '@/components/graph-view';
export const story = defineStory({
Component: GraphView,
args: {
controls: {
node: {
type: 'object',
properties: [],
},
},
},
});frame
Components filling the viewport, like layouts, can be rendered in an iframe. The story panel scales the frame down to fit, with a button to open it in fullscreen.
import { defineStory } from '@/lib/story';
import { MyLayout } from './my-layout.lazy';
import { Window } from './window';
export const story = defineStory({
Component: MyLayout,
frame: {
src: '/story/my-layout',
// viewport size, optional
width: 1280,
height: 800,
// wraps the frame, e.g. in a window, optional
Container: Window,
},
});Create a page at src to render the component. It receives the arguments from the story panel:
import { createFileRoute } from '@tanstack/react-router';
import { story } from '@/components/my-layout.story';
export const Route = createFileRoute('/story/my-layout')({
component: () => <story.Frame />,
});The story loads its component on every page that renders it. Pass a lazy component instead, so only the frame loads it:
import { lazy } from 'react';
export const MyLayout = lazy(() =>
import('./my-layout').then((mod) => ({ default: mod.MyLayout })),
);Good to know
- The page must be on the same origin as your docs.
- Arguments are sent with
postMessage(), so they must be serializable (no functions or JSX).
Internationalization
Assuming you have configured Internationalization at UI level:
import { defineI18n } from 'fumadocs-core/i18n';
import { uiTranslations } from 'fumadocs-ui/i18n';
import { storyTranslations } from '@fumadocs/story/i18n';
const i18n = defineI18n({
languages: ['en', 'cn'],
defaultLanguage: 'en',
});
export const translations = i18n
.translations()
.extend(uiTranslations())
.extend(storyTranslations())
.add({
cn: {
displayName: 'Chinese',
'Add Item(story arguments form)': '新增項目',
},
});See Translations for more details.
How is this guide?
Last updated on
