HTML Tool Builder
Build single-file HTML tools following Simon Willison's proven patterns for creating practical, self-contained web applications.
When to Use This Skill
Use this skill when asked to create:
- •Single-file HTML applications
- •Browser-based utilities and tools
- •Quick prototypes that need no build step
- •Tools that process files, clipboard data, or API responses locally
Core Principles
- •Single-file structure: All HTML, CSS, and JavaScript in one file
- •Own subdirectory: Each tool gets its own subdirectory (e.g.,
tools/json-formatter/index.html) - •No build steps: Avoid React and frameworks requiring compilation
- •CDN dependencies: Load external libraries from jsDelivr or cdnjs
- •Compact scope: Keep it simple - a few hundred lines max
- •No server required: Everything runs in the browser
Standard Template
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tool Name</title>
<style>
* { box-sizing: border-box; }
body {
font-family: system-ui, -apple-system, sans-serif;
max-width: 800px;
margin: 2rem auto;
padding: 0 1rem;
line-height: 1.6;
}
/* Add tool-specific styles here */
</style>
</head>
<body>
<h1>Tool Name</h1>
<p>Brief description of what this tool does.</p>
<!-- Tool UI goes here -->
<script>
// Tool logic goes here
</script>
</body>
</html>
Input/Output Patterns
File Input
html
<input type="file" id="fileInput" accept=".txt,.json,.csv">
<script>
document.getElementById('fileInput').addEventListener('change', async (e) => {
const file = e.target.files[0];
if (!file) return;
const text = await file.text();
// Process file contents
});
</script>
Clipboard Operations
javascript
// Read from clipboard
document.getElementById('pasteBtn').addEventListener('click', async () => {
const text = await navigator.clipboard.readText();
// Process pasted content
});
// Write to clipboard
document.getElementById('copyBtn').addEventListener('click', async () => {
await navigator.clipboard.writeText(outputText);
});
File Download
javascript
function downloadFile(content, filename, mimeType = 'text/plain') {
const blob = new Blob([content], { type: mimeType });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
Drag and Drop
html
<div id="dropZone" style="border: 2px dashed #ccc; padding: 2rem; text-align: center;">
Drop files here or click to select
</div>
<script>
const dropZone = document.getElementById('dropZone');
dropZone.addEventListener('dragover', (e) => {
e.preventDefault();
dropZone.style.borderColor = '#007bff';
});
dropZone.addEventListener('dragleave', () => {
dropZone.style.borderColor = '#ccc';
});
dropZone.addEventListener('drop', async (e) => {
e.preventDefault();
dropZone.style.borderColor = '#ccc';
const files = e.dataTransfer.files;
// Process dropped files
});
</script>
State Persistence
URL Hash (for bookmarkable state)
javascript
// Save state to URL
function saveState(state) {
window.location.hash = encodeURIComponent(JSON.stringify(state));
}
// Load state from URL
function loadState() {
if (window.location.hash) {
return JSON.parse(decodeURIComponent(window.location.hash.slice(1)));
}
return null;
}
localStorage (for API keys and preferences)
javascript
// Save API key securely in browser
function saveApiKey(key) {
localStorage.setItem('apiKey', key);
}
function getApiKey() {
return localStorage.getItem('apiKey') || '';
}
External Libraries via CDN
Common Libraries
html
<!-- Marked (Markdown parser) --> <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> <!-- Prism (Syntax highlighting) --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/prismjs/themes/prism.css"> <script src="https://cdn.jsdelivr.net/npm/prismjs/prism.js"></script> <!-- Papa Parse (CSV parser) --> <script src="https://cdn.jsdelivr.net/npm/papaparse@5/papaparse.min.js"></script> <!-- JSZip (ZIP file creation) --> <script src="https://cdn.jsdelivr.net/npm/jszip@3/dist/jszip.min.js"></script> <!-- html2canvas (Screenshot capture) --> <script src="https://cdn.jsdelivr.net/npm/html2canvas@1/dist/html2canvas.min.js"></script> <!-- Chart.js --> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
CORS-Friendly APIs
These APIs work directly from browser JavaScript:
- •PyPI:
https://pypi.org/pypi/{package}/json - •GitHub Raw:
https://raw.githubusercontent.com/{owner}/{repo}/{branch}/{path} - •Bluesky: Public API endpoints
- •Mastodon: Instance APIs
- •iNaturalist: Observations API
- •OpenAI/Anthropic/Gemini: LLM APIs (store keys in localStorage)
Advanced: Pyodide (Python in Browser)
html
<script src="https://cdn.jsdelivr.net/pyodide/v0.24.1/full/pyodide.js"></script>
<script>
async function initPython() {
const pyodide = await loadPyodide();
await pyodide.loadPackage(['pandas', 'matplotlib']);
const result = await pyodide.runPython(`
import pandas as pd
# Your Python code here
`);
return result;
}
</script>
Implementation Checklist
When building an HTML tool:
- • Single HTML file with inline CSS and JS
- • Responsive design (works on mobile)
- • Clear UI with instructions
- • Error handling with user-friendly messages
- • Loading states for async operations
- • No external dependencies except CDN libraries
- • Test in multiple browsers if possible
Example Prompt for New Tools
When the user describes what they want, build a complete HTML tool that:
- •Follows the single-file pattern
- •Uses semantic HTML and clean CSS
- •Handles errors gracefully
- •Provides clear feedback during operations
- •Works without any server
Output the complete HTML file ready to save and use.