Slipsheet: PDFs That Read Like Part of the Page
In audit and assurance, the work often arrives as a PDF, and embedding one in an article has always meant an ugly iframe or a "click to download." So we built Slipsheet, an inline PDF reader that reads like part of the page, and open-sourced it under MIT. The part I care about is underneath: every embedded PDF degrades to a plain download link that works with no JavaScript and keeps working even if the viewer or the whole project disappears. Content that outlives its code. #opensource #webdev
An inline PDF reader we built for audit writing, now open source under MIT
---
The problem with a PDF in an article
I publish for audit and assurance professionals, and a lot of what matters in that world arrives as a PDF: a findings report, a regulator's guidance, a standard, a board pack. Sooner or later you want one of those sitting inside an article, where the reader is already paying attention.
The web has never made that pleasant. Your two real options have both been bad. You can drop in a "download this PDF" link, which stops the reader, sends them to a separate tab, and hopes they come back. Or you can use an `<iframe>` and inherit the browser's built-in PDF viewer, which is inconsistent across browsers, visually out of place, and fights the reader for the scroll wheel.
What I actually wanted is the experience you already know from LinkedIn: you scroll through a post, reach an embedded document, page through it right there without leaving, expand to fullscreen if you care, and download it if you want the file. On a phone, it should wait for a tap instead of quietly spending the reader's data plan.
So we built it. It is called Slipsheet, and I am putting it out in the open, under the MIT license, at github.com/jonto/slipsheet. It is early, and public on purpose: I would rather build a tool like this in the open than polish it in private.
You can see it working on this blog already: a 33-page PDF sits inline in [a post on Anthropic's guide to building Claude Skills, where you can page through the whole thing without leaving the article.
The name is borrowed from offset printing, where a slipsheet is a clean sheet slid between freshly printed pages to stop the ink from transferring. On the web, that is exactly what the tool does: it slides a PDF panel cleanly between two pieces of your article.
Content That Outlives Its Code
The viewer is the fun part to demo. It is not the part I am proud of.
The part I am proud of is what gets written into your content when you embed a PDF. It is not a viewer. It is not an iframe. It is not a fragile widget that depends on a script staying alive. It is this:
<div class="slipsheet"
data-src="https://cdn.example.com/report.pdf"
data-pages="12"
data-filename="Q3-audit-findings.pdf">
<a href="https://cdn.example.com/report.pdf" download>
Download Q3-audit-findings.pdf (12 pages)
</a>
</div>
A plain container wrapping a real download link. That lives in your stored content, forever.
Everything else is enhancement layered on top, and enhancement is allowed to fail. Follow the consequences:
- No JavaScript? It still works. A reader with scripts disabled, or a script that never loaded, gets a working download link. The author's intent survives.
- A search engine crawls it? It follows the link and indexes the actual PDF.
- Before the viewer runs, or if it never runs? The reader sees a clear, clickable link, not a broken empty box.
- We ship a new viewer, or a new editor, or break something, or abandon the project entirely? Every PDF in every article you ever published still works, because the thing you saved was never our code. It was a link.
That is the design goal stated plainly: the only thing that lives in your content is a plain link. The polished reading experience is real, but take it away and the plain link underneath still works.
Three layers, deliberately kept apart
Slipsheet is three pieces, and the separation is the point. Each is independently useful, and trouble in one never reaches the others.
1. `@slipsheet/hugerte`, the editor plugin. It adds a toolbar button, takes the PDF the author picks, hands it to an upload function you provide, and inserts the markup above. It knows nothing about where files are stored, nothing about rendering, and it does not carry a PDF library.
2. The markup contract, the `<div class="slipsheet">` you just saw. This is the connective tissue, and it is editor-agnostic. It does not know or care what created it.
3. `@slipsheet/viewer`, the reader. Standalone JavaScript and CSS that finds every `.slipsheet` on a page and turns it into the interactive viewer, with page navigation, fullscreen, mobile tap-to-load, keyboard control, and a download button. It does not know that any editor exists.
Because the layers are separate, you can take only what you need. Have your own PDF viewer but want the editor button? Take layer one. Use a different editor but want the reader? Take layer three. Nobody has to adopt the whole thing to use a piece of it.
A word about the license
Slipsheet's editor plugin targets [HugeRTE](https://hugerte.org/), not TinyMCE, and that was a deliberate, license-driven decision.
TinyMCE moved from MIT to GPLv2-or-later in its 7.0 release in 2024. That is a perfectly legitimate choice for them, but it makes the editor awkward to embed in most applications without buying a commercial license. HugeRTE is a community fork of the last MIT-licensed TinyMCE, actively maintained under MIT with no commercial path. Building on it keeps Slipsheet permissively licensed for everyone, and it rides a wave of people already migrating for exactly this reason.
There is a quiet bonus in that decision. HugeRTE's editor API is identical to TinyMCE 6, so the same plugin runs unchanged in a TinyMCE 6 install. If you are holding on an older version for license reasons, Slipsheet works for you too, with no fork and no patch.
Why give it away
I built Slipsheet for my own platforms: Assurcast, where I publish and curate news for the audit and assurance community, and SignalKit, the self-hostable blog this website runs on.
But there is nothing about a PDF inside an article that is specific to audit, or to me. The platforms that solved it well, LinkedIn among them, keep the solution locked inside their own walls, leaving everyone on the open web stuck with the iframe and the download link. Keeping a decent solution private would have been a small, petty kind of moat, and the code is not the valuable thing anyway. So I carved it out of the application it grew up in, put it in its own repository under my own name, and licensed it MIT. It is structured as two packages, `@slipsheet/viewer` and `@slipsheet/hugerte`, and it is meant to ship both as npm packages and as plain minified files you can drop in without a build step. The repository is public today; the code is still finding its feet, and that is deliberate.
This is the instinct I keep coming back to. Trust comes from things you can inspect and that keep working on their own, not from a dependency you hope someone keeps alive. Not everything I build is free, and not all of it will be open source. But whether I give a thing away or put a price on it, the test is the same: you can see how it works, it does not lock you in, and it keeps running without me. This time that meant open source and MIT. Another time it might not, and the promise underneath will still hold.
Slipsheet is a small tool, built to serve you rather than bind you. What you make with it stays yours: plain HTML, working on its own, long after you have moved on from the tool that made it. Your content was never holding our hand. It was holding a link.
---
Slipsheet lives at github.com/jonto/slipsheet, MIT licensed and early. Issues, pull requests, and better ideas are all welcome.
Comments