Skip to content

Screenshot Design Best Practices ​

Screenshots are how users understand your product before (or instead of) reading documentation. A good screenshot communicates instantly. A bad one confuses or misleads.

This guide covers how to capture screenshots that genuinely help your users.

What to Screenshot ​

Focus on Actions, Not Pages ​

Users don't need a screenshot of your entire settings page. They need to see the specific toggle they're looking for.

json
// Too much - where do I look?
{
  "name": "Settings Page",
  "url": "https://app.example.com/settings"
}

// Just right - shows exactly what to click
{
  "name": "Enable Dark Mode",
  "url": "https://app.example.com/settings",
  "selector": ".appearance-settings"
}

Ask yourself: What is the user trying to do? Show them that thing, not the entire page around it.

Capture the Result, Not Just the Button ​

If you're documenting how to export data, don't just show the Export button - show the export dialog that appears, or the success message.

json
{
  "name": "Export Dialog",
  "url": "https://app.example.com/data",
  "selector": ".export-modal",
  "actions": [
    { "type": "click", "selector": "button.export" },
    { "type": "wait", "text": "Export Options" }
  ]
}

Show cause and effect. Users need to know what happens next.

Include Context ​

A button floating in isolation doesn't help. Include enough surrounding UI so users can orient themselves.

json
{
  "name": "Save Button",
  "url": "https://app.example.com/editor",
  "selector": ".toolbar",
  "padding": { "bottom": 20 }
}

The toolbar gives context. Users can find this button because they see what's around it.

Skip the Obvious ​

Not everything needs a screenshot. Login pages, basic forms, standard UI patterns - users know what these look like. Save screenshots for:

  • Unique features
  • Complex workflows
  • Non-obvious UI elements
  • Anything users ask about repeatedly

Sizing and Viewports ​

Match the Context ​

Where will this screenshot appear?

README or docs homepage: Desktop width, horizontal aspect ratio

json
{
  "viewports": ["desktop"],
  "selector": ".hero-section"
}

Mobile app documentation: Mobile viewport

json
{
  "viewports": ["mobile"]
}

Side-by-side comparison: Same viewport for both

json
{
  "name": "Before",
  "viewports": ["1200x800"]
},
{
  "name": "After",
  "viewports": ["1200x800"]
}

Generate Multiple Viewports ​

If your docs serve both desktop and mobile users, capture both:

json
{
  "name": "Dashboard",
  "url": "https://app.example.com/dashboard",
  "selector": ".main-content",
  "viewports": ["desktop", "mobile"]
}

This generates:

  • dashboard-desktop-light.png
  • dashboard-desktop-dark.png
  • dashboard-mobile-light.png
  • dashboard-mobile-dark.png

Your docs can then show the appropriate version.

Avoid Extremes ​

Very wide screenshots (2560px+) get shrunk and lose detail. Very narrow ones look stretched.

Recommended widths:

  • Desktop: 1280px (default)
  • Tablet: 768px
  • Mobile: 375px

Custom sizes work too - just keep them reasonable:

json
{
  "viewports": ["1400x900"]
}

Padding and Breathing Room ​

Add Padding Around Elements ​

Elements captured edge-to-edge look cramped. Add padding for visual breathing room:

json
{
  "selector": ".feature-card",
  "padding": { "top": 20, "right": 20, "bottom": 20, "left": 20 }
}

Or use shorthand for uniform padding:

json
{
  "selector": ".feature-card",
  "padding": { "top": 20, "right": 20, "bottom": 20, "left": 20 }
}

How much padding? Start with 16-24px. Increase for elements that need more context, decrease for inline screenshots.

Control the Background ​

By default, padding shows whatever is behind the element. Sometimes that's noise.

Solid background fill:

json
{
  "selector": ".modal",
  "padding": { "top": 40, "right": 40, "bottom": 40, "left": 40 },
  "paddingFill": "solid"
}

This fills the padding area with the element's detected background color. Clean and focused.

Transparent background:

json
{
  "paddingFill": "transparent"
}

Useful when you'll composite the screenshot onto a custom background later.

Light and Dark Mode ​

Capture Both by Default ​

Heroshot captures light and dark variants automatically (when browser.colorScheme isn't set). This is usually what you want.

json
{
  "name": "Dashboard"
}

Generates:

  • dashboard-light.png
  • dashboard-dark.png

Docs sites can then serve the right version based on the reader's preference.

When to Capture Only One ​

Sometimes one variant is enough:

  • Your app doesn't have dark mode
  • You're showing code/terminal output (typically dark)
  • The feature looks identical in both modes
json
{
  "browser": {
    "colorScheme": "light"
  }
}

Check Both Variants ​

What looks good in light mode might be invisible in dark mode (or vice versa). Always verify both:

bash
heroshot -v

Then check both output files. Light text on light backgrounds, missing borders, broken contrast - these are common issues.

Text and Data ​

Use Realistic Data ​

Screenshots with "Test User" and "Lorem ipsum" look unprofessional. Use realistic (but fictional) data:

json
{
  "textOverrides": {
    ".user-name": "Sarah Chen",
    ".project-name": "Q4 Marketing Campaign",
    ".date": "December 15, 2024"
  }
}

Good example data:

  • Real-looking names
  • Plausible numbers
  • Actual dates (not "XX/XX/XXXX")
  • Sensible text content

Sanitize Sensitive Information ​

Real data might leak in:

json
{
  "textOverrides": {
    ".email": "team@example.com",
    ".api-key": "sk-••••••••••••xxxx",
    ".revenue": "$XX,XXX"
  }
}

Or hide sections entirely:

json
{
  "actions": [{ "type": "hide", "selectors": [".billing-details", ".user-email"] }]
}

Avoid Time-Sensitive Content ​

"5 minutes ago" or "Today at 3:42 PM" dates your screenshots. Override with stable text:

json
{
  "textOverrides": {
    ".timestamp": "2 hours ago",
    ".notification-count": "3"
  }
}

Consistency Across Screenshots ​

Use a Standard Viewport ​

Pick one size and stick with it:

json
{
  "browser": {
    "viewport": { "width": 1280, "height": 800 }
  }
}

All screenshots at the same width feel cohesive.

Consistent Padding ​

Define padding once in your defaults, override only when needed:

json
{
  "outputDirectory": "screenshots",
  "screenshots": [
    {
      "name": "Feature A",
      "selector": ".feature",
      "padding": { "top": 24, "right": 24, "bottom": 24, "left": 24 }
    },
    {
      "name": "Feature B",
      "selector": ".feature",
      "padding": { "top": 24, "right": 24, "bottom": 24, "left": 24 }
    }
  ]
}

Same Scale Factor ​

Retina screenshots (2x) look crisp on high-DPI displays. Standard (1x) screenshots look crisp on standard displays. Pick one:

json
{
  "browser": {
    "deviceScaleFactor": 2
  }
}

Mixing scales makes some screenshots look blurry relative to others.

Removing Distractions ​

They're in every screenshot otherwise:

json
{
  "actions": [
    {
      "type": "hide",
      "selectors": [".cookie-banner", ".chat-widget", ".announcement-bar", "[data-intercom-widget]"]
    }
  ]
}

Dismiss Tooltips and Popovers ​

Unless you're documenting them, they're noise:

json
{
  "actions": [{ "type": "press_key", "key": "Escape" }]
}

Remove Development UI ​

Dev tools, debug panels, feature flags - hide them:

json
{
  "actions": [{ "type": "hide", "selectors": [".dev-toolbar", ".debug-panel"] }]
}

Or capture against a production-like environment.

Hide Elements Per Domain ​

Some elements are distracting on every screenshot from a domain - cookie banners, chat widgets, navigation bars. Instead of adding hide actions to each screenshot, hide them once at the domain level:

json
{
  "hiddenElements": {
    "app.example.com": [".cookie-banner", ".chat-widget"],
    "docs.example.com": [".announcement-bar"]
  }
}

These elements are hidden automatically for all screenshots captured from that domain. You can manage them visually in the editor using the eraser tool in the sidebar.

Highlighting with Annotations ​

Sometimes a screenshot needs more than just showing the UI - you need to draw attention to a specific button, field, or area. That's what annotations are for.

Draw Attention to What Matters ​

Add arrows, rectangles, and ellipses directly in the visual editor. These get baked into the screenshot, so they're always there when you regenerate.

Use annotations when:

  • Pointing out a specific button or menu item in a busy UI
  • Circling a setting that's easy to miss
  • Drawing an arrow from a label to the thing it describes
  • Highlighting the result of an action (e.g., "this notification appears")

Keep It Minimal ​

One or two annotations per screenshot. If you need more, the screenshot is probably showing too much - split it into multiple focused captures instead.

Use Consistent Styling ​

Pick a color and stroke width and stick with them across your docs. The visual editor remembers your last annotation style, which helps with consistency.

State and Interaction ​

Show Meaningful States ​

Empty states, loading states, error states - these often matter more than the happy path:

Empty state:

json
{
  "name": "No Projects Yet",
  "url": "https://app.example.com/projects",
  "selector": ".empty-state"
}

Error state:

json
{
  "name": "Form Validation",
  "actions": [
    { "type": "click", "selector": "button[type='submit']" },
    { "type": "wait", "text": "This field is required" }
  ]
}

Capture Interactions ​

Hover states, open dropdowns, expanded accordions:

json
{
  "name": "User Menu Open",
  "selector": ".user-dropdown",
  "actions": [
    { "type": "click", "selector": ".user-avatar" },
    { "type": "wait", "time": 0.3 }
  ]
}

Fill Forms Meaningfully ​

Empty forms are boring. Show them filled:

json
{
  "name": "Signup Form",
  "selector": "form.signup",
  "actions": [
    {
      "type": "fill_form",
      "fields": [
        { "selector": "#name", "value": "Sarah Chen", "fieldType": "textbox" },
        { "selector": "#email", "value": "sarah@example.com", "fieldType": "textbox" },
        { "selector": "#plan", "value": "pro", "fieldType": "combobox" }
      ]
    }
  ]
}

Organizing Your Screenshots ​

Naming Convention ​

Names become filenames. Keep them:

  • Lowercase with hyphens
  • Descriptive but concise
  • Grouped by feature
json
{
  "screenshots": [
    { "name": "dashboard-overview" },
    { "name": "dashboard-filters" },
    { "name": "dashboard-export" },
    { "name": "settings-profile" },
    { "name": "settings-billing" }
  ]
}

Results in:

heroshots/
├── dashboard-overview-light.png
├── dashboard-overview-dark.png
├── dashboard-filters-light.png
└── ...

One Config, One Purpose ​

If you have wildly different screenshot needs (marketing site vs docs vs blog), consider separate configs:

.heroshot/
├── config.json        # Main docs screenshots
├── marketing.json     # Landing page shots
└── blog.json          # Blog post images

Run them independently:

bash
heroshot -c .heroshot/marketing.json

What's Next ​