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:
- Build a new component variant in Storybook
- Manually screenshot it
- Update the documentation page
- Wait for peer review
- Someone updates the component, forgets to update the screenshot
- 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:
- Push code to main
- Build Storybook
- Screenshot all components automatically
- Generate HTML documentation page from those screenshots
- Commit screenshots to repo
- 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:
- Storybook builds
- All 12 button variants are screenshotted automatically
- HTML docs regenerate with new screenshots
- Changes committed to main
- 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 โ