---
name: annotated-screenshot-technique
description: Create annotated screenshots for user manuals by injecting DOM overlays. Inject colored borders, labels, and highlights onto live web pages, capture screenshots, then clean up—without post-processing images.
risk: safe
source: community
date_added: '2026-03-04'
metadata:
  version: '1.0.0'
---

## Use this skill when

- Creating step-by-step annotated screenshots for user manuals
- Building visual guides with highlighted elements and labels
- Documenting UIs with indicators showing what to click or interact with
- Creating tutorial materials with visual annotations
- Preparing documentation screenshots that need interactive element highlights
- Demonstrating user workflows with visually marked steps
- Building accessibility guides with clear visual callouts

## Do not use this skill when

- The task is unrelated to creating annotated screenshots
- You need post-processing image editing (use image editing tools instead)
- Creating infographics from scratch (use `infographic-creator` instead)
- Writing text-only documentation
- You have no access to live web pages or browsers
- The target page doesn't support JavaScript injection

---

## Instructions

- Understand the annotation helper functions and their parameters
- Identify target DOM elements using CSS selectors or direct references
- Apply annotations with appropriate colors and positions
- Capture screenshots using suitable methods for your environment
- Always clean up annotations after capture
- Verify captured screenshots match expected visual annotations
- Test multiple environments (macOS, Windows, Linux) as needed

---

## Core Expertise

### Annotation Architecture

**Three-Step Workflow:**
1. **Inject DOM Overlays** — Add colored borders and labels via JavaScript
2. **Capture Screenshot** — Screenshot the annotated page
3. **Clean Up** — Remove all overlay elements

**Why this approach?**
- No manual image post-processing
- Annotations stay synchronized with actual page content
- Easy to regenerate if page changes
- Supports complex positioning and responsive layouts

### Core Helper Functions

#### `window.__ann()` - Annotation Creator

Creates a colored border + label overlay on top of a target element.

**Parameters:**
- `el` (HTMLElement|string) — Target element or CSS selector
- `txt` (string) — Label text (e.g., "Click this button")
- `pos` (string, optional) — Label position: `'top'` | `'bottom'` | `'left'` | `'right'` (default: `'top'`)
- `col` (string, optional) — Border & label color as CSS color value (default: `'#ff0000'`)
- `opts` (object, optional) — Advanced options:
  - `borderStyle` (string) — CSS border-style: `'solid'` | `'dashed'` | `'dotted'` (default: `'solid'`)
  - `borderWidth` (number) — Border width in px (default: `3`)
  - `padding` (number) — Extra padding around element in px (default: `4`)
  - `labelFontSize` (number) — Label font size in px (default: `13`)
  - `zIndex` (number) — z-index for overlay (default: `99999`)

**Implementation:**
```js
window.__ann = (el, txt, pos = 'top', col = '#ff0000', opts = {}) => {
  if (typeof el === 'string') el = document.querySelector(el);
  if (!el) { console.warn('__ann: element not found'); return; }

  const rect = el.getBoundingClientRect();
  const {
    borderStyle = 'solid',
    borderWidth = 3,
    padding = 4,
    labelFontSize = 13,
    zIndex = 99999
  } = opts;

  // Create border overlay
  const border = document.createElement('div');
  border.className = 'ma'; // marker class for cleanup
  Object.assign(border.style, {
    position: 'fixed',
    top: (rect.top - padding) + 'px',
    left: (rect.left - padding) + 'px',
    width: (rect.width + padding * 2) + 'px',
    height: (rect.height + padding * 2) + 'px',
    border: `${borderWidth}px ${borderStyle} ${col}`,
    borderRadius: '4px',
    pointerEvents: 'none',
    zIndex: zIndex,
    boxSizing: 'border-box',
  });
  document.body.appendChild(border);

  // Create label if text provided
  if (txt) {
    const label = document.createElement('div');
    label.className = 'ma'; // marker class for cleanup
    label.textContent = txt;
    Object.assign(label.style, {
      position: 'fixed',
      backgroundColor: col,
      color: '#fff',
      padding: '2px 8px',
      borderRadius: '3px',
      fontSize: labelFontSize + 'px',
      fontFamily: 'Arial, Helvetica, sans-serif',
      fontWeight: 'bold',
      whiteSpace: 'nowrap',
      pointerEvents: 'none',
      zIndex: zIndex + 1,
    });

    // Position label relative to border box
    const bTop = rect.top - padding;
    const bLeft = rect.left - padding;
    const bWidth = rect.width + padding * 2;
    const bHeight = rect.height + padding * 2;

    switch (pos) {
      case 'top':
        label.style.top = (bTop - 24) + 'px';
        label.style.left = bLeft + 'px';
        break;
      case 'bottom':
        label.style.top = (bTop + bHeight + 4) + 'px';
        label.style.left = bLeft + 'px';
        break;
      case 'left':
        label.style.top = bTop + 'px';
        label.style.right = (window.innerWidth - bLeft + 8) + 'px';
        break;
      case 'right':
        label.style.top = bTop + 'px';
        label.style.left = (bLeft + bWidth + 8) + 'px';
        break;
    }

    document.body.appendChild(label);
  }
};
```

