# Tearline > One custom element. Wrap any HTML in `` and it renders as a thermal > receipt — torn edge, barcode, receipt type — then exports itself as a PNG. > Zero dependencies, no build step, MIT. Tearline is a single-file web component (a custom element). It is not a template language, not a canvas library, and not an image service. The receipt is CSS wrapped around your own markup, so the text stays real text in the light DOM: selectable, searchable, translatable, and read by screen readers in document order. ## Load it ```html

Meridian


Cortado · 4.25

``` Verified reachable (HTTP 200) on 2026-07-31. There is no npm package to install today — `npm i tearline` does not resolve, and the script tag above is the supported install path. ## Export it to a PNG ```js 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(); ``` The export serialises the rendered receipt into an SVG `` and paints it onto a canvas. No dependency — but that sandbox cannot reach the network, so an `` inside the receipt must be a `data:` URI or the export rejects with an explicit error rather than handing back a receipt with a hole in it. Text and CSS are inlined automatically. The technique is not specific to Tearline, and it has a second failure mode worth knowing: per MDN, drawing cross-origin data onto a canvas without CORS approval taints it, after which `toBlob()`, `toDataURL()` and `captureStream()` throw a `SecurityError` instead of returning anything. A blank export and a thrown export are different bugs with different fixes. Both are written up at https://tearline.kynth.studio/dom-to-png, alongside how the zero-dependency DOM-to-image packages on npm compare (figures fetched 2026-07-31). ## Attributes - `width` (default `330`) — paper width in pixels. - `seed` (default `1`) — any integer. The same seed always produces the same torn edge and the same barcode, so the export matches what the user saw. - `barcode` — the digits printed under the bars. Omit for no barcode. It is decorative and is not a scannable Code 128. - `tilt` (default `-1.15`) — rotation in degrees. - `flat` — no rotation, no drop shadow. For embedding inside another layout. - `animate` — prints out on first paint. 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. `name` defaults to `receipt.png`. ## 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 anything can be restyled from the outside without a specificity fight. Paper and ink are custom properties: `--paper`, `--ink`, `--ink-strong`, `--ink-faded`, `--font`. That override behaviour is not a Tearline convention — it is the cascade. Per CSS Cascade and Inheritance Level 5 (fetched 2026-08-01), when two declarations come from different encapsulation contexts, the declaration from the OUTER context wins for normal rules, and the inner context wins for important ones. A `::slotted()` rule inside a component is therefore a default that any ordinary rule on the host page beats. ## The receipt look, without the component The look is plain CSS and can be rebuilt from scratch: a narrow measure, a monospace stack, 11.5px type on 1.62 line-height with `.04em` tracking, `font-variant-numeric: tabular-nums` so price columns align, dashed `
` rules, an SVG `feTurbulence` data URI laid over the paper at `mix-blend-mode: multiply`, and a seeded `clip-path` polygon for the torn edge (mostly straight, roughly one point in six a deeper nick). The full write-up, with the shipped values and why a share-image widget belongs in a custom element rather than a framework component, is at https://tearline.kynth.studio/receipt-ui. ## Building a Spotify listening receipt Tearline renders the receipt and exports the PNG. It does nothing about getting the data out of Spotify, and that is the hard half. Per Spotify's quota-modes documentation (fetched 2026-08-02), a newly-created app is in development mode, where "Up to 5 authenticated Spotify users can use an app that is in development mode" and the owner must hold a Spotify Premium account. Extended quota mode lifts the cap, but since 15 May 2025 "Spotify only accepts applications from organizations (not individuals)", with stated criteria including a registered business entity and "a minimum of active users (at least 250k MAUs)". So an individual building one today has three realistic routes: ship it for the five allowed users, take the listening data from the user (a paste box, a CSV, a personal-data export) instead of via OAuth, or make a receipt of something that has no gatekeeper. The full write-up — including the single endpoint the whole genre is built on, GET /me/top/{type} with time_range and a limit capped at 50, and five compared ways to turn the rows into a PNG — is at https://tearline.kynth.studio/spotify-receipt-generator. ## Frameworks It is a custom element, so it works anywhere HTML does — React, Vue, Svelte, Astro, or a plain script tag in a static file. React 19 passes unknown attributes through, so the tag needs no wrapper. ## Pages - [Home and live playground](https://tearline.kynth.studio/) - [Documentation and full API reference](https://tearline.kynth.studio/docs) - [Export a DOM element as a PNG — the technique, its limits, and the npm alternatives](https://tearline.kynth.studio/dom-to-png) - [Receipt-style UI on the web — the CSS behind the paper look, and why it is a custom element](https://tearline.kynth.studio/receipt-ui) - [Receiptify-style Spotify receipt generators — the API cap that stops most of them, and five ways to make the image](https://tearline.kynth.studio/spotify-receipt-generator) MIT © Kynth Studios.