HTML:Tools
lastmod 2025-12-15created 2025-12-151 edits
Small web tools that bundle JavaScript and CSS into a single HTML file and run immediately
About
- A summary of recurring design patterns, based on experience building HTML tools that bundle HTML, JavaScript, and CSS into one file
- Explains, through concrete examples, how the more than 150 small tools built over 2 years are actually made and maintained
- A no-build-step structure and use of CDNs naturally lead to a copy-and-paste-based development flow
- Extends what is possible with browser features alone, from URLs, localStorage, opening files, and downloads to Pyodide and WebAssembly
- Recording prompts and results and recombining existing tools strengthens a flow in which the tool collection itself becomes a development foundation
The HTML tools concept and examples
- Small web tools that bundle JavaScript and CSS into a single HTML file and run immediately are called HTML tools
- More than 150 built over the past 2 years, mostly made of code written by LLMs
- Representative examples
- svg-render: converts SVG code to PNG/JPEG images
- pypi-changelog: generates a diff of changes between PyPI package releases
- bluesky-thread: displays Bluesky discussion threads as a nested structure
Basic structure of an HTML tool
- A single HTML file with JavaScript and CSS inline
- Avoids React, which requires a JSX build, making copying and redistribution simple
- Needed libraries are loaded directly from CDNs such as CDNjs or jsDelivr, and dependencies are kept to a minimum
- Keeps code to a few hundred lines to lower the maintenance burden and keep it quick for LLMs to read and modify
Prototyping with Canvas and Artifacts
- Uses ChatGPT Canvas, Claude Artifacts, and Gemini Canvas to run HTML tools immediately and shape their initial form
- Adding a “No React” condition produces code that works right away without a build step
- Example: paste JSON and convert it to YAML
- ChatGPT Canvas result: JSON to YAML (ChatGPT), full conversation
- Claude Artifact result: JSON to YAML (Claude), full conversation
- Gemini Canvas result: JSON to YAML (Gemini), full conversation
Switching to coding agents when things get more complex
- With Claude Code or Codex CLI, repetitive work including browser testing with Playwright and the like can be automated
- For more complex tools (e.g., the Bluesky thread viewer), “upgrades” to these agents are used
- Tools are added and updated by creating PRs directly against the simonw/tools repository
- Repository: github.com/simonw/tools
- Actual PR history: list of closed PRs
CDN-based dependency management
- Additional libraries are loaded from CDNs
- LLM Artifacts/Canvas have a CDN allow-list, so an instruction like “use PDF.js” is enough to get the URL set up
- When needed, find the URL on cdnjs or jsDelivr and paste it into the chat
- Works as a single HTML file with no npm install or build process
- npm plus a build step reduces the productivity of quickly hacking on and self-hosting individual tools
External static hosting
- Hosting inside LLM platforms has many sandbox restrictions, sometimes blocking loading data/images from external URLs or external links
- In terms of user experience, warning messages, extra loading, and platform promotion can get in the way
- The “No React + CDN” combination makes it easy to put tools anywhere without a build
- Uploading an HTML file to GitHub Pages makes it immediately accessible at a fixed URL
- GitHub Pages guide: docs.github.com/pages
Copy-and-paste-centric input and output
- Many tools use, as their core mechanism, transforming pasted input and copying it back to the clipboard
- Copy and paste is awkward on mobile, so a “Copy to clipboard” button is often added
- The OS clipboard can hold multiple formats at once, and this “rich” data is also accessible from the JavaScript paste event
- Representative tools
- hacker-news-thread-export: converts HN threads into text for LLM input
- paste-rich-text: extracts the HTML of pasted rich text
- alt-text-extractor: extracts alt text after pasting images
Debugging tools
- Dedicated tools built to inspect the actual data structures handled in the browser
- clipboard-viewer: shows all clipboard data, including text, rich text, images, and files
- Additional tools
- keyboard-debug
- cors-fetch
- exif
Storing state in the URL
- A lot of state can be stored in the URL even without a server DB
- This pattern is preferred for tools meant to be bookmarked or shared
- icon-editor: preserves the 24×24 icon editing state directly in the URL
Using localStorage
- localStorage is a browser API that persistently stores data on the user's device without exposing it to the server
- Used to store large state that is hard to fit in a URL, or secrets such as API keys, to avoid the risk of exposure in a static host's server logs
- word-counter: automatically saves text being written
- render-markdown: keeps content while writing Markdown
- haiku: stores the API key in localStorage and generates haiku based on webcam images
Using CORS-enabled APIs
- CORS (Cross-origin resource sharing) is a mechanism that controls whether browser JavaScript can call APIs on other domains
- APIs with open CORS headers are valuable resources for HTML tools, and worth collecting into a list over time
- Uses iNaturalist, PyPI, GitHub (public repo content can be accessed anonymously via raw.githubusercontent.com), Bluesky, Mastodon, and more
- Example tools
- species-observation-map
- zip-wheel-explorer
- github-issue-to-markdown
- terminal-to-html
- bluesky-quote-finder
Calling LLM APIs directly
- Calls the OpenAI, Anthropic, and Gemini JSON APIs directly from the browser via CORS
- Embedding API keys in HTML risks theft and charges, so a “secrets pattern” that stores them in localStorage is used
- In terms of user experience, getting and pasting an API key adds a lot of friction, but it works
- Example tools
- openai-audio-output
- gemini-bbox
Don't be afraid of opening files
- With <input type="file">, files can be read and used directly in the browser without uploading them to a server
- ocr: uses PDF.js and Tesseract.js to turn PDFs into page images and performs OCR in the browser
- social-media-cropper: after opening/pasting an image, supports cropping to social media ratios (e.g., Twitter/LinkedIn 2:1, Substack 1.4:1)
- ffmpeg-crop: generates ffmpeg commands for cropping video
Offering downloadable files
- Files can be generated in the browser and offered as downloads without a server
- The JavaScript ecosystem has libraries for generating many formats
- Example tools
- svg-render: renders SVG and offers PNG/JPEG downloads
- social-media-cropper: offers downloads of cropped images
- open-sauce-2025: builds a schedule and offers an ICS file download that can be added to a calendar
Pyodide and WebAssembly
- Pyodide: a distribution of Python compiled to WebAssembly that runs in the browser
- It loads cleanly from a CDN, so there is no reason not to use it in HTML tools
- Additional pure-Python packages from PyPI can be loaded via CORS through micropip
- Example tools
- pyodide-bar-chart
- numpy-pyodide-lab
- apsw-query
Going further with WebAssembly
- Pyodide is made possible by WebAssembly, which also lets software written in other languages be loaded in the browser
- Squoosh.app provides several image compression libraries in the browser
- Example tools
- ocr : Tesseract.js uses a WebAssembly port of the existing Tesseract OCR engine
- sloccount**: ports David Wheeler's SLOCCount utility, written in Perl and C, to the browser. Detailed write-up.
- micropython** : @micropython/micropython-webassembly-pyscript
Recombining previous tools
- Another benefit is that with a single public collection of more than 100 tools, LLMs can easily recombine them
- Sometimes previous tools are copied into the context; with coding agents, they are referenced by name or the agent is asked to search for examples
- The source code of a working tool serves as documentation, including usage patterns for editing libraries, and raises the LLM's chance of success
- Full record of how pypi-changelog was built: prompt transcript
- The prompt instructed it to look at the “pypi package explorer” tool, read the zip-wheel-explorer source, and build a new tool that fetches wheels via the PyPI API, renders a diff between versions, and provides a Copy button
Recording prompts and transcripts
- A habit of saving and publishing LLM usage records helps you improve your own skill at using them
- For tools built on chat-based LLM platforms, the share feature is used as the recording method
- When using agents such as Claude Code or Codex CLI, the full terminal transcript is copied, converted to HTML with terminal-to-html, and shared as a Gist
- When saving a finished tool to the repo, a link to the transcript is included in the commit message - collection of records: tools.simonwillison.net/colophon
Wrap-up
- Exploring LLMs and HTML tools this way over the past year and a half has been very enjoyable, and greatly helped in understanding what can be built with HTML and what LLMs can do
- To start your own tool collection, just create a GitHub repo, enable GitHub Pages, and copy in .html files
- As a bonus, a link to the transcript of adding screenshots to this post using Claude Code and shot-scraper is provided
- Claude Code + shot-scraper transcript