build69
build69  /  Components  /  Media & Files  /  ImageAnnotator

ImageAnnotator

v1.0.3Updated 19 Oct 2026

Mark up an image — arrow, box, freehand and text over a screenshot — with select / move / recolour / delete of existing marks, undo and redo, and a flattened PNG export at the image's natural size (fb_f8a55acb). Marks are DATA (AnnotationShape[] in natural pixels), not baked pixels, so they can be stored, re-opened and edited later; mark sizes are scaled to the capture's resolution, so a HiDPI screenshot shown small still exports legibly.

Preview

Live · 10 stories
DefaultOpen ↗
▸Why this is recommendedannotateannotationmarkupscreenshot+7

The default, out-of-the-box ImageAnnotator.

Recommended for+14
  • ✓Mark up a screenshot before filing a bug report
  • ✓Let support staff draw on the customer's capture when they answer
  • ✓Circle the problem area on a photo or scan and attach the flattened image
Tags+6
annotateannotationmarkupscreenshotdrawarrowcanvasbug-reportexportpngdefault
Features+4
  • Marks are data: AnnotationShape[] (arrow / box / freehand / text) in the image's NATURAL pixels — controlled value + onChange, or uncontrolled defaultValue
  • Resolution scaling: sizes are picked in display px and stored × naturalWidth / displayedWidth, so a 3240px capture shown at 660px exports with legible marks
  • Select, drag to move, recolour, resize and delete an existing mark; Delete key removes it, Escape cancels the gesture or the selection
  • Undo / redo over a bounded snapshot stack (historyLimit), including moves and text edits
  • Text marks type in place: a caret inside the surface at the click point, rendered as SVG text — never innerHTML
  • flatten() / toDataURL() through the ref handle, plus standalone flattenImageAnnotations(src, shapes) for exporting stored marks without mounting
  • Cross-origin safety: crossOrigin prop, and a tainted canvas surfaces a clear error (AnnotationExportError with reason "tainted") instead of a silent failure
  • Pointer events (mouse, touch and pen) with pointer capture and touch-action none while drawing
  • States: loading image, image error with retry, empty (no src), readOnly (marks, no toolbar) and disabled
  • i18n labels prop (ImageAnnotatorLabels — tools, swatch names, sizes, errors; markCount is a {count} template)
Not recommended for−2
  • ✕Capturing a signature (use SignaturePad)
  • ✕General image editing — crop, filters, layers
  • ✕Annotating a PDF page by page (this marks up one raster image)

Install

Ask your agent, or call the MCP tool directly. The resolved bundle is written into your project.

get_registry_item({ name: "ImageAnnotator" })

Usage

import { ImageAnnotator, type ImageAnnotatorHandle, type AnnotationShape } from '@/components/media/image-annotator';

const editor = React.useRef<ImageAnnotatorHandle>(null);
const [marks, setMarks] = React.useState<AnnotationShape[]>([]);

<ImageAnnotator ref={editor} src={capture.url} alt="Screenshot" value={marks} onChange={setMarks} />;

// on submit: composite the marks at the capture's natural size
const png = await editor.current!.flatten();

Tick variants in the left menu — each adds a tab with that variant’s real story source, the same code the preview above renders.

Props

PropTypeRequired
src

Image URL (screenshot / capture). Omit for the empty state

string—
alt

Alternative text for the image

stringyes
crossOrigin

CORS mode — REQUIRED to export a cross-origin image (its server must send Access-Control-Allow-Origin); without it the canvas is tainted and export reports reason "tainted"

'anonymous' | 'use-credentials'—
value

Controlled marks, in the image's natural pixels

AnnotationShape[]—
defaultValue

Uncontrolled initial marks

AnnotationShape[]—
onChange

Fires with the full next array after every committed change (draw, move, recolour, delete, undo, redo)

(shapes: AnnotationShape[]) => void—
tool

Controlled active tool

'select' | 'arrow' | 'box' | 'freehand' | 'text'—
defaultTool

Uncontrolled initial tool (default box)

'select' | 'arrow' | 'box' | 'freehand' | 'text'—
onToolChange

Fires when the tool changes (toolbar or the v / a / b / p / t shortcuts)

(tool: AnnotationTool) => void—
colors

Ink swatches [{ value, label }] — mark colours are data baked into the export, so they are literal colours by design

AnnotationColorOption[]—
defaultColor

Initial ink colour (defaults to the first swatch)

string—
defaultMarkSize

Initial stroke / font size in DISPLAY px, scaled to natural px on creation (default md)

'sm' | 'md' | 'lg'—
onSelectionChange

Fires when the selected mark changes

(id: string | null) => void—
readOnly

Show the marks with no toolbar and no editing (how a filed report reads)

boolean—
disabled

Keep the toolbar visible but block every edit (e.g. while submitting)

boolean—
historyLimit

Maximum undo steps kept (default 100)

number—
toolbarEnd

Extra controls at the end of the toolbar, e.g. an Export button calling ref.flatten()

React.ReactNode—
emptyState

Rendered instead of the default text when there is no src

React.ReactNode—
labels

Override any built-in string (tools, colours, sizes, undo/redo, loading/error/empty, export errors) for i18n

Partial<ImageAnnotatorLabels>—
ref

Imperative handle: flatten(), toDataURL(), undo(), redo(), clear(), deleteSelected(), getShapes(), focus()

React.Ref<ImageAnnotatorHandle>—

Related