Sign In

@kynth/tearline

Package Overview
Dependencies
Maintainers
1
Versions
1
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@kynth/tearline

Wrap any HTML in one tag and it prints out as a thermal receipt. Save it as a PNG. Zero dependencies, no build step.

latest
npmnpm
Version
0.1.0
Version published
Weekly downloads
59
Maintainers
1
Weekly downloads
 
Created
Source

Tearline

Any HTML you wrap in this one tag prints out as a thermal receipt. Then save it as a PNG your users will actually post.

Zero dependencies. No build step. One custom element. MIT.

Live playground → tearline.kynth.studio

Install

npm i @kynth/tearline
import '@kynth/tearline';

The package is scoped because npm's registry refuses the bare name tearline — its similarity filter reads it as too close to the existing readline package. The element is still <tear-line>.

Importing the package registers the <tear-line> element. There is nothing to configure and nothing to call.

Or skip the install entirely — it is an ES module, so a script tag is a complete install:

<script type="module" src="https://tearline.kynth.studio/tearline.js"></script>

Use it

<tear-line barcode="047320260726">
  <h1>Meridian</h1>
  <hr>
  <p>Cortado &middot; 4.25</p>
</tear-line>

Why

The receipt look gets rebuilt from scratch every time somebody wants it. Receiptify turned one person's Spotify history into a receipt and got used millions of times; the aesthetic has been cloned for years. Nobody shipped the reusable piece underneath it — every receipt package on npm is an ESC/POS driver for actual thermal printers, not the look.

This is the look, as one custom element, with a PNG export so the thing your users make is a thing they can post.

Save it as an image

const el = document.querySelector('tear-line');

await el.download('receipt.png');      // saves it
const blob = await el.toBlob();        // or handle it yourself
const url  = await el.toDataURL();

Attributes

default
width330Paper width in pixels.
seed1Any integer. The same seed always produces the same torn edge and the same barcode, so a receipt looks identical every time it renders — and the export matches what the user saw.
barcodeThe digits printed under the bars. Omit for no barcode. Decorative; it is not a scannable Code 128, and it doesn't pretend to be.
tilt-1.15Rotation in degrees.
flatNo rotation, no drop shadow. For embedding the receipt inside another layout.
animatePrints out on first paint, like paper feeding from a till. Skipped under prefers-reduced-motion.

Methods

toBlob({scale})Resolves to a PNG Blob. scale defaults to 2.
toDataURL({scale})Resolves to a PNG data URL.
download(name, {scale})Saves the PNG.

Export progress

An export fires a tearline:stage event as it enters each of its four real steps, with detail: { stage, index, of }:

flattenClone the shadow tree and inline the slotted light DOM.
serialiseXMLSerializer into an SVG <foreignObject>.
rasteriseDecode that SVG in an <img>. On a long receipt this is most of the wait, and it is the step that fails.
encodeCanvas to PNG blob.

They are the four things the code actually does, not a progress bar's worth of invented percentages. When an export throws, the stage is the difference between "export failed" and "the browser could not decode the receipt as an image".

el.addEventListener('tearline:stage', (e) => {
  const { stage, index, of } = e.detail;   // e.g. 'rasterise', 3, 4
});

Styling

h1, h2, hr, p, small, strong, table, ul and ol are styled for you inside the receipt. Every one of those rules is ::slotted(), which loses to your own CSS — so restyle anything you like from the outside without fighting specificity.

Paper and ink are custom properties:

tear-line {
  --paper: #f6f3ec;
  --ink: #2b2724;
  --ink-strong: #1a1715;
  --ink-faded: #6a635c;
  --font: ui-monospace, Menlo, monospace;
}

One export caveat, stated plainly

Rendering to an image uses an SVG <foreignObject>, which is sandboxed and cannot fetch anything over the network. Text and styles are inlined for you automatically. But an <img> inside the receipt must be a data: URI, or it will be missing from the exported PNG. The export throws with a message saying so rather than silently handing you a receipt with a hole in it.

Separately, drawing cross-origin data onto a canvas without CORS approval taints it, after which toBlob(), toDataURL() and captureStream() throw a SecurityError. A blank export and a thrown export are different bugs with different fixes. Both are written up at tearline.kynth.studio/dom-to-png.

Accessibility

The receipt is real text in the light DOM — not a canvas, not an image. It stays selectable, searchable, translatable, and is read by screen readers in document order, because the paper is styling wrapped around your markup rather than a picture of it. Your headings stay headings and your tables stay tables.

The print-out animation is skipped entirely under prefers-reduced-motion, and --ink / --paper are exposed so you can push contrast past the default receipt look where you need to.

License

MIT © Kynth Studios

Keywords

receipt

FAQs

Package last updated on 13 Aug 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts