Documenting Storybook with TS and JSDocs

/** 
 * JSDoc 
 */

JSDoc

/**
/** Adds two numbers together */
function add(a, b) {
  return a + b;
}

JSDoc

/**
 * Adds two numbers together
 * @deprecated since version 2.0
 * @param {number} a any number
 * @param {number} b any number
 */
function add(a, b) {
  return a + b;
}

JSDoc

Benefits

  • Documentation lives with the code.
  • Tools can create documentation from those code comments

Limitations

  • Advanced types are not supported
    •  e.g. generics
  • Types are only documented and not enforced
// No errors, including runtime
add("1", 2) // returns "12"

TypeScript

function add(a: number, b: number): number {
  return a + b;
}

TypeScript

Benefits

  • Advanced types are supported
  • New JavaScript features are supported through transpilation.
  • Types are enforced and will error at compilation time.

Limitations

  • TypeScript not intended to document code.   
interface Props {
  size?: number | string;
}

What is the size prop really looking for?

TS + JSDoc = 💪

/**
 * Adds two numbers together
 * @deprecated since version 2.0
 */
function add(a: number, b: number): number {
  return a + b;
}
/** 
 * JSDoc 
 */

Storybook 7 Autodocs

export interface ButtonProps {
  /**
   * Is this the principal call to action on the page?
   */
  primary?: Boolean;
  /**
   * What background color to use
   */
  backgroundColor?: string;
  /**
   * How large should the button be?
   */
  size?: "small" | "medium" | "large";
  /**
   * Button contents
   */
  label: string;
  /**
   * Optional click handler
   */
  onClick?: Function;
}
/**
 * Primary UI component for user interaction
 */
export function Button({
  primary = false,
  backgroundColor,
  size = "medium",
  label
}: ButtonProps) {
  /* implementation details here */
}

Storybook 7 Autodocs

// Button.stories.tsx
import type { Meta } from "@storybook/react";

import { Button } from "./Button";

const meta: Meta<typeof Button> = {
  component: Button,
  tags: ["autodocs"],
};

export default meta;
type Story = StoryObj<typeof Button>;

export const Primary: Story = {
  args: {
    primary: true,
    label: "Button",
  },
};

export const Secondary: Story = {
  args: {
    ...Primary.args,
    primary: false,
  },
};

Storybook 7 Autodocs

Storybook 7 Autodocs

type ColorOption = "brand" | "accent" | "alert" | "none";

export interface ButtonProps {
  /**
   * Is this the principal call to action on the page?
   */
  primary: boolean;
  /**
   * What background color option to use
   */
  backgroundColor?: ColorOption;
  /**
   * How large should the button be?
   */
  size?: "small" | "medium" | "large";
  /**
   * Button contents
   */
  label: string;
  /**
   * Optional click handler
   */
  onClick?: Function;
}
/**
 * Primary UI component for user interaction
 */
export function Button({
  primary = false,
  backgroundColor = 'none',
  size = "medium",
  label
}: ButtonProps) {
  /* implementation details here */
}

Storybook 7 Autodocs

Can be enable globally

// .storybook/main.ts
import type { StorybookConfig } from '@storybook/your-framework';

const config: StorybookConfig = {
  framework: '@storybook/your-framework',
  stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
  addons: ['@storybook/addon-essentials'],
  docs: {
    autodocs: 'tag',
    defaultName: 'Documentation',
  },
};

export default config;

Change the default template

{/* DocumentationTemplate.mdx */}

import { Meta, Title, Subtitle, Description, Primary, Controls, Stories } from '@storybook/blocks';

<Meta isTemplate />

<Title />

# Default implementation

<Primary />

## Inputs

The component accepts the following inputs (props):

<Controls />

---

## Additional variations

Listed below are additional variations of the component.

<Stories />

Change the default template

// .storybook/preview.jsx

import DocumentationTemplate from './DocumentationTemplate.mdx';

export default {
  parameters: {
    docs: {
      page: DocumentationTemplate,
    },
  },
};

Custom Doc Blocks

// .storybook/blocks/StoryName.jsx

import { useOf } from '@storybook/blocks';

export const StoryName = ({ of }) => {
  const resolvedOf = useOf(of || 'story', ['story', 'meta']);
  switch (resolvedOf.type) {
    case 'story': {
      return <h1>{resolvedOf.story.name}</h1>;
    }
    case 'meta': {
      return <h1>{resolvedOf.preparedMeta.title}</h1>;
    }
  }
  return null;
};
{/* ButtonDocs.mdx */}

import { Meta } from '@storybook/blocks';
import { StoryName } from '../.storybook/blocks/StoryName';
import * as ButtonStories from './Button.stories';

<Meta of={ButtonStories} />

{/* renders "Secondary" */}
<StoryName of={ButtonStories.Secondary} />

{/* renders "Primary" */}
<StoryName />

{/* renders "Button" */}
<StoryName of={ButtonStories} />
  • Reduced Manual Effort
  • Accurate and up-to-date documentation
  • Encourages documentation to be updated as part of the code review process
  • User has access to the best documentation where they are at, not just in Storybook

Benefits of using JSDoc, TS, and StoryBook

Documenting Storybook with TS and JSDocs

By Justin Travis Waith-Mair

Documenting Storybook with TS and JSDocs

  • 58