Fumadocs

Headless

Replace the UI of stories, or build your own on the headless layer.

Overview

@fumadocs/story/headless provides the state of a story, without UI:

  • the selected variant, and the preset it resolves to
  • the arguments of the rendered component, debounced so typing doesn't re-render it on every keystroke

The built-in panel is built on it, and you can swap the panel for your own.

Your own panel

defineStoryFactory() takes a WithControl component, rendered in place of the built-in panel.

lib/story.tsx
import { defineStoryFactory } from '@fumadocs/story/vite/client';
import { StoryPanel } from '@/components/story-panel';

export const { defineStory } = defineStoryFactory({ WithControl: StoryPanel });

It receives the presets of the story and the component to render.

components/story-panel.tsx
'use client';
import {
  StoryProvider,
  useStory,
  useStoryArgs,
  type WithControlProps,
} from '@fumadocs/story/headless';

export function StoryPanel(props: WithControlProps) {
  return (
    <StoryProvider {...props}>
      <Content />
    </StoryProvider>
  );
}

function Content() {
  const { presets, variant, setVariant, preset, Component } = useStory();
  const args = useStoryArgs();

  return (
    <div>
      <select value={variant} onChange={(e) => setVariant(e.target.value)}>
        {presets.map((item) => (
          <option key={item.variant} value={item.variant}>
            {item.variant}
          </option>
        ))}
      </select>
      <Component {...args} />
      <pre>{JSON.stringify(preset?.controls, null, 2)}</pre>
    </div>
  );
}
HookDescription
useStory()The presets, the selected variant with setVariant, and the Component.
useStoryArgs()The arguments of the rendered component, following the controls.

Controls are edited through @fumari/stf, the form engine <StoryProvider /> sets up. preset.controls is the type tree of the component props.

Start from the built-in panel

Writing a full control panel means handling every node kind of the type tree. Install the built-in one below and edit it instead.

Install the UI

The entire panel, including its argument controls:

npx @fumadocs/cli add fumadocs/story/controls
lib/story.tsx
import { defineStoryFactory } from '@fumadocs/story/vite/client';
import { WithControl } from '@/components/story';

export const { defineStory } = defineStoryFactory({ WithControl });

How is this guide?

Last updated on

On this page