Integrations
Other ways in, for the shape your app already has.
Other entry points
captureAll() gives you one capture per matching element — a whole list of report cards in a single call.
const { results, errors } = await s2a.captureAll({
target: '.report-card',
formats: ['png'],
})It runs sequentially on purpose: each capture rasterizes a full subtree, and firing twenty at once makes the page stutter for the user who is still looking at it. One element failing doesn’t abandon the rest — check errors. Each result carries index and total in its params, so your webhook can tell them apart. The editor is skipped here; annotating N images back to back isn’t a flow anyone wants.
preload() warms the font and asset caches so the first capture isn’t the slow one. On a font-heavy page, downloading and base64-ing every @font-face is most of the wall clock — do it while the user isn’t waiting.
// on idle, or when they hover your export button
requestIdleCallback(() => s2a.preload())result.copy() puts the capture on the system clipboard. It resolves false rather than throwing when the browser refuses — it needs a secure context and a recent user gesture — so you can fall back to a download.
const result = await s2a.capture({ target: '#chart' })
if (result && !(await result.copy())) {
// clipboard blocked; offer the file instead
}And the lower-level pieces, if you want to build your own pipeline: toCanvas(), toBlob(format), renderToCanvas(), elementToCsv() and canvasToPdf() are all exported.
React
import { Screen2ApiProvider, useCaptureTarget } from '@screen2api/sdk/react'
function Report() {
const { ref, capture, capturing } = useCaptureTarget({
formats: ['png', 'pdf'],
})
return (
<>
<div ref={ref}>…</div>
<button onClick={() => capture()} disabled={capturing}>
{capturing ? 'Exporting…' : 'Export'}
</button>
</>
)
}Wrap your app in <Screen2ApiProvider publishableKey="…">. There is also a plain useCapture() if you’d rather pass a selector, and a <CaptureButton /> for the simple case.
Export buttons from markup
Everything above assumes you want to write the wiring yourself — pick a target, choose formats, hang a click handler on a button. If you just want the export to exist, tag the content instead and skip all of it.
<div data-screen2api>
<!-- your gallery, invoice, dashboard, anything -->
</div>s2a.autoBind() // once, anywhereThat is the whole integration. An export button appears on the element, and clicking it captures that element and everything inside it.
It works out the formats
You don’t say what the element is; we look. A <table> is worth having as a spreadsheet, an image isn’t.
| Element | Offered |
|---|---|
<table>, ARIA grid, or a wrapper round one | csv, png, pdf |
<ul>, <ol>, <dl> | csv, png |
<img>, <canvas>, <svg>, <video> | png |
| anything else | png, pdf |
One format exports on click; more than one opens a small menu. It deliberately doesn’t export all of them at once — that would triple the quota a single click costs you. Override with data-s2a-formats whenever the guess is wrong.
It waits for the content
The button doesn’t appear until the element has a real size and its images have finished loading. This matters more than it sounds: on a gallery that fills in after load, an export button that works immediately hands the first person who clicks it a picture of half-drawn placeholders, and they conclude the product is broken. Content added later — infinite scroll, a client-side route change — is picked up automatically.
Every attribute
<div
data-screen2api
data-s2a-formats="png,pdf" <!-- override the detected set -->
data-s2a-editor="true" <!-- let the user annotate first -->
data-s2a-params='{"orderId":"ord_9"}' <!-- echoed back on your webhook -->
data-s2a-position="top-right" <!-- ...top-left | bottom-right |
bottom-left | outside-top-right |
outside-top-left -->
data-s2a-show="hover" <!-- default: always visible -->
data-s2a-label="Export" <!-- default: icon only -->
data-s2a-target="#something-else" <!-- capture that instead of me -->
data-s2a-area="element" <!-- element | viewport | document -->
data-s2a-scale="2"
data-s2a-exclude=".controls, nav"
data-s2a-redact="[data-pii]"
data-s2a-filename="order-9"
data-s2a-wait="false" <!-- skip the readiness wait -->
data-s2a-role="target" <!-- force target | trigger -->
data-s2a-webhook="false" <!-- user download only, no delivery -->
>On a button, it stays a button
Tag a <button>, <a> or role="button" and it behaves as a trigger rather than growing a button of its own — so the original markup-only form still works exactly as it did.
<button data-screen2api data-s2a-target="#invoice" data-s2a-formats="png,pdf">
Export invoice
</button>Anything ambiguous can be settled with data-s2a-role.
Defaults for the whole page
s2a.autoBind({
position: 'outside-top-right',
visibility: 'hover',
label: 'Export',
editor: true,
})Per-element attributes win over these. autoBind() returns a function that removes every button it added, which is what you call from a framework cleanup.
The outside-* positions place the button above the element rather than over it, which is useful when the content goes right to its own edge — but it sits in whatever is above, so check it against a sticky header before shipping it.
The buttons render in their own layer rather than inside your elements. Nothing is inserted into your DOM, so your flex and grid item counts, your :last-child rules and your global button styles are all left alone — and the button can never end up inside the capture it triggers.