DEV Community

Cover image for Building a Single-File Offline Prompt Tool With Vanilla JS
PromptMaster
PromptMaster

Posted on

Building a Single-File Offline Prompt Tool With Vanilla JS

Building a Single-File Offline Prompt Tool With Vanilla JS

There's a quiet joy in shipping a tool as one file. No npm install, no build step, no deploy pipeline, no backend to keep alive. You double-click an HTML file and it works — on any OS, offline, forever. Here's the architecture I used to ship a 3,000-entry prompt tool this way, and why vanilla JS is underrated for this class of product.

Why single-file at all

The constraints force good decisions. When everything — markup, styles, logic and data — lives in one file, you can't reach for a dependency casually. You end up with something small, fast, and portable enough to sell as a download that runs anywhere.

The tradeoff: no ecosystem. No React, no component library, no bundler niceties. For a focused tool, that's fine. You don't need a virtual DOM to render a filterable list.

Embedding the data

The data (thousands of prompt objects) ships inside the file as a JSON literal:

<script>
const DB = [{"v":1,"n":1,"c":"Sci-Fi","t":"Neon Rain City","p":"..."}, ...];
</script>
Enter fullscreen mode Exit fullscreen mode

At ~1MB this is completely fine for a browser to parse on load. No fetch, no CDN, no CORS. The data is just there the moment the file opens. For a read-mostly dataset this is simpler and more robust than any API.

Rendering without a framework

The whole UI is document.createElement and a render function that runs on filter changes. Pagination keeps it fast — render 48 cards, "load more" for the rest — so even 3,000 entries never block the main thread. Filtering is a single .filter() over the in-memory array. No state library; the "state" is a handful of module-scoped variables.

function onFilter(){
  filtered = DB.filter(r =>
    (!activeVol || r.v === activeVol) &&
    (!q || r.p.toLowerCase().includes(q))
  );
  shown = 0;
  grid.innerHTML = "";
  renderMore();
}
Enter fullscreen mode Exit fullscreen mode

That's the entire search engine. For a local dataset, String.includes over an array outperforms anything you'd reach for, and it's zero setup.

The payoff

The result opens in any browser, needs no connection, and can be distributed as a plain download. No hosting bill, no maintenance, no framework churn to keep up with in two years.

If you want to see the finished thing, there's a free demo of the tool — a single offline HTML file you can crack open and read the source of. It's a decent reference for this pattern.

In the next post I'll cover the tricky part: persisting user data (favorites, history) in a file that has no backend.

Try the free demo →

Top comments (0)