#### `window.__clear()` - Cleanup Helper

Removes all annotation overlays from the page in one call.

**Implementation:**
```js
window.__clear = () => {
  document.querySelectorAll('.ma').forEach(e => e.remove());
};
```

### Step-by-Step Workflow

#### Step 1: Inject Helpers

Inject both helper functions into the browser console, DevTools, or via automation tool:

```js
// Paste both __ann() and __clear() functions above
```

#### Step 2: Identify Target Elements

Use CSS selectors or direct element references:

```js
// By CSS selector
window.__ann('.sidebar-menu-item', 'Click here');
window.__ann('#submit-btn', 'Submit button', 'bottom', '#00aa00');

// By element reference
const el = document.querySelectorAll('.nav-item')[2];
window.__ann(el, 'Third menu item');

// Via DevTools $0 reference
window.__ann($0, 'Selected element');
```

#### Step 3: Apply Annotations

Call `__ann()` for each highlight needed. Use color conventions:

| Color  | Hex       | Use for                |
|--------|-----------|------------------------|
| Red    | `#ff0000` | Primary action         |
| Orange | `#ff6600` | Secondary elements     |
| Blue   | `#0066ff` | Informational labels   |
| Green  | `#00aa00` | Success actions        |
| Purple | `#9900cc` | Optional elements      |

**Example: Multiple annotations**
```js
window.__ann('.profile-pic', 'Profile Picture', 'left', '#ff6600');
window.__ann('.username-header', 'Username & Badge', 'top', '#0066ff');
window.__ann('.bio-section', 'Account Bio', 'right', '#ff0000', {
  borderStyle: 'dashed'
});
window.__ann('.follow-btn', 'Follow button', 'bottom', '#00aa00');
```

#### Step 4: Capture Screenshot

Use the appropriate method for your environment:

**macOS `screencapture`:**
```bash
# 1. Focus browser window
osascript -e 'tell application "Google Chrome" to activate'
sleep 0.5

# 2. Get window bounds (returns left,top,right,bottom)
osascript -e 'tell application "Google Chrome" to get bounds of front window'
# Example: 0, 33, 1470, 956

# 3. Capture region (x,y,width,height)
screencapture -R 0,33,1470,923 -x /path/to/output.png
```

**Chrome DevTools:**
- `Cmd+Shift+P` → "Screenshot" → full page or current view

**Puppeteer/Playwright:**
```js
await page.screenshot({ path: 'step.png', fullPage: false });
```

**Selenium:**
```python
driver.save_screenshot('/path/to/output.png')
```

**Cypress:**
```js
cy.screenshot('step-name');
```

#### Step 5: Clean Up

Remove all overlays:

```js
window.__clear();
```

---

## Critical Implementation Details

### Position: Fixed vs Absolute
- Use `position: fixed` for viewport-relative coordinates matching `getBoundingClientRect()`
- If page scrolls, re-call `__ann()` after scrolling to update coordinates

