Embed in your page

<zpl-viewer> shows a label as an image in your web page. It renders in the browser with the same renderer as the server, so the preview is pixel-exact and updates instantly, with no request per preview. It shows only the rendered label: a PNG image (plus a pager when the ZPL holds more than one label). Users can right-click it to save or copy the image.

<script type="module" src="https://labelviewer.example.internal/sdk/1.0.1/labelviewer-elements.min.js"
        integrity="sha384-<hash from the SDK page>" crossorigin="anonymous"></script>

<zpl-viewer id="preview" zoom="fit"></zpl-viewer>
const viewer = document.getElementById('preview');
viewer.zpl = '^XA^FO50,50^A0N,50,50^FDHello {{name|World}}^FS^XZ';
viewer.data = { name: 'Ada' };   // optional: merged in the browser

Load it in your framework#

Load the SDK once per page: the script tag above (in index.html for Angular, React and Vue apps), or a dynamic import() of the same URL where the viewer is used. The SDK page has the exact URL and hash for this install, the versioned and latest URLs, TypeScript types and the CSP your app needs. Then bind the element:

<!-- Angular: add schemas: [CUSTOM_ELEMENTS_SCHEMA] to the component -->
<zpl-viewer [zpl]="zpl()" [data]="record()" zoom="fit"></zpl-viewer>
// React 19 sets the properties directly.
// React 18 only sets string attributes: set zpl and data through a ref instead.
<zpl-viewer zpl={zpl} data={record} zoom="fit" />
<!-- Vue 3: compilerOptions.isCustomElement = (tag) => tag.startsWith('zpl-') -->
<zpl-viewer :zpl="zpl" :data="record" zoom="fit" />

Framework details (zones, React event names, SSR, bundler comments): SDK › Load it per framework.

Attributes and properties#

Attributes are strings; every attribute also has a property (camelCase).

AttributePropertyTypeDefault
zplzplstringZPL, or a template with {{placeholders}}.
datadataobject | array (attribute: JSON)noneMerged into zpl in the browser. An array renders one label set per record. Without data, placeholders show their defaults (or their names).
recordrecordinteger | nullnullWith array data: render only this record (0-based).
dpmmdpmm6 | 8 | 12 | 248Printhead density (152, 203, 300, 600 dpi).
width, heightwidth, heightinches, 0.1-154, 6Label size.
uprightuprightbooleantrueIgnore ^PO I so the label reads the right way up. upright="false" shows it as it leaves the printer.
zoomzoomfit | actual | numberfitfit fits the label in the element (the element's width only, when its height is auto). actual = physical size on a 96 ppi screen. A number is CSS pixels per printer dot (1 = 100%; "150%" works too).
labellabelinteger0The label shown; the pager changes it.
altaltstringLabel previewAlternative text of the image.
fonts-base-urlfontsBaseUrlURLfonts/ next to the scriptWhere the font files are. Only needed when the SDK is bundled from npm (SDK › Fonts).
api-baseapiBaseURLthe install the SDK was loaded fromThe labelviewer install whose license applies (SDK). Only needed when the SDK is bundled from npm.
—labelCount (read-only)numberLabels of the last render.

When the image is scaled up it keeps hard pixel edges (image-rendering: pixelated). Below one screen pixel per dot it is scaled smoothly, so thin lines stay visible.

The element is height: 360px by default; set your own height, or height: auto to size it by its width.

Methods#

Method
toPNG(index?)Promise<Blob>: the label (default: the one shown) as a 1-bit PNG at printer dots, the same bytes the server's PNG route returns (watermarked on a trial license, like the server's). Rejects when the Viewer is not licensed.
print(index?)Opens the browser's print dialog with the label at its physical size (through a hidden iframe).
render()Render now; returns { labels, warnings }. Changes made in one task are otherwise batched into one render.
document.getElementById('download').onclick = async () => {
    const blob = await viewer.toPNG();
    const a = Object.assign(document.createElement('a'), { href: URL.createObjectURL(blob), download: 'label.png' });
    a.click();
    URL.revokeObjectURL(a.href);
};

Events#

All are CustomEvents that bubble and cross the shadow root.

EventdetailWhen
render{ labels, warnings }After each render. labels is the number of labels.
error{ source, message, details? }source: merge (the data does not fit the template; details lists { record, placeholder, reason }), data (the data attribute is not valid JSON, or record is out of range), render, fonts, license (the install's license does not cover the Viewer: the element shows a notice instead of the label).

On a merge error the viewer still shows the template with its defaults, plus the message.

Theming#

Custom properties (set them on the element or an ancestor):

PropertyDefault
--lv-viewer-bgcheckerboardBackground around the label
--lv-label-shadowsoft shadowShadow of the label image
--lv-panel, --lv-border, --lv-radius#ffffff, #d9dce1, 4pxPager bar and its buttons
--lv-text, --lv-font#1f2933, system UIPager text
--lv-error#cf222eError message text

Parts for anything else (zpl-viewer::part(image) { ... }):

Part
frameEverything
stageThe scroll area around the label
imageThe label image (<img>). canvas still matches it, for stylesheets written for older versions; it will be removed.
pagerThe bar with the previous/next buttons (only shown for more than one label)
messageThe error message
zpl-viewer { height: 480px; --lv-viewer-bg: #fff; --lv-label-shadow: none; }
zpl-viewer::part(image) { outline: 1px solid #ccc; }

Content Security Policy#

The image is a blob: URL, so your page's CSP needs img-src blob: (besides the script-src and connect-src entries every page that loads the SDK needs, see SDK › Content Security Policy).