[HN Gopher] Useful patterns for building HTML tools
       ___________________________________________________________________
        
       Useful patterns for building HTML tools
        
       Author : simonw
       Score  : 212 points
       Date   : 2025-12-10 21:08 UTC (3 days ago)
        
 (HTM) web link (simonwillison.net)
 (TXT) w3m dump (simonwillison.net)
        
       | wiseowise wrote:
       | Amazing article with lots of useful info. Big kudos, Simon.
       | 
       | This really showcases the power of the single page apps and why
       | web will be always ahead of native for this kind of Swiss Army
       | Knife tools.
       | 
       | With LLMs, it gets ridiculously easy to "develop" (generate)
       | those too.
        
         | simonw wrote:
         | It really is wild how quickly these things pop out - this one
         | here took a couple of prompts, probably ten minutes from idea
         | to shipping a working implementation:
         | https://tools.simonwillison.net/pypi-changelog?package=llm&c...
        
       | TomasBM wrote:
       | Pretty nice. I've been using LLMs to generate different Python
       | and JS tools for wrangling data for ontology engineering
       | purposes.
       | 
       | More recently, I've found a lot of benefit from using the
       | _extended thinking_ mode in GPT-5 and -5.1. It tends to provide a
       | fully functional and complete result from a zero-shot prompt. It
       | 's as close as I've gotten to pair programming with a
       | (significantly) more experienced coder.
       | 
       | One functional example of that (with 30-50% of my own coding,
       | reprompting and reviews) is my OntoGSN [1] research prototype.
       | After a couple of weeks of work, it can handle different
       | integration, reasoning and extension needs of people working in
       | assurance, at least based on how I understood them. It's an
       | example of a human-AI collab that I'm particularly proud of.
       | 
       | [1] Playground at w3id.org/OntoGSN/
        
       | NotMichaelBay wrote:
       | Nice, I do this often enough that I created a bookmarklet to
       | download an HTML file from clipboard after copying ChatGPT's code
       | block.
       | 
       | I've also been using LLMs to create and maintain a "work assist"
       | Chrome extension that I load unpacked from a local directory.
       | Whenever I notice a minor pain point, I get the LLM to quickly
       | implement a remedy. For example, I usually have several browser
       | tabs open for Jira, and they all have the same company logo as
       | the favicon, so my Chrome extension changes the favicon to be the
       | issue type icon (e.g. Bug, Story, etc) when the page loads. It
       | saves a little time when I'm looking for a specific ticket I've
       | already opened.
        
       | lewisjoe wrote:
       | I also find that sticking to a single file makes coding agents
       | perform better (fewer surgical edits, faster outputs, sensible
       | changes, etc).
       | 
       | Not sure why, but the moment the file is split into files and
       | subfolders, coding agents tend to do a lot more changes that what
       | is absolutely necessary. That way a single html file wins!
        
       | dave1010uk wrote:
       | Thanks Simon!
       | 
       | My tool collection [0] is inspired by yours, with a handful of
       | differences. I'm only at 53 tools at the moment.
       | 
       | What I did differently:
       | 
       | Hosted on Cloudflare Pages. This gives you preview URLs for pull
       | requests out the box. This might be possible with Github Pages
       | but I haven't checked. I've used Vercel for similar projects in
       | the past. Cloudflare seems to have the odd failed build that
       | needs a kick from their dashboard.
       | 
       | Some tools can make use of Workers/Functions for backend
       | processing and secrets. I try to keep these to a minimum but
       | they're occasionally useful.
       | 
       | I have an AGENTS.md that's updated with a Github action to
       | automatically pull in Claude-style Skills from the .skills
       | directory. I blogged about this pattern and am still waiting for
       | a standard to evolve [2].
       | 
       | I have a base stylesheet that I instruct agents to pull in. This
       | gives a bit of consistency and also let's them use Tailwind,
       | which they'd seem to love.
       | 
       | [0] https://tools.dave.engineer/
       | 
       | [1] https://github.com/dave1010/tools/tree/main/functions
       | 
       | [2] https://dave.engineer/blog/2025/11/skills-to-agents/
        
         | TheTaytay wrote:
         | Thanks for showing this! It's cool, and I enjoyed reading
         | through some of the code. Note that I tried to use some of the
         | regex tools that needed LLMs and got a rate limit error.
        
       | nels wrote:
       | Really enjoyed this, interesting read as always! It reminded me
       | of Google Labs' recent GenTabs project [1], and also of a recent
       | ACM paper on user-assembled LLM-mediated tools from web content
       | [2]. Feels like similar concepts are emerging in multiple places,
       | all centered around lightweight intent-driven tools rather than
       | traditional apps, which I think makes a lot of sense. Curious how
       | this will evolve!
       | 
       | [1] https://labs.google/disco
       | 
       | [2] https://dl.acm.org/doi/10.1145/3706598.3713285
        
         | btbuildem wrote:
         | I think UIs will become more "generative" or "on demand". Not
         | necessarily always generated anew, but assembled from pre-
         | generated (reproducible) components, to suit a specific
         | workflow.
         | 
         | I think especially in context of software that is complex and
         | takes a long time to master, this could be the next
         | breakthrough. Instead of paths-to-goal being buried in
         | sequences of menus and config panels, workflow pathways would
         | be invocable with plain language.
        
           | girvo wrote:
           | A lot of investment at some very large companies you know are
           | trying to achieve this right now, and betting big on it. So
           | far the lack of determinism when it _is_ required gets in the
           | way, but we'll see: it feels surmountable.
        
       | Havoc wrote:
       | Great timing - I've taken up vibecoding and picked personal tools
       | with simple HTML/CSS/JS/python stack as the learning ground too.
       | 
       | Personal tools seem like a reasonable place for happy path
       | vibecoding given small blast radius and LLMs can do that sort of
       | static page in front of python backend really well.
       | 
       | I've also been surprised how much active learning I'm doing
       | despite specifically _not_ look at code. Between the need to spec
       | things out carefully (plan.md) and fast iteration loop it 's been
       | a huge boost. Having the LLM look at a plan.md and suggest
       | improvements has lead to a lot of "oh I didn't think about that"
       | learning on architecture and user requirements link.
       | 
       | Presumably much of that learning boost is because I'm a hobbyist
       | tier programmer, guessing professionals wouldn't experience the
       | same since they learned this via manual coding trial & error over
       | years.
        
         | girvo wrote:
         | > Presumably much of that learning boost is because I'm a
         | hobbyist tier programmer, guessing professionals wouldn't
         | experience the same since they learned this via manual coding
         | trial & error over years.
         | 
         | I can only speak for myself and my not-quite two decades of
         | professional experience, but yes pretty much!
         | 
         | It's neat to see that sped up for others though with lower
         | stakes, though it's not _quite_ the same unless you prompt your
         | agent to question you back a lot (Claude is much better at this
         | in my experience)
        
       | al_borland wrote:
       | I've been making stuff like this for a while (pre-LLM days). They
       | are great little side projects that tend to be useful without
       | getting overly complex. While I've vibe coded a couple just to
       | get something done that I needed, I tend to like to write them
       | myself still, as I enjoy the process.
        
         | mattkdev wrote:
         | Been writing code since the 80's on my C64 and I love it but at
         | this point, I will never write another line of code yet I will
         | produce more code than I ever have! It's just as fun if not
         | more.
        
       | toastal wrote:
       | > The alternative to CDNs is to use npm and have a build step for
       | your projects. I find this reduces my productivity at hacking on
       | individual tools and makes it harder to self-host them.
       | 
       | No. You can vendor these scripts & host them 1st party so you
       | aren't leaking data to these CDNs or risk users not actually
       | getting the scripts. It isn't like CDNs give you a performance
       | boost anymore.
       | 
       | https://httptoolkit.com/blog/public-cdn-risks/
        
         | chrisweekly wrote:
         | Great link.
        
         | simonw wrote:
         | I wish vendoring was less of a hassle.
         | 
         | I'll vendor and self-host for my professional projects, but for
         | these small experimental utilities I've stopped caring.
        
           | toastal wrote:
           | > small experimental utilities
           | 
           | This is what CDNs should be used for at this time--or for
           | fetching the scripts to vendor. That's fine, but
           | _recommending_ I don't think is the best call since one
           | folk's experimental utility will inevitably get released into
           | production--often not even at fault of the utility's maker.
           | When I use CDNs like this, there are  <!-- WARNING ... -->
           | around the code just in case someone were to run with it,
           | along with adding the integrity attribute.
        
       | xnx wrote:
       | If HTML tools could make network calls (CORS be damned), they
       | could replace a huge portion of hosted apps.
        
         | simonw wrote:
         | I'm tempted to run a CORS proxy somewhere - maybe on Cloudflare
         | Workers - but I'm nervous that any open proxy is liable to be
         | abused.
         | 
         | I could do an authentication protected one that only I could
         | access though...
        
       | born-jre wrote:
       | I am building platform which makes it easy to host this kind of
       | app but it has own set of idiosyncrasy u have to adhere to.
       | 
       | https://github.com/blue-monads/potatoverse
        
       | btbuildem wrote:
       | I really like the simplicity here (maybe because it mirrors my
       | own approach). No build chain, no node_packages, no frameworks.
       | 
       | I wonder if packaging the results as web components would be the
       | next logical step.
        
       | valbaca wrote:
       | HTML tools are a good name. I called them something like "a local
       | html file"
       | 
       | One problem I solved with this was a packer needed to scan a few
       | (10-40) ids into his barcode scanner. It was not enough where
       | pulling up their bulk-id-uploader program but also too tedious to
       | go to some "number to barcode" website.
       | 
       | Turns out, barcodes can be made from a google font!
       | 
       | https://fonts.google.com/specimen/Libre+Barcode+39
       | 
       | You can just display a number using that font. Then hooked up a
       | for-loop that's progressed by pressing the space bar: paste in
       | IDs, scan first, space, scan next, repeat.
        
       | oulipo2 wrote:
       | Unrelated, but I bought a walking pad, and because I often use it
       | at home while working on the laptop, and I wanted a nice UI to
       | track the speed/time etc, I just asked claude to do one:
       | 
       | https://pastebin.com/5HRLh1G6
       | 
       | it does something like this
       | 
       | https://imgur.com/a/888BtpG
       | 
       | and connects through BLE
        
       | fallinditch wrote:
       | Another useful pattern for certain types of app is to include a
       | function that saves the HTML file to your local drive/memory as a
       | new file - for example, if the app features user inputs like
       | writing or drawing.
        
         | fallinditch wrote:
         | It's a little problematic to share an HTML file that you made
         | or saved on your phone with other phone users directly, for
         | example by sending via a messaging app: Android it's ok, but
         | iOS won't open or save an HTML file sent in this way.
         | Apparently there is a workaround to long press on the file to
         | save it to your Files folder first.
         | 
         | This issue is relevant if your app's functionality includes the
         | user changing the contents of the file and re-saving as a new
         | file.
        
       | simonw wrote:
       | I just shipped a new one of these a few minutes ago (from my
       | phone).
       | 
       | I found out about a new Python HTML parsing library -
       | https://github.com/EmilStenstrom/justhtml - and wanted to try it
       | out but I'm out without my laptop. So I had Claude Code for web
       | build me a playground interface for trying it out:
       | https://tools.simonwillison.net/justhtml
       | 
       | It loads the Python library using Pyodide and lets you try it out
       | with a simple HTML UI.
       | 
       | The prompts I used are in this PR:
       | https://github.com/simonw/tools/pull/156
        
         | dotancohen wrote:
         | Serious question. How do you "try out" a library if somebody
         | else (or something else) is writing the code?
         | 
         | Thank you.
        
           | simonw wrote:
           | The thing that matters for me when considering a new library
           | is what it can _do_. Once I know that I can decide if it 's
           | worth learning how to use it at a code level.
           | 
           | In the case of JustHTML I've now been able to try it against
           | a few different HTML documents, seen it do good pretty-
           | printing, played with its CSS selector implementation and got
           | a feel for its event-based streaming parser. I'm very
           | impressed! I think I'll be using it in the future next time I
           | need an HTML parser.
        
             | dotancohen wrote:
             | I see, thank you.
             | 
             | Until vibe coding came along, the ergonomics of a library
             | were no less important than its functionality. But I
             | understand how LLM assisted coding changes that
             | perspective.
             | 
             | I'll go tend to my empty lawn now.
        
         | mirekrusin wrote:
         | You can use react with jsx without any build step in single
         | html file as well.
        
           | simonw wrote:
           | Yeah I used it for this one via Babel from a CDN but it felt
           | pretty crufty: https://tools.simonwillison.net/box-shadow
        
       | pseudosavant wrote:
       | I love this pattern. I've been using it myself here:
       | https://pseudosavant.github.io/ps-web-tools/
       | 
       | Create PDFs from images, a Wordle hint/solver, or a classic DVD
       | screensaver. Lots of stuff.
        
       | chrisweekly wrote:
       | > _" If you want to see the code and prompts, almost all of the
       | examples in this post include a link in their footer to "view
       | source" on GitHub. The GitHub commits usually contain either the
       | prompt itself or a link to the transcript used to create the
       | tool."_
       | 
       | As if your steady stream of learning-in-public experiments and
       | insights weren't generous enough. Seriously, massive kudos for
       | sharing all the details.
        
       | gaigalas wrote:
       | Why not introduce a single shared CSS for style consistency? Not
       | full CSS separation, each tool could still have its local CSS.
       | 
       | Things like styling buttons, responsiveness, and so on are better
       | solved once.
       | 
       | A good rule of thumb is: if the shared CSS fails to load, page
       | still fully works but it might be uglier (weird fonts, etc).
       | That's a reasonable rule for proper isolation (tools remain
       | simple to understand, code remains reusable, etc).
       | 
       | I love the idea of self-contained tools, but you're already using
       | CDNs. Having a shared CSS wouldn't hurt and actually make the
       | tools better.
       | 
       | I would go as far as having a shared JS too (same idea, works if
       | it doesn't load).
       | 
       | That's essentially what I did in
       | https://alganet.github.io/spiral/ (also vibe coded).
       | 
       | Each spiral is mostly independent. You can go ahead and delete
       | the shared CSS from the <head>, they still work and don't break
       | funcionality. However, by having the shared CSS I made them
       | consistent, made them friendly to phone users and so on.
        
         | simonw wrote:
         | Yeah, I've been thinking some kind of reusable styles or style
         | guide might be a good idea at this point.
         | 
         | It's been fun collecting a bunch of inconsistent tool designs
         | just to see how the different models behave, plus occasionally
         | I go for something with a topical theme like
         | https://tools.simonwillison.net/terminal-to-html or
         | https://tools.simonwillison.net/new-yorker-style - but a little
         | more consistency could be nice.
        
           | gaigalas wrote:
           | Definitely!
           | 
           | Not only for the user, but it makes sense for the process of
           | making the tools as well.
           | 
           | If I left the agent for itself, it often come up with
           | outrageous styles and I need to prompt it for something more
           | sober.
           | 
           | ---
           | 
           | You can do a lot with just CSS. I restored this 2009 project
           | of mine just now:
           | 
           | https://alganet.github.io/ghiaweb/
           | 
           | It still works (minor misalignments though), all HTML is pure
           | (no class=, no css=, no <div>). The global CSS does
           | everything: the forms, the drop-down menus, etc.
           | 
           | Nowadays, we can do even better, no build step or anything
           | like that.
        
       | eliben wrote:
       | Great work, Simon -- thanks for sharing!
       | 
       | One tool I'd really like to see in this format is a simple "turn
       | the background of this PNG to transparent". Models still refuse
       | to follow the instruction to create transparent backgrounds for
       | logos they create, and I often have to look for other tools doing
       | this as post-processing.
       | 
       | It's possible that this is too complicated for the "few hundred
       | lines of js" code envelope, though.
        
         | simonw wrote:
         | Running this now...                 Build transparent-png.html
         | - a tool that lets you open any image and then click on colors
         | within that image to make them transparent - showing a preview
         | of the resulting PNG against a checkerboard pattern and
         | optional against other selected background colors below, plus a
         | download PNG option              It should also accept pasted
         | images
         | 
         | Here's what I got (from Opus 4.5 in Claude Code for web via the
         | Claude iPhone app):
         | https://tools.simonwillison.net/transparent-png
        
           | indigodaddy wrote:
           | Dude, you are amazing. Maybe you should make a suggestion
           | box. Then while you are sleeping, the AI could evaluate the
           | suggestions, and if they are good, it could prototype the
           | tool, and then you could review the prototypes in your waking
           | hours before clicking the tool to production. :)
           | 
           | (I'm not actually kidding)
        
           | eliben wrote:
           | Nice, I'm proud I managed to nerd-snipe you :-) Thanks for
           | taking the time.
           | 
           | Seriously, though, I think this solves a nicely framed
           | simpler problem. I was thinking about a more general tool,
           | but that's genuinely hard (you'll need heavy CV algorithms or
           | a special ML model to detect what is background what what
           | isn't).
           | 
           | To be honest, what you built here is probably sufficient
           | anyway, because the models are better at obeying "create a
           | white background" or "create a 0xffffff background" than
           | "transparent", so this tool can post-process to what's
           | needed.
           | 
           | When asked for "transparent", I've had a model generate a
           | fake checkerboard pattern of gray colors to imitate how
           | viewers render transparent areas :-) For this kind of
           | nonsense, the transparent-png tool wouldn't do!
        
       | throwaway7783 wrote:
       | In the same spirit I have started building single page nanodjango
       | +htmx apps hosted on railway, starting with, you guessed it, a
       | Todo list app. No login needed.
       | 
       | https://web-production-1fc69.up.railway.app/
        
       | bilater wrote:
       | Have been doing the same except I do use React because its my
       | hammer haha. But I agree if you're not used to it then pure
       | html/cs/js make sense.
       | 
       | https://www.hackyexperiments.com/micro
        
       | soared wrote:
       | Surprised AI studio isn't recommended here - you can go from
       | prompt->preview->deploy in like a couple seconds.
       | 
       | They have a library of sample apps you can edit but I wish they
       | included the prompts and history to build each since I generally
       | can't get large apps to work - after a while the I'll just
       | produces more bugs as complexity grows. But I'm also a bad vibe
       | coder and never read the code so entirely my fault :)
        
         | simonw wrote:
         | I don't trust it to give me share URLs that anyone can access
         | without a Google account and that will work forever into the
         | future.
         | 
         | It may well do that, but it's not earned my trust yet!
        
           | soared wrote:
           | Ah I've only used that feature once actually I'll have to try
           | it, I was talking about the deploy on gcp button (but
           | pointlessly not free compared to your github pages recco!)
        
           | indigodaddy wrote:
           | I was displeased as well with the share url requiring login
           | for AI studio.
        
       | jackfranklyn wrote:
       | The barcode font trick from valbaca is brilliant. I've done
       | similar things with base64-encoded data URIs to make tools
       | completely standalone - no network calls, no CDN dependencies.
       | 
       | One pattern I've settled into: keeping tools under ~200 lines of
       | JS total. Past that threshold I start losing the ability to hold
       | the whole thing in my head, and the main benefit of these tools
       | is that you can open them in a text editor and understand
       | everything immediately.
       | 
       | The CORS limitation that xnx mentions is real though. I've worked
       | around it a few times by having tools accept paste-from-clipboard
       | instead of fetching URLs directly. Less elegant but it keeps the
       | tool self-contained and avoids the proxy problem simonw
       | mentioned.
        
       | ulrischa wrote:
       | This was the first submission:
       | https://news.ycombinator.com/item?id=46236333
        
       | christophilus wrote:
       | Nice. I do something similar, but with Bun + Preact, since I love
       | that stack. I've found it very productive after the hurdle of the
       | initial project setup (in which I wasted time crafting an RPC
       | layer, etc, because I wanted to.)
        
       | mettamage wrote:
       | > Avoid React, or anything with a build step. The problem with
       | React is that JSX requires a build step, which makes everything
       | massively less convenient. I prompt "no react" and skip that
       | whole rabbit hole entirely.
       | 
       | I haven't found too many issues with loading React and Babel from
       | a CDN. I find React easier to read than straight HTML/JS. I find
       | it more annoying to code in but seeing what state is needed in
       | what components is a pleasant reading experience for me with
       | single file tools.
        
         | binsquare wrote:
         | Yep - the author is very opinionated and it's good because it
         | works really well for his workflows/usecase.
         | 
         | I'm with you though, personally react is a acceleration
         | mechanism for me because I often find existing well built
         | components already. I don't built the same thing as the author
         | though.
        
       | cooljoseph wrote:
       | I've done something similar for a couple tools.
       | 
       | I tend to make them as Python servers which serve plain
       | html/js/css with web components. I know this is a bit more
       | complicated than just having a single html file with inline js
       | and css, but the tools I made were a bit too complicated for the
       | LLMs to get just right, and separating out the logic into
       | separate js files as web components made it easy for me to fix
       | the logic myself. I also deliberately prompted the LLMs to avoid
       | React because adding I didn't want to need a build step.
       | 
       | The only one I actually still use is the TODO app I made:
       | https://github.com/cooljoseph1/todo-app It stores everything in a
       | JSON file, and you can have multiple TODO lists at once by
       | specifying that JSON file when you launch it.
        
       | mettamage wrote:
       | > HTML tools may not have access to server-side databases for
       | storage but it turns out you can store a lot of state directly in
       | the URL.
       | 
       | I use indexedDB for it and will use sqlite if I start to get more
       | serious data needs.
        
       | calebm wrote:
       | I've been calling these Single-File Web Apps. I've written a
       | couple myself:
       | 
       | [?] https://fuzzygraph.com [?] https://hypervault.github.io/
        
         | calebm wrote:
         | Slight correction: I'd only refer to it as a "Single-File Web
         | App" if it has no external dependencies (where as the "HTML
         | Tool" definition allows for external dependencies loaded from
         | CDNs).
        
       | blixt wrote:
       | One thing I tend to do myself is use https://generator.jspm.io/
       | to produce an import map once for all base dependencies I need
       | (there's also a CLI), then I can easily copy/paste this template
       | and get a self-contained single-file app that still supports JSX,
       | React, and everything else. Some people may think it's overkill,
       | but for me it's much more productive than
       | document.getElementById("...") everywhere.
       | 
       | I don't have a lot of public examples of this, but here's a
       | larger project where I used this strategy for a relatively large
       | app that has TypeScript annotations for easy VSCode use, Tailwind
       | for design, and it even loads in huge libraries like the Monaco
       | code editor etc, and it all just works quite well 100%
       | statically:
       | 
       | HTML file: https://github.com/blixt/go-
       | gittyup/blob/main/static/index.h...
       | 
       | Main entrypoint file: https://github.com/blixt/go-
       | gittyup/blob/main/static/main.js
        
       | rossant wrote:
       | Awesome. This is the way.
        
       | steren wrote:
       | I've also been creating HTML tools over the years when I couldn't
       | find client-side only websites for PDF to SVG, CSV to Sheet,
       | Audio to Video, Video to MP4.
       | 
       | I list them at https://client-side.app/
        
       | meistertigran wrote:
       | I was also heavy on these single-file HTML tools. My problem was
       | not being able to sync the localstorage data between PC and
       | smartphone. I made a paid service which did that automatically.
       | It didn't take off unfortunately, and I am keeping it on
       | maintenance mode because it has some amount of free users that
       | rely on it.
       | 
       | For anyone interested, to achieve synchronization I basically
       | just use the https://github.com/google/diff-match-patch lib and
       | save the patches in a db for each version with a version id. Then
       | there's a generic JS file that I inject to uploaded HTML files
       | that monkey patches the localstorage methods and optimistically
       | updates the localstorage at the same time sending the diff to the
       | server to save to the db.
        
       ___________________________________________________________________
       (page generated 2025-12-13 23:00 UTC)