Tearline

Try it

Tearline

Playground

Docs

API

FAQ

Try it

Tearline

Playground

Docs

API

FAQ

Try it

MIT · zero dependencies

Any HTML.
Printed.

One custom element renders whatever you wrap in it as a thermal receipt — then hands your users a PNG of it.

Try it

menu.html
renders to ↓
1
2
3
4
5
6
<tear-line barcode="04732026">
<h1>Meridian</h1>
<hr>
<p>Cortado · 4.25</p>
<p>Total · 4.25</p>
</tear-line>

Meridian


Cortado · 4.25

Total · 4.25

Features

Paper is a rendering target
you already know how to write

No canvas API, no template language, no image service. Markup goes in, paper comes out.

Wrap anything

Headings, rules, tables, lists, images. If it renders in HTML it prints on paper — you are not learning a receipt DSL.

run.html
1
2
3
4
5
6
7
8
9
<tear-line>
<h1>Sunday long run</h1>
<hr>
<table>
<tr><td>Distance</td><td>21.1 km</td></tr>
<tr><td>Pace</td><td>5:12 /km</td></tr>
</table>
<img src="data:…">
</tear-line>

Hand them a PNG

One call turns the receipt into an image, at any scale, ready to post.

share.js
1
2
3
4
const el = document.querySelector('tear-line')
await el.download('receipt.png')
const blob = await el.toBlob()

Real text, not a canvas

The paper is styling wrapped around your markup, so everything a browser does with text still works.

tear-line · light DOM
✓
Selectable
✓
Searchable (⌘F)
✓
Translatable
✓
Screen-reader order
✓
Headings stay headings
✓
Tables stay tables
✓
prefers-reduced-motion

Same seed, same paper

The torn edge and the barcode are generated from a seed, so a receipt renders identically every time — and the export matches what your user actually saw.

deterministic
ORDER 4471
seed="4471"
edge · a1f2
ORDER 4472
seed="4472"
edge · 7c09
ORDER 4471
seed="4471"
edge · a1f2

Drops in anywhere

It is a custom element. React, Vue, Svelte, Astro, or a script tag in a static file.

Order.jsx
1
2
3
4
5
import 'tearline'
<tear-line seed={id}>
{order}
</tear-line>

Zero dependencies

No build step, no runtime, no peer deps. One file you can read in an afternoon.

package.json
{
"name": "tearline",
"type": "module",
"dependencies": {}
}

Playground

Type on the left.
Tear off the right.

This is the real element, loaded the way the docs tell you to load it. Edit anything.

<h1>Meridian</h1>
<p><small>WRAP ANYTHING</small></p>
<hr>

<table>
  <tr>
    <td>1</td>
    <td>Cortado</td>
    <td align="right">4.25</td>
  </tr>
  <tr>
    <td>1</td>
    <td>Sourdough</td>
    <td align="right">3.50</td>
  </tr>
</table>
<hr>

<table>
  <tr>
    <td><strong>TOTAL</strong></td>
    <td align="right"><strong>7.75</strong></td>
  </tr>
</table>
<hr>

<p><small>THANK YOU</small></p>
seed 20260726

1

tag

0

dependencies

0

build steps

MIT

licence

Install

One tag.
No build step.

drop in a script tag

That is the install. It is an ES module served from this origin, so there is no package to add, no bundler to configure and no build step — the custom element registers itself on load and every <tear-line> on the page upgrades in place.

Rendering to an image uses an SVG foreignObject, which is sandboxed and cannot fetch over the network. Text and styles are inlined for you, but an image inside the receipt must be a data: URI. The export throws with a message saying so rather than handing you a receipt with a hole in it.

then wrap anything

index.html
1
2
3
4
5
6
7
<script type="module" src="/tearline.js"></script>
<tear-line barcode="04732026">
<h1>Meridian</h1>
<hr>
<p>Cortado · 4.25</p>
</tear-line>

attributes

width330Paper width in pixels.
seed1Any integer. The same seed always produces the same torn edge and the same barcode, so a receipt renders identically every time — and the export matches what the user saw.
barcode—The digits printed under the bars. Omit for no barcode. Decorative: it is not a scannable Code 128 and does not pretend to be.
tilt-1.15Rotation in degrees.
flat—No rotation, no shadow. For embedding inside another layout.
animate—Prints 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.

accessibility

The receipt is real text in the light DOM — not a canvas and not an image. It is selectable, searchable, translatable, and read by screen readers in document order, because the paper is styling wrapped around your markup. Ink and paper are exposed as --ink and --paper so you can raise contrast past the default receipt look.

FAQ

Got questions?
We've got answers

Everything you need to know before you install it.

What is Tearline?

One custom element. Wrap any HTML in <tear-line> and it renders as a thermal receipt — torn edge, barcode, receipt type — and exports itself as a PNG. It is not a template language and not an image service; it is styling wrapped around your own markup.

What is it actually for?

Anything a user might want to keep or post: an order summary, a workout, a year in review, a booking, a set list, a diff. Receipts are shareable in a way that a screenshot of a table is not, which is the whole reason this exists.

Does the barcode scan?

No, and it does not pretend to. The bars are generated from the seed for looks. If you need a scannable code, render a real one and put it inside the receipt as a data: URI image.

Will the image match what my user saw?

Yes, as long as you set a seed. The torn edge and the barcode come from it, so the same seed always produces the same paper. Without one you get a new tear on every render — fine for a playground, wrong for an order confirmation.

Is the receipt accessible?

It is real text in the light DOM, not a canvas and not an image, so it is selectable, searchable, translatable, and read by screen readers in document order. Your headings stay headings and your tables stay tables. The print-out animation is skipped entirely under prefers-reduced-motion.

Does it work with React, Vue, Svelte?

It is a custom element, so it works anywhere HTML does — including a plain script tag in a static file. React 19 passes unknown attributes through, so the tag needs no wrapper.

What does it cost?

Nothing. MIT, zero dependencies, one file. There is no account, no key and no server — the export happens in the browser.

Wrap something in it

One tag, zero dependencies, no build step. MIT — free forever.

Install it

Read the docs

Wrap something in it

One tag, zero dependencies, no build step. MIT — free forever.

Install it

Read the docs

Wrap something in it

One tag, zero dependencies, no build step. MIT — free forever.

Install it

Read the docs

Tearline

Wrap any HTML in one tag and it prints as a receipt.

Product

Overview

Features

Playground

FAQ

Docs

Install

API reference

Exporting a PNG

Accessibility

DOM to PNG, explained

Receipt-style UI

Spotify receipt generators

Code

Read the source

llms.txt

MIT licence

Studio

Kynth Studios

© 2026 Tearline. MIT licensed — free forever.

A Kynth Studios project

Tearline

Wrap any HTML in one tag and it prints as a receipt.

Product

Overview

Features

Playground

FAQ

Docs

Install

API reference

Exporting a PNG

Accessibility

DOM to PNG, explained

Receipt-style UI

Spotify receipt generators

Code

Read the source

llms.txt

MIT licence

Studio

Kynth Studios

© 2026 Tearline. MIT licensed — free forever.

A Kynth Studios project

Tearline

Wrap any HTML in one tag and it prints as a receipt.

Product

Overview

Features

Playground

FAQ

Docs

Install

API reference

Exporting a PNG

Accessibility

DOM to PNG, explained

Receipt-style UI

Spotify receipt generators

Code

Read the source

llms.txt

MIT licence

Studio

Kynth Studios

© 2026 Tearline. MIT licensed — free forever.

A Kynth Studios project