title |
---|
Write an addon |
Storybook addons are a powerful way to extend Storybook's functionality and customize the development experience. They can be used to add new features, customize the UI, or integrate with third-party tools.
This reference guide is to help you develop a mental model for how Storybook addons work by building a simple addon based on the popular Outline addon. Throughout this guide, you'll learn how addons are structured, Storybook's APIs, how to test your addon locally, and how to publish it.
There are two main categories of addons, each with its role:
- UI-based: These addons are responsible for customizing the interface, enabling shortcuts for common tasks, or displaying additional information in the UI.
- Presets: These are pre-configured settings or configurations that enable developers to quickly set up and customize their environment with a specific set of features, functionality, or technology.
The addon built in this guide is a UI-based addon, specifically a toolbar addon, enabling users to draw outlines around each element in the story through a shortcut or click of a button. UI addons can create other types of UI elements, each with its function: panels and tabs, providing users with various ways to interact with the UI.
<CodeSnippets paths={[ 'common/storybook-addon-toolkit-toolbar-example.toolbar.ts.mdx', 'common/storybook-addon-toolkit-panel-example.panel.ts.mdx', 'common/storybook-addon-toolkit-tab-example.tab.ts.mdx', ]} />
To create your first addon, you're going to use the Addon Kit, a ready-to-use template featuring all the required building blocks, dependencies and configurations to help you get started building your addon. In the Addon Kit repository, click the Use this template button to create a new repository based on the Addon Kit's code.
Clone the repository you just created and install its dependencies. When the installation process finishes, you will be prompted with questions to configure your addon. Answer them, and when you're ready to start building your addon, run the following command to start Storybook in development mode and develop your addon in watch mode:
<CodeSnippets paths={[ 'common/storybook-addon-run-dev-mode.yarn.js.mdx', 'common/storybook-addon-run-dev-mode.npm.js.mdx', 'common/storybook-addon-run-dev-mode.pnpm.js.mdx', ]} />
The Addon Kit uses Typescript by default. If you want to use JavaScript instead, you can run the eject-ts
command to convert the project to JavaScript.
Addons built in the Storybook ecosystem rely on tsup, a fast, zero-config bundler powered by esbuild to transpile your addon's code into modern JavaScript that can run in the browser. Out of the box, the Addon Kit comes with a pre-configured tsup
configuration file that you can use to customize the build process of your addon.
When the build scripts run, it will look for the configuration file and pre-bundle the addon's code based on the configuration provided. Addons can interact with Storybook in various ways. They can define presets to modify the configuration, add behavior to the manager UI, or add behavior to the preview iframe. These different use cases require different bundle outputs because they target different runtimes and environments. Presets are executed in a Node environment. Storybook's manager and preview environments provide certain packages in the global scope, so addons don't need to bundle them or include them as dependencies in their package.json
file.
The tsup
configuration handles these complexities by default, but you can customize it according to their requirements. For a detailed explanation of the bundling techniques used, please refer to the README of the addon-kit, and check out the default tsup
configuration here.
By default, code for the UI-based addons is located in one of the following files, depending on the type of addon built: src/Tool.tsx
, src/Panel.tsx
, or src/Tab.tsx
. Since we're building a toolbar addon, we can safely remove the Panel
and Tab
files and update the remaining file to the following:
<CodeSnippets paths={[ 'common/storybook-addon-tool-initial-setup.ts.mdx', ]} />
Going through the code blocks in sequence:
// src/Tool.tsx
import { useGlobals, useStorybookApi } from '@storybook/manager-api';
import { IconButton } from '@storybook/components';
import { LightningIcon } from '@storybook/icons';
The useGlobals
and useStorybookApi
hooks from the manager-api
package are used to access the Storybook's APIs, allowing users to interact with the addon, such as enabling or disabling it.
The IconButton
or Button
component from the @storybook/components
package can be used to render the buttons in the toolbar. The @storybook/icons
package provides a large set of appropriately sized and styled icons to choose from.
// src/Tool.tsx
export const Tool = memo(function MyAddonSelector() {
const [globals, updateGlobals] = useGlobals();
const api = useStorybookApi();
const isActive = [true, 'true'].includes(globals[PARAM_KEY]);
const toggleMyTool = useCallback(() => {
updateGlobals({
[PARAM_KEY]: !isActive,
});
}, [isActive]);
useEffect(() => {
api.setAddonShortcut(ADDON_ID, {
label: 'Toggle Addon [8]',
defaultShortcut: ['8'],
actionName: 'myaddon',
showInMenu: false,
action: toggleMyTool,
});
}, [toggleMyTool, api]);
return (
<IconButton key={TOOL_ID} active={isActive} title="Enable my addon" onClick={toggleMyTool}>
<LightningIcon />
</IconButton>
);
});
The Tool
component is the entry point of the addon. It renders the UI elements in the toolbar, registers a keyboard shortcut, and handles the logic to enable and disable the addon.
Moving onto the manager, here we register the addon with Storybook using a unique name and identifier. Since we've removed the Panel
and Tab
files, we'll need to adjust the file to only reference the addon we're building.
<CodeSnippets paths={[ 'common/storybook-addon-manager-initial-state.ts.mdx', ]} />
Notice the match
property. It allows you to control the view mode (story or docs) and tab (the story canvas or custom tabs) where the toolbar addon is visible. For example:
({ tabId }) => tabId === 'my-addon/tab'
will show your addon when viewing the tab with the IDmy-addon/tab
.({ viewMode }) => viewMode === 'story'
will show your addon when viewing a story in the canvas.({ viewMode }) => viewMode === 'docs'
will show your addon when viewing the documentation for a component.({ tabId, viewMode }) => !tabId && viewMode === 'story'
will show your addon when viewing a story in the canvas and not in a custom tab (i.e. whentabId === undefined
).
Run the start
script to build and start Storybook and verify that the addon is registered correctly and showing in the UI.
In Storybook, applying styles for addons is considered a side-effect. Therefore, we'll need to make some changes to our addon to allow it to use the styles when it is active and remove them when it's disabled. We're going to rely on two of Storybook's features to handle this: decorators and globals. To handle the CSS logic, we must include some helper functions to inject and remove the stylesheets from the DOM. Start by creating the helper file with the following content:
<CodeSnippets paths={[ 'common/storybook-addon-css-helpers.ts.mdx', ]} />
Next, create the file with the styles we want to inject with the following content:
<CodeSnippets paths={[ 'common/storybook-addon-css-example.ts.mdx', ]} />
Since the addon can be active in both the story and documentation modes, the DOM node for Storybook's preview iframe
is different in these two modes. In fact, Storybook renders multiple story previews on one page when in documentation mode. Therefore, we'll need to choose the correct selector for the DOM node where the styles will be injected and ensure the CSS is scoped to that particular selector. That mechanism is provided as an example within the src/withGlobals.ts
file, which we'll use to connect the styling and helper functions to the addon logic. Update the file to the following:
<CodeSnippets paths={[ 'common/storybook-addon-use-global.ts.mdx', ]} />
Storybook addons, similar to most packages in the JavaScript ecosystem, are distributed as NPM packages. However, they have specific criteria that need to be met to be published to NPM and crawled by the integration catalog:
- Have a
dist
folder with the transpiled code. - A
package.json
file declaring:- Module-related information
- Integration catalog metadata
The first category of metadata is related to the addon itself. This includes the entry for the module, which files to include when the addon is published. And the required configuration to integrate the addon with Storybook, allowing it to be used by its consumers.
{
"exports": {
".": {
"types": "./dist/index.d.ts",
"node": "./dist/index.js",
"require": "./dist/index.js",
"import": "./dist/index.mjs"
},
"./manager": "./dist/manager.mjs",
"./preview": "./dist/preview.mjs",
"./package.json": "./package.json"
},
"main": "dist/index.js",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"files": ["dist/**/*", "README.md", "*.js", "*.d.ts"],
"devDependencies": {
"@storybook/blocks": "^7.0.0",
"@storybook/components": "^7.0.0",
"@storybook/core-events": "^7.0.0",
"@storybook/manager-api": "^7.0.0",
"@storybook/preview-api": "^7.0.0",
"@storybook/theming": "^7.0.0",
"@storybook/types": "^7.0.0"
},
"bundler": {
"exportEntries": ["src/index.ts"],
"managerEntries": ["src/manager.ts"],
"previewEntries": ["src/preview.ts"]
}
}
The second metadata category is related to the integration catalog. Most of this information is already pre-configured by the Addon Kit. However, items like the display name, icon, and frameworks must be configured via the storybook
property to be displayed in the catalog.
{
"name": "my-storybook-addon",
"version": "1.0.0",
"description": "My first storybook addon",
"author": "Your Name",
"storybook": {
"displayName": "My Storybook Addon",
"unsupportedFrameworks": ["react-native"],
"icon": "https://yoursite.com/link-to-your-icon.png"
},
"keywords": ["storybook-addons", "appearance", "style", "css", "layout", "debug"]
}
The storybook
configuration element includes additional properties that help customize the addon's searchability and indexing. For more information, see the Integration catalog documentation.
One essential item to note is the keywords
property as it maps to the catalog's tag system. Adding the storybook-addons
ensures that the addon is discoverable in the catalog when searching for addons. The remaining keywords help with the searchability and categorization of the addon.
Once you're ready to publish your addon to NPM, the Addon Kit comes pre-configured with the Auto package for release management. It generates a changelog and uploads the package to NPM and GitHub automatically. Therefore, you need to configure access to both.
- Authenticate using npm adduser
- Generate a access token with both
read
andpublish
permissions. - Create a personal access token with
repo
andworkflow
scoped permissions. - Create a
.env
file in the root of your project and add the following:
GH_TOKEN=value_you_just_got_from_github
NPM_TOKEN=value_you_just_got_from_npm
Next, run the following command to create labels on GitHub. You'll use these labels to categorize changes to the package.
npx auto create-labels
Finally, run the following command to create a release for your addon. This will build and package the addon code, bump the version, push the release into GitHub and npm, and generate a changelog.
<CodeSnippets paths={[ 'common/storybook-addon-release.yarn.js.mdx', 'common/storybook-addon-release.npm.js.mdx', 'common/storybook-addon-release.pnpm.js.mdx', ]} />
By default, the Addon Kit comes pre-configured with a GitHub Actions workflow, enabling you to automate the release management process. This ensures that the package is always up to date with the latest changes and that the changelog is updated accordingly. However, you'll need additional configuration to use your NPM and GitHub tokens to publish the package successfully. In your repository, click the Settings tab, then the Secrets and variables dropdown, followed by the Actions item. You should see the following screen:
Then, click the New repository secret, name it NPM_TOKEN
, and paste the token you generated earlier. Whenever you merge a pull request to the default branch, the workflow will run and publish a new release, automatically incrementing the version number and updating the changelog.
Learn more about the Storybook addon ecosystem
- Types of addons for other types of addons
- Writing addons for the basics of addon development
- Presets for preset development
- Integration catalog for requirements and available recipes
- API reference to learn about the available APIs