A next-generation tool to create blazing-fast documentation sites


You can create interactive component examples (aka stories) in pure Javascript or Typescript.

To do so, you need to create a default export with at least a title attribute and at least one named export, which is considered a story.

Our ESM story format is loosely compatible with Storybook's CSF with a few properties in common (ie title, component, decorators). However, our ESM story format is fully extensible beyond just using a select few hard-coded props (ie parameters).


The ESM format has the following advantages:

  • The documentation, testing, and design pages are created automatically.
  • Since ESM are regular javascript or typescript files, you get full support from your IDE and linting tools.

The ESM format has the following disadvantages:

  • For control over the documentation pages, you need to use advanced features such as replacing the pages and tabs.
  • You can not easily embed custom documentation into the pages

File extensions

By default, your ESM story files should end with .(story|stories).(js|jsx|ts|tsx). An example of a valid ESM typescript file would be my-story.stories.tsx;


All the document metadata is exported as the default module export (there can be only one default export per es module). For example the document(page) title, component, etc. are defined as properties:

export default {
title: 'My Story'

more: mdxjs


The stories (live examples of your components) are defined as named exports

export const myStory = <Button>some text</Button>;

Story properties

You can attach properties to you stories either directly:

export const myStory = <Button>some text</Button>;
myStory.description = 'A button example with some text'

Or using the story namespace:

export const myStory = <Button>some text</Button>;
myStory.story = {
description: 'A button example with some text',

Story templates

Using story templates you can reduce the amount of repetitive code in your ESM stories documentation. This is usually needed when you want to create separate stories for the various state values of your components.

Component-controls supports two ways of creating story templates:

  1. Use the document template field.

This is a more compact syntax, where you can assign the template to the document object.

import React from 'react';
import { Document } from '@component-controls/core';
import { ButtonProps, Button } from '../../components/button-props';
export default {
title: 'Introduction/Template doc',
component: Button,
template: props => <Button {...props} />,
} as Document<ButtonProps>;
export const John = {
controls: {
children: 'john',
type: 'reset',

live example

  1. Bind stories to a template

This syntax is slightly more verbose and is compatible with Storybook 6

import { Example, Document } from '@component-controls/core';
import { ButtonProps, Button } from '../components/Button';
export default {
title: 'Introduction/Template bind',
component: Button,
smartControls: false,
} as Document;
const ComponentTemplate: Example<ButtonProps> = props => <Button {...props} />;
export const John = ComponentTemplate.bind();
John.controls = {
children: 'john',
type: 'reset',

live example

Dynamic stories

You can create multiple stories dynamically from a function To do this, all you need is to add dynamic = true flag to the exported story:

import React from 'react';
import { theme } from '@component-controls/components';
export default {
title: Components/Button',
export const buttonColors = () => {
return Object.keys(theme.colors)
.filter(color => typeof theme.colors[color] === 'string')
.map(color => {
return {
name: color,
source: `<Button sx={{ bg: '${color}'}}>Color ${color}: ${theme.colors[color]}</Button>`,
renderFn: () => (
sx={{ bg: color }}
>{`Color ${color}: ${theme.colors[color]}`}</Button>
buttonColors.dynamic = true;

live example

dynamic stories are created at run-time (when the site loads in the browser), while in the case of gatsby, the static page routes are created under a nodejs process. This makes it so all the newly created dynamic stories for gatsby will reside under the URL of the dynamic parent story, with a story parameter: https://xxx/api/components-button--button-colors/?story=Background

Async stories

Sometimes, you might need to fetch data (or execute some other async activity) in your stories or decorators, and component-controls provides a custom useAsync hook for that purpose:

import { useAsync } from '@component-controls/core';
export const asyncFetch = () => {
// async function to fetch data
const fetchData = async () => {
const response = await fetch('//dummy.restapiexample.com/api/v1/employee/1');
const { data } = await response.json();
return data;
const { value: employee } = useAsync(fetchData);
return <h2>{`Hello, my name is ${
employee ? employee.employee_name : 'loading...'

live example

Note that you can also fetch data using react's useEffect hooks, without using useAsync.