Storybook Docs

Custom Titles for Storybook Docs?

Mostafa Sherif
3 min readJan 26, 2023

Remember last time when I talked about StoryIndexers being one of the hidden features of Storybook. I actually added proper documentation for that feature on the official Storybook docs, you can check it out now here 👇.

One of the issues I was still facing was the story titles in the sidebar, where Storybook was insisting on using the file name instead of offering me a way to customize the name. Given that in .mdx we can use the following to set the title

<Meta title="Legendary Component" />

I wanted to introduce more ways for customizing the title in the form of

  • Custom titles array passed to the addon thru main.js
  • Title in an HTML tag in .md or .html file
  • Title in a Meta tag in .md or .html file
  • Title in a Markdown comment {/*title:"Something Something"*/}

Comes into the picture, another hidden feature by Storybook 😄. This one in the form of makeTitle method which is passed to the compile method of @storybook/mdx2-csf library.

How `makeTitle` works?

Simple enough makeTitle either calls a custom method you devleop, or use the autoTitle. What is autoTitle you say? That’s the mechanism by which Storybook determines the title of a story, long story short (see what I did there 😄), it reads the title of the current sidebar item based on:

  • title property of the default export meta
  • component property of the default export meta
  • name of the story export, e.g. export const Primary: Story = {};
  • the actual name property of the story export (formerly storyName)
  • title attribute of the Meta tag in MDX
  • name attribute of the Story tag in MDX
  • When using of={ComponentStories} attribute on the Meta tag, ComponentStories has to be compiled first, then the title will be retrieved from there.

Yep, quite a tall order for processing a title of a sidebar item. Nevertheless, you can override the makeTitle method to make it return the title you want. So here is how mine looks like:

import { resolve } from 'path';
import { readFileSync } from 'fs';

export const getMakeTitle = (fileName, addonOptions) => {
const addonFiles = addonOptions.titles.map(key => resolve(addonOptions.configDir, key));
return userTitle => {
const shortName = fileName.split('/').pop().split('.').shift();
const titleKey = addonFiles.find(key => key === fileName);
const content = readFileSync(fileName, 'utf8').toString();

const markdownTitle = (content.match(/\{\/\*.*title:[`'"""''„"«»](.*)[`'"""''„"«»]\*\/\}/i) || [])[1];
const metaTitle = (content.match(/<meta title=[`'"""''„"«»](.*)[`'"""''„"«»] \/>/i) || [])[1];
const htmlTitle = (content.match(/<title>(.*)<\/title>/i) || [])[1];

if (titleKey) {
return titles[titleKey];
} else if (markdownTitle) {
return markdownTitle;
} else if (metaTitle) {
return metaTitle;
} else if (htmlTitle) {
return htmlTitle;
}
return userTitle || shortName;
};
};

And simply pass it along your StoryIndexer like so:

import { readFileSync } from 'fs';
import { makeTitle } from './makeTitle';

export const storyIndexer = (indexers = [], addonOptions = {}) => {
return [
{
test: /\.(md|html)$/,
indexer: async (fileName, compileOptions) => {
const code = readFileSync(fileName, 'utf-8');
return loadCsf(code, {
...compileOptions,
fileName,
makeTitle: getMakeTitle(fileName, addonOptions),
});
},
},
...indexers,
];
};

Of course my final code won’t be looking like that, but I wanted to show the concepts for simplicity.

On other news, we moved from the name @sheriffmoose/storybook-md to @storybook-extras/markdown which is now available in v0.0.21 with the updates above. Check it out.

--

--