Back to Blog
Guide February 26, 2026

Automated design system documentation with screenshots

Automatically screenshot every component variant in your design system and generate visual documentation that never goes stale.

Automated Design System Documentation with Screenshots

Design system documentation falls out of sync the moment you ship a new component or update an existing variant. Screenshots get out of date. Examples become misleading. Teams stop trusting the docs.

The solution: screenshot every component variant automatically after every deploy, generate visual docs from those screenshots, commit them to your repo. Your design system docs are now always accurate.

The problem with manual docs

Design teams maintain docs like this:

  1. Build a new component variant in Storybook
  2. Manually screenshot it
  3. Update the documentation page
  4. Wait for peer review
  5. Someone updates the component, forgets to update the screenshot
  6. Docs are now lies

This happens constantly. Design system docs become outdated, then nobody trusts them, then designers and developers stop using them.

The automated approach

Instead:

  1. Push code to main
  2. Build Storybook
  3. Screenshot all components automatically
  4. Generate HTML documentation page from those screenshots
  5. Commit screenshots to repo
  6. Done

Your design system docs are always accurate because they're generated directly from the live components.

Implementation

Step 1: Get all Storybook stories

// fetch-stories.js
const fetch = require('node-fetch');
const fs = require('fs');

async function getAllStories(baseUrl) {
  let stories = [];

  try {
    const res = await fetch(`${baseUrl}/index.json`);
    const data = await res.json();
    stories = Object.values(data.stories || {});
  } catch {
    const res = await fetch(`${baseUrl}/stories.json`);
    const data = await res.json();
    stories = data || [];
  }

  return stories.map(story => ({
    id: story.id,
    title: story.title,
    kind: story.kind,
    url: `${baseUrl}/iframe.html?id=${story.id}&viewMode=story`,
  }));
}

async function main() {
  const stories = await getAllStories('http://localhost:6006');
  fs.writeFileSync('stories.json', JSON.stringify(stories, null, 2));
  console.log(`Found ${stories.length} stories`);
}

main();

Step 2: Screenshot each story

// screenshot-variants.js
const fetch = require('node-fetch');
const fs = require('fs');
const path = require('path');

const PAGEBOLT_API_KEY = process.env.PAGEBOLT_API_KEY;
const stories = JSON.parse(fs.readFileSync('stories.json'));
const docsDir = 'design-system-docs/screenshots';

if (!fs.existsSync(docsDir)) {
  fs.mkdirSync(docsDir, { recursive: true });
}

async function screenshotStory(story) {
  const res = await fetch('https://pagebolt.dev/api/v1/screenshot', {
    method: 'POST',
    headers: {
      'x-api-key': PAGEBOLT_API_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      url: story.url,
      selector: '#storybook-root',
      blockBanners: true,
      width: 1280,
      height: 720,
    }),
    timeout: 30000,
  });

  if (!res.ok) {
    console.error(`Failed: ${story.id}`);
    return null;
  }

  const buffer = await res.buffer();
  const filename = `${story.kind.replace(/\//g, '_')}.png`;
  fs.writeFileSync(path.join(docsDir, filename), buffer);
  console.log(`โœ“ ${story.title}`);
  return filename;
}

async function main() {
  const concurrency = 3;
  for (let i = 0; i < stories.length; i += concurrency) {
    const batch = stories.slice(i, i + concurrency);
    await Promise.all(batch.map(screenshotStory));
    if (i + concurrency < stories.length) {
      await new Promise(r => setTimeout(r, 1000));
    }
  }
  console.log(`Screenshots saved to ${docsDir}`);
}

main();

Step 3: Generate HTML documentation

// generate-docs-page.js
const fs = require('fs');
const path = require('path');

const stories = JSON.parse(fs.readFileSync('stories.json'));
const screenshotDir = 'design-system-docs/screenshots';

function generateHTML() {
  const groups = {};

  // Group stories by kind (e.g., "Button" or "Form/Input")
  stories.forEach(story => {
    const kind = story.kind.split('/')[0];
    if (!groups[kind]) groups[kind] = [];
    groups[kind].push(story);
  });

  let html = `<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Design System Components</title>
  <style>
    body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; padding: 40px; max-width: 1200px; margin: 0 auto; }
    h1 { margin-bottom: 30px; }
    h2 { margin-top: 40px; padding-bottom: 10px; border-bottom: 2px solid #eee; }
    .component { margin-bottom: 40px; }
    .component h3 { margin-top: 20px; margin-bottom: 10px; font-size: 16px; }
    .component img { max-width: 100%; height: auto; border: 1px solid #eee; border-radius: 4px; }
    .screenshot-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); gap: 30px; }
  </style>
</head>
<body>
  <h1>๐ŸŽจ Design System Components</h1>
  <p>Auto-generated documentation. Last updated: ${new Date().toLocaleString()}</p>
`;

  // Generate sections by component group
  Object.keys(groups).sort().forEach(kind => {
    html += `<h2>${kind}</h2>\n<div class="screenshot-grid">\n`;

    groups[kind].forEach(story => {
      const filename = `${story.kind.replace(/\//g, '_')}.png`;
      html += `
  <div class="component">
    <h3>${story.title}</h3>
    <img src="screenshots/${filename}" alt="${story.title}">
  </div>
`;
    });

    html += `</div>\n`;
  });

  html += `
  <hr>
  <p style="color: #666; font-size: 12px;">
    Documentation auto-generated from Storybook. Updated on every commit to main.
  </p>
</body>
</html>`;

  fs.writeFileSync('design-system-docs/index.html', html);
  console.log('Documentation generated: design-system-docs/index.html');
}

generateHTML();

Step 4: GitHub Actions workflow

name: Update Design System Docs

on:
  push:
    branches: [main]
    paths:
      - 'src/**'
      - '.storybook/**'

jobs:
  generate-docs:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install dependencies
        run: npm ci

      - name: Build Storybook
        run: npm run build-storybook

      - name: Serve Storybook in background
        run: npx http-server storybook-static -p 6006 &

      - name: Wait for Storybook
        run: sleep 3

      - name: Fetch stories
        run: node fetch-stories.js

      - name: Screenshot all components
        env:
          PAGEBOLT_API_KEY: ${{ secrets.PAGEBOLT_API_KEY }}
        run: node screenshot-variants.js

      - name: Generate documentation
        run: node generate-docs-page.js

      - name: Commit and push
        run: |
          git config user.name "Design System Bot"
          git config user.email "bot@pagebolt.dev"
          git add design-system-docs/
          git commit -m "chore: update design system documentation" || echo "No changes"
          git push

Why this works

Always accurate โ€” Screenshots are generated from live code, not manually maintained

Discoverable โ€” All component variants in one place, organized by component type

Collaborative โ€” Designers and developers can point to exact screenshots when discussing changes

Living documentation โ€” Every time you update a component, the docs update automatically

Git-friendly โ€” Screenshots live in your repo alongside code; no external service dependency

Real-world scenario

Your design team updates the "Button" component to have rounded corners. The commit triggers the workflow:

  1. Storybook builds
  2. All 12 button variants are screenshotted automatically
  3. HTML docs regenerate with new screenshots
  4. Changes committed to main
  5. Designers and developers see updated docs immediately

No manual work. No outdated screenshots. No "I thought the button looked different" confusion.


Try it free โ€” 100 requests/month, no credit card. โ†’ Get started in 2 minutes

Get Started Free

100 requests/month, no credit card

Screenshots, PDFs, video recording, and browser automation โ€” no headless browser to manage.

Get Your Free API Key โ†’