### Z-Index Management
- Use very high z-index (99999+) to appear above modals, dropdowns, popovers
- Label uses `zIndex + 1` to stay above the border

### Pointer Events
- **CRITICAL:** Set `pointerEvents: none` on overlays so you can still interact with the page
- Without this, annotations block clicks and prevent page navigation

### Dynamic Content
- Wait for async content to load before annotating
- Use `setTimeout` or polling to verify element exists
- Verify element is in viewport before calling `__ann()`

### Multiple Windows / Displays
- `screencapture -R` captures screen region, not window
- Ensure only one relevant window is visible, or position windows correctly
- Use AppleScript to focus/position windows if needed

### Scrollable Containers
- If target is in a scrollable container, scroll to make it visible first
- `getBoundingClientRect()` works with scrolled containers correctly
- Verify element's viewport position is within screen bounds

---

## Workflow Checklist

Before and after each screenshot:

- [ ] Page is fully loaded and stable
- [ ] Target elements are visible in viewport
- [ ] `__ann()` and `__clear()` functions injected
- [ ] All required annotations applied
- [ ] No overlapping labels that obscure content
- [ ] Screenshot method verified for your environment
- [ ] Screenshot captured successfully
- [ ] Overlays cleaned up with `__clear()`
- [ ] Page remains interactive after cleanup
- [ ] Ready to proceed to next step or element

---

## Common Issues & Solutions

| Issue | Cause | Solution |
|-------|-------|----------|
| Annotations not visible | Element off-screen or z-index too low | Scroll to element, verify CSS, increase z-index |
| Label positioned incorrectly | Window resize after annotation | Re-call `__ann()` or hard-code label coords |
| Can't click on page | `pointerEvents: none` missing | Verify CSS rule is applied |
| Annotation coordinates wrong | Element scrolled in container | Verify container scroll position, re-call `__ann()` |
| Labels overlapping | Too many annotations close together | Adjust `pos` parameter or manual offset calculation |
| Element selector fails | Selector doesn't exist or is dynamic | Use DevTools Inspector, verify selector exists |
| Screenshot captures wrong window | Multiple windows overlap | Focus correct window before `screencapture -R` |
| Annotation disappears on interaction | Page reflow during user action | Re-inject before capturing next step |

---

## Advanced Customization

### Custom Label Positioning

If auto positioning doesn't work, manually set coordinates:

```js
const el = document.querySelector('#target');
const rect = el.getBoundingClientRect();
window.__ann(el, 'Label', 'top', '#ff0000');

// Then override label position
const label = document.querySelector('.ma:last-child');
label.style.top = (rect.top - 100) + 'px';
label.style.left = (rect.left + 50) + 'px';
```

### Batch Annotations

Create helper for multiple elements:

```js
function annotateList(selectorArray, color = '#ff0000') {
  selectorArray.forEach((sel, i) => {
    window.__ann(sel, `Step ${i + 1}`, 'top', color);
  });
}

annotateList(['.step1', '.step2', '.step3']);
```

### Conditional Annotations

Annotate based on page state:

```js
if (document.querySelector('.success-message')) {
  window.__ann('.success-message', 'Success!', 'top', '#00aa00');
}
```

---

## Integration with Documentation Systems

Use this technique in your workflow:

1. **Jupyter Notebooks** — Inject JS via `IPython.display.Javascript`
2. **Puppeteer/Playwright Scripts** — Automate entire screenshot sequences
3. **CI/CD Pipelines** — Generate documentation screenshots on each release
4. **Manual Workflows** — Use DevTools console for one-off screenshots
5. **Headless Browsers** — Selenium + PhantomJS for screenshots without GUI

---

## Quality Assurance

**Before publishing screenshots:**

- [ ] All annotations are visible and readable
- [ ] Colors follow the established convention
- [ ] Labels accurately describe the annotated elements
- [ ] No sensitive information visible in annotations
- [ ] Screenshot resolution matches documentation standards
- [ ] Across different browsers (Chrome, Firefox, Safari) if needed
- [ ] On different screen sizes (desktop, tablet) if needed
- [ ] Font sizes and colors have adequate contrast
