Projects Blog

How to add a script tag in a single story in Storybook

There are certain rare scenarios where it becomes necessary to import a JavaScript script into a specific story view when working with Storybook.

Since I couldn’t find any solutions online, I’m publishing this article to address the needs of those who have asked similar unanswered questions.

I have also created a GitHub repository with a working demo for this resolution.

Let’s break this down step by step.

Locate your script

For this example, I created a simple JavaScript file located at stories/scripts/custom-script.js with the following content:

stories/scripts/custom-script.js
alert('Hello!')

Make the scripts folder available to Storybook

Next, we need to configure Storybook to load the files from the scripts folder.

To achieve this, simply add the staticDirs field in your Storybook configuration, located at storybook/main.js, specifying the path to your folder.

.storybook/main.js
/** @type { import('@storybook/html-vite').StorybookConfig } */
const config = {
stories: ['../stories/**/*.mdx', '../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
addons: [
'@storybook/addon-essentials',
'@chromatic-com/storybook',
'@storybook/addon-interactions',
],
framework: {
name: '@storybook/html-vite',
options: {},
},
staticDirs: ['../stories/scripts'],
}
export default config

Adding custom head scripts for component views

In some cases, you may need to include custom JavaScript scripts for for all views of a specific component.

Simply, add the following decorator in the default export.

stories/Button.stories.js
export default {
title: 'Example/Button',
tags: ['autodocs'],
render: ({ label, ...args }) => {
// You can either use a function to create DOM element<a href="https://github.com/victor-lillo/storybook-custom-head-scripts-single-story/tree/main/stories/scripts" target="_blank" rel="noreferrer">`stories/scripts/custom-script.js`</a>s or use a plain html string!
// return `<div>${label}</div>`;
return createButton({ label, ...args })
},
argTypes: {
backgroundColor: { control: 'color' },
label: { control: 'text' },
onClick: { action: 'onClick' },
primary: { control: 'boolean' },
size: {
control: { type: 'select' },
options: ['small', 'medium', 'large'],
},
},
// Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args
args: { onClick: fn() },
// Dynamically inject head elements for this story
decorators: [
(Story) => {
const script = document.createElement('script')
script.src = '/custom-script.js'
script.type = 'module'
document.head.appendChild(script)
return Story()
},
],
}
export const Primary = {
args: {
primary: true,
label: 'Button',
},
}
export const Secondary = {
args: {
label: 'Button',
},
}

Adding custom head scripts for a single story view

Instead, there are times when we only need to add the script to a specific view of a component.

As in the previous example, add the decorator, but this time into the specific view export.

stories/Page.stories.js
export default {
title: 'Example/Page',
// ...
}
export const LoggedOut = {}
export const LoggedIn = {
// ...
}
export const withScript = {
// Dynamically inject head elements for this story
decorators: [
(Story) => {
const script = document.createElement('script')
script.src = '/custom-script.js'
script.type = 'module'
document.head.appendChild(script)
return Story()
},
],
}

I hope you found this article useful.

Happy coding! 🚀