Attach a useRef to the element you want to capture, install @zumer/snapdom, and call snapdom.toPng(ref.current) from a button handler. SnapDOM returns the HTMLImageElement ready to append.
1. Install
npm install @zumer/snapdom
SnapDOM has zero runtime dependencies and ships as an ES module — works with Vite, Create React App, Parcel, esbuild and any modern bundler out of the box.
2. Minimal capture component
import { useRef } from 'react'; import { snapdom } from '@zumer/snapdom'; export function CaptureCard() { const cardRef = useRef(null); const handleCapture = async () => { if (!cardRef.current) return; const img = await snapdom.toPng(cardRef.current, { scale: 2 }); document.body.appendChild(img); }; return ( <div> <div ref={cardRef} className="card"> <h3>Hello SnapDOM</h3> <p>This whole div will be captured.</p> </div> <button onClick={handleCapture}>Capture</button> </div> ); }
That's the entire integration. Click the button, get a PNG appended at the bottom of the document.
3. Download instead of appending
Most apps want to download the image rather than insert it. SnapDOM exposes a one-liner:
const handleDownload = async () => { if (!cardRef.current) return; const result = await snapdom(cardRef.current); await result.download({ format: 'png', filename: 'card' }); };
4. Keep the captured image in state
If you want to render a preview inside your component, store the image as a data URL using toBlob + URL.createObjectURL:
import { useRef, useState } from 'react'; import { snapdom } from '@zumer/snapdom'; export function CapturePreview() { const cardRef = useRef(null); const [previewUrl, setPreviewUrl] = useState(null); const handleCapture = async () => { if (!cardRef.current) return; const blob = await snapdom.toBlob(cardRef.current); setPreviewUrl(URL.createObjectURL(blob)); }; return ( <> <div ref={cardRef}>…</div> <button onClick={handleCapture}>Capture</button> {previewUrl && <img src={previewUrl} alt="preview" />} </> ); }
Remember to revoke the object URL with URL.revokeObjectURL(previewUrl) when you're done with it to avoid leaks.
5. Reusable React hook
If you call SnapDOM from many components, extract a small hook:
// useSnapdom.js import { useCallback, useRef } from 'react'; import { snapdom } from '@zumer/snapdom'; export function useSnapdom(options = {}) { const ref = useRef(null); const capture = useCallback(async (format = 'png') => { if (!ref.current) return null; const result = await snapdom(ref.current, options); return result.to(format); }, [options]); return { ref, capture }; }
Use it like this:
const { ref, capture } = useSnapdom({ scale: 2, embedFonts: true }); return ( <> <div ref={ref}>…</div> <button onClick={() => capture('png')}>Capture</button> </> );
Common gotchas
My captured image is blank or empty
The two most common causes:
1. You called snapdom before the ref was attached. Always trigger the capture from a user event (button click) or inside useEffect after mount, never directly inside the component body.
2. The captured element has display: none or is detached from the document. SnapDOM needs the element to be in the live DOM tree.
Web fonts render as Times New Roman
Pass { embedFonts: true } in the options. SnapDOM will inline the matching @font-face declarations into the SVG output so the captured image uses your real fonts.
Cross-origin images don't render
SnapDOM fetches external images and inlines them as data URLs. If your CDN doesn't send Access-Control-Allow-Origin: *, configure a proxy with { useProxy: 'https://your-proxy.example.com/' }.
Does SnapDOM work with React 18 strict mode?
Yes. SnapDOM doesn't subscribe to React lifecycle, so the double-invocation in development strict mode doesn't affect captures triggered from event handlers.
Can I use SnapDOM in Next.js?
Yes — use a client component ('use client') and a dynamic import. See the dedicated Next.js guide.
Does SnapDOM work with React Native?
No. SnapDOM is a browser-only library — it relies on DOM APIs and SVG foreignObject that don't exist in React Native.
Try it in your project
Drop SnapDOM into your React app and capture your first component in under a minute.
Open the demo Install from npm