A Storybook add-on for live editing stories. Supports React and TypeScript.
npm install storybook-addon-code-editorA 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.
1. Install as a dev dependency:
``sh`
npm install --save-dev storybook-addon-code-editorOr yarn:
yarn add --dev storybook-addon-code-editor
2. Add storybook-addon-code-editor in your .storybook/main.ts file and ensure the staticDirs, addons, and framework fields contain the following:
`ts
// .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:
`ts
// .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.
`mdx
// MyComponent.stories.mdx
import { Playground } from 'storybook-addon-code-editor'
`
More advanced example
`mdx
// 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';
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:
`ts`
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.@types/react
React TypeScript definitions will be automatically loaded if is available.
Use the makeLiveEditStory function in traditional stories to show a code editor panel:
`ts
// 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
export default meta;
type Story = StoryObj
export const MyStory: Story = {
// Story config
};
makeLiveEditStory(MyStory, {
availableImports: { 'my-library': MyLibrary },
code: storyCode,
});
`
makeLiveEditStory options:
`ts`
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.
`ts
// .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:
`ts`
interface MonacoSetup {
monacoEnvironment?: Monaco.Environment;
onMonacoLoad?: (monaco: Monaco) => any;
}
`sh`
npm install
`sh`
npm run start-example
When making changes to the library, the server needs to be manually restarted.
`sh`
npm run test
`sh`
npm run format
`sh`
npm run build
Use 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!:
- , or fix!:, 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.