Storybook Addon Angular Manifest

Storybook addon to generate a manifest of Angular components

View on GithubNew to Storybook?Get started

Storybook Addon Angular Manifest

npm version license

A Storybook addon that builds an Angular component manifest from your stories and Compodoc documentation. It plugs into Storybook's experimental_manifests API so tools like AI assistants and the MCP server can understand your Angular component library: inputs, outputs, selectors, descriptions and ready-to-use template snippets.

Features

  • Compodoc integration — reads documentation.json and matches each story's component to its Compodoc entry to extract inputs, outputs, selector, standalone flag and change detection strategy.
  • Angular template snippets — generates a <component ...> snippet per story from the component's selector and args. When the selector has multiple comma-separated variants (e.g. button[lib-btn], a[lib-btn]), the first variant is used.
  • JSDoc tags — parses @summary, @describe/@desc and other JSDoc tags from the story's meta or the story export itself.
  • @useTemplate opt-in — use the story's parameters.docs.source.code (the same code Storybook's Docs "Show code" panel displays) as the snippet instead of the generated one, falling back to the story's render template if no docs.source.code is set.
  • Import statement resolution — infers the import specifier for each component, preferring the nearest package.json name when the component belongs to a published package.
  • Works with @storybook/angular and @storybook/angular-vite.

Requirements

  • Storybook 10.5.0 or later, with experimental_manifests support.

  • Compodoc documentation generated for your Angular project. The easiest way is to enable it in your angular.json build target:

    {
      "options": {
        "compodoc": true
      }
    }
    

    This produces a documentation.json file that the addon looks for at documentation.json or .compodoc/documentation.json relative to your Storybook working directory before Storybook starts.

    Alternatively, generate it manually:

    npx compodoc -p tsconfig.json
    

Installation

npm install --save-dev @anrouxel/storybook-addon-angular-manifest
# or
pnpm add -D @anrouxel/storybook-addon-angular-manifest
# or
yarn add -D @anrouxel/storybook-addon-angular-manifest

Then register it in .storybook/main.ts, and enable Storybook's componentsManifest feature flag so the manifest is actually written out:

import type { StorybookConfig } from '@storybook/angular-vite';

const config: StorybookConfig = {
  framework: '@storybook/angular-vite',
  addons: ['@anrouxel/storybook-addon-angular-manifest'],
  features: {
    componentsManifest: true,
  },
  // ...
};

export default config;

Running storybook build then writes the manifest to storybook-static/manifests/components.json.

How it works

For every story indexed by Storybook, the addon:

  1. Resolves the story's meta.component and its import declaration to find the Angular component/directive class.
  2. Looks it up in the Compodoc documentation.json (components, directives, pipes, injectables and classes are all searched).
  3. Builds an Angular template snippet from the component's selector, its inputsClass/outputsClass, and the story's args — statically extracted from the story file's AST.
  4. Extracts JSDoc metadata (description, summary, custom tags) from the story or component comment.
  5. Assembles everything into a manifest entry served through Storybook's experimental_manifests mechanism.

When a component can't be resolved or isn't found in the Compodoc output, the entry still appears in the manifest with an error field explaining why (e.g. missing meta.component, or the class not covered by your tsconfig.json).

Example output

Given this component and story:

@Component({
  selector: 'app-button',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: '<button [disabled]="disabled">{{ label }}</button>',
})
export class ButtonComponent {
  /** Text displayed inside the button. */
  @Input() label = 'Click me';
  /** When true the button is non-interactive. */
  @Input() disabled = false;
  /** Emitted when the user clicks the button. */
  @Output() clicked = new EventEmitter<void>();
}
export const Primary: StoryObj<ButtonComponent> = {
  args: { label: 'Click me', disabled: false },
};

the addon generates a manifest entry like this (real output, generated by building this repo's own test Storybook against the snippet above):

{
  "id": "components-button",
  "name": "ButtonComponent",
  "path": "./src/button/button.stories.ts",
  "stories": [
    {
      "id": "components-button--primary",
      "name": "Primary",
      "snippet": "<app-button label=\"Click me\" [disabled]=\"false\"></app-button>"
    }
  ],
  "import": "import { ButtonComponent } from \"@my-org/my-lib\";",
  "jsDocTags": {},
  "description": "Primary UI component for user interaction.",
  "compodoc": {
    "name": "ButtonComponent",
    "type": "component",
    "selector": "app-button",
    "standalone": true,
    "changeDetection": "ChangeDetectionStrategy.OnPush",
    "inputs": [
      { "name": "label", "type": "string", "optional": true, "defaultValue": "'Click me'", "description": "Text displayed inside the button." },
      { "name": "disabled", "type": "boolean", "optional": true, "defaultValue": "false", "description": "When true the button is non-interactive." }
    ],
    "outputs": [
      { "name": "clicked", "type": "EventEmitter<void>", "description": "Emitted when the user clicks the button." }
    ],
    "description": "Primary UI component for user interaction."
  },
  "standalone": true,
  "changeDetection": "ChangeDetectionStrategy.OnPush",
  "selector": "app-button"
}

Opting out of generated snippets

If a story already has the exact template you want exposed, tag it with @useTemplate and the addon will use that instead of generating one from the selector. It prefers parameters.docs.source.code — the same code Storybook's Docs "Show code" panel displays — and falls back to the story's render template when docs.source.code isn't set:

/**
 * Uses the Docs "Show code" template instead of the Compodoc snippet.
 * @useTemplate
 */
export const CustomTemplate: StoryObj<ButtonComponent> = {
  render: (_args) => ({
    template: `<app-button label="custom template"></app-button>`,
  }),
  parameters: {
    docs: {
      source: {
        code: `<app-button label="custom template"></app-button>`,
      },
    },
  },
};

API

The package also exposes its manifest type for consumers building on top of it:

import type { AngularComponentManifest } from '@anrouxel/storybook-addon-angular-manifest';

Troubleshooting

"No Compodoc documentation.json found" — enable compodoc: true in your angular.json build options, or run npx compodoc -p tsconfig.json before starting Storybook.

"We could not detect the component from your story file" — make sure your story's default export sets component (e.g. meta.component = ButtonComponent).

"<Component> was not found in the Compodoc documentation" — check that the component's source file is included in the tsconfig.json used to generate Compodoc's documentation.

Contributing

pnpm install
pnpm build       # build the addon
pnpm test        # run the test suite
pnpm check       # lint with Biome

License

MIT © Antonin Rouxel