A Storybook add-on for live editing stories. Supports React and TypeScript.
See an example project using this add-on.
This is a Storybook addon that enables live editing of React components with real-time previews. Think of it like a lightweight CodeSandbox, directly in stories or MDX pages.
It uses Monaco Editor (VS Code for the browser) for an excellent TypeScript editing experience.
- Install as a dev dependency:
npm install --save-dev storybook-addon-code-editor
# Or yarn:
yarn add --dev storybook-addon-code-editor- Add
storybook-addon-code-editorin your.storybook/main.tsfile and ensure thestaticDirs,addons, andframeworkfields contain the following:
// .storybook/main.ts
import type { StorybookConfig } from '@storybook/react-vite';
import { getCodeEditorStaticDirs } from 'storybook-addon-code-editor/getStaticDirs';
const config: StorybookConfig = {
staticDirs: [...getCodeEditorStaticDirs(__filename)],
addons: ['storybook-addon-code-editor'],
framework: {
name: '@storybook/react-vite',
options: {},
},
};
export default config;About `staticDirs`
staticDirs sets a list of directories of static files to be loaded by Storybook.
The editor (monaco-editor) requires these extra static files to be available at runtime.
Additional static files can be added using the getExtraStaticDir helper from storybook-addon-code-editor/getStaticDirs:
// .storybook/main.ts
import {
getCodeEditorStaticDirs,
getExtraStaticDir,
} from 'storybook-addon-code-editor/getStaticDirs';
const config: StorybookConfig = {
staticDirs: [
...getCodeEditorStaticDirs(__filename),
// files will be available at: /monaco-editor/esm/*
getExtraStaticDir('monaco-editor/esm'),Important:
@storybook/react-vite is the only supported framework at this time.
Use the Playground component in MDX format.
// MyComponent.stories.mdx
import { Playground } from 'storybook-addon-code-editor'
<Playground code="export default () => <h1>Hello</h1>" />More advanced example
// MyComponent.stories.mdx
import { Playground } from 'storybook-addon-code-editor';
import \* as MyLibrary from './index';
import storyCode from './MyStory.source.tsx?raw';
// TypeScript might complain about not finding this import or
// importing things from .d.ts files wihtout `import type`.
// Ignore this, we need the string contents of this file.
// @ts-ignore
import MyLibraryTypes from '../dist/types.d.ts?raw';
<Playground
availableImports={{ 'my-library': MyLibrary }}
code={storyCode}
height="560px"
id="unique id used to save edited code until the page is reloaded"
modifyEditor={(monaco, editor) => {
// editor docs: https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IStandaloneCodeEditor.html
// monaco docs: https://microsoft.github.io/monaco-editor/api/modules/monaco.html
editor.getModel().updateOptions({ tabSize: 2 });
monaco.editor.setTheme('vs-dark');
monaco.languages.typescript.typescriptDefaults.addExtraLib(
MyLibraryTypes,
'file:///node_modules/my-library/index.d.ts',
);
}}
/>Playground props:
interface PlaygroundProps {
availableImports?: {
[importSpecifier: string]: {
[namedImport: string]: any;
};
};
code?: string;
defaultEditorOptions?: Monaco.editor.IEditorOptions;
height?: string;
id?: string | number | symbol;
modifyEditor?: (monaco: Monaco, editor: Monaco.editor.IStandaloneCodeEditor) => any;
}React is automatically imported if code does not import it.
React TypeScript definitions will be automatically loaded if @types/react is available.
Use the makeLiveEditStory function in traditional stories to show a code editor panel:
// MyComponent.stories.ts
import type { Meta, StoryObj } from '@storybook/react';
import { makeLiveEditStory } from 'storybook-addon-code-editor';
import * as MyLibrary from './index';
import storyCode from './MyStory.source.tsx?raw';
const meta = {
// Story defaults
} satisfies Meta<typeof MyLibrary.MyComponent>;
export default meta;
type Story = StoryObj<typeof meta>;
export const MyStory: Story = {
// Story config
};
makeLiveEditStory(MyStory, {
availableImports: { 'my-library': MyLibrary },
code: storyCode,
});makeLiveEditStory options:
interface LiveEditStoryOptions {
availableImports?: {
[importSpecifier: string]: {
[namedImport: string]: any;
};
};
code: string;
modifyEditor?: (monaco: Monaco, editor: Monaco.editor.IStandaloneCodeEditor) => any;
defaultEditorOptions?: Monaco.editor.IEditorOptions;
}setupMonaco allows customization of monaco-editor.
Use this in your .storybook/preview.ts to add type definitions or integrations.
Check out examples of monaco-editor with different configurations.
// .storybook/preview.ts
import { setupMonaco } from 'storybook-addon-code-editor';
setupMonaco({
// https://microsoft.github.io/monaco-editor/typedoc/interfaces/Environment.html
monacoEnvironment: {
getWorker(moduleId, label) {
...
},
},
// onMonacoLoad is called when monaco is first loaded, before an editor instance is created.
onMonacoLoad(monaco) {
...
},
});setupMonaco options:
interface MonacoSetup {
monacoEnvironment?: Monaco.Environment;
onMonacoLoad?: (monaco: Monaco) => any;
}npm installnpm run start-exampleWhen making changes to the library, the server needs to be manually restarted.
npm run testnpm run formatnpm run buildUse conventional commits to allow automatic versioned releases.
fix:represents bug fixes, and correlates to a SemVer patch.feat:represents a new feature, and correlates to a SemVer minor.feat!:, orfix!:,refactor!:, etc., represent a breaking change (indicated by the !) and will result in a SemVer major.
The automated release-please PR to the main branch can be merged to deploy a release.