Agent skill

simon-html-tools

Build single-file HTML tools following Simon Willison's patterns - self-contained HTML+JS+CSS applications optimized for LLM generation, no build step, CDN dependencies. Use when creating browser-based tools, utilities, or demos that should be (1) Self-contained in one HTML file, (2) Easy to distribute and host statically, (3) Quick to prototype with LLMs, (4) Client-side only with no server requirements. Ideal for data visualization, API explorers, format converters, debugging tools, and interactive demos.

Stars 163
Forks 31

Install this agent skill to your Project

npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/simon-html-tools

SKILL.md

Simon's HTML Tools

Build production-quality single-file HTML tools following patterns from Simon Willison's collection.

Core Philosophy

HTML tools are self-contained browser applications that:

  • Combine HTML, CSS, and JavaScript in a single file
  • Load dependencies from CDNs (no build step)
  • Can be copy-pasted from LLM responses
  • Host anywhere as static files
  • Remain small and maintainable (typically <500 lines)

Essential Principles

1. Single File, No Build Step

Always avoid React and build tools:

Prompt: "Build a JSON to YAML converter. No React."

Why no React:

  • JSX requires compilation
  • Slower to render in LLM environments
  • More prone to bugs
  • Harder to copy/paste and distribute

2. Load Dependencies from CDNs

Use version-pinned URLs from cdnjs.com or jsdelivr.com:

html
<script src="https://cdnjs.cloudflare.com/ajax/libs/js-yaml/4.1.0/js-yaml.min.js"></script>

3. Keep It Small

Target <500 lines total:

  • Easy for LLMs to understand and modify
  • Can be rewritten from scratch in minutes
  • Maintainability becomes less critical

Implementation Patterns

State Persistence in URL

Store application state in the URL for bookmarking and sharing:

javascript
// Save state to hash
function saveState(state) {
  window.location.hash = encodeURIComponent(JSON.stringify(state));
}

// Load state from hash
function loadState() {
  if (!window.location.hash) return null;
  try {
    return JSON.parse(decodeURIComponent(window.location.hash.slice(1)));
  } catch (e) {
    return null;
  }
}

Secrets in localStorage

Never put API keys in URLs or code. Use localStorage:

javascript
function getApiKey(serviceName) {
  const key = localStorage.getItem(`${serviceName}_api_key`);
  if (!key) {
    const newKey = prompt(`Enter your ${serviceName} API key:`);
    if (newKey) localStorage.setItem(`${serviceName}_api_key`, newKey);
    return newKey;
  }
  return key;
}

Advanced Clipboard Operations

javascript
document.addEventListener('paste', async (e) => {
  e.preventDefault();
  
  for (const item of e.clipboardData.items) {
    if (item.type.startsWith('image/')) {
      const file = item.getAsFile();
      // Handle image
    } else if (item.type === 'text/html') {
      const html = await new Promise(r => item.getAsString(r));
      // Handle HTML
    }
  }
});

// Copy to clipboard with fallback
async function copyToClipboard(text) {
  try {
    await navigator.clipboard.writeText(text);
  } catch (err) {
    const textarea = document.createElement('textarea');
    textarea.value = text;
    textarea.style.position = 'fixed';
    textarea.style.opacity = '0';
    document.body.appendChild(textarea);
    textarea.select();
    document.execCommand('copy');
    document.body.removeChild(textarea);
  }
}

Working with Local Files

javascript
document.getElementById('fileInput').addEventListener('change', async (e) => {
  const file = e.target.files[0];
  
  // Read as text
  const text = await file.text();
  
  // Or as Data URL
  const reader = new FileReader();
  reader.onload = (e) => processImage(e.target.result);
  reader.readAsDataURL(file);
});

Generate Downloadable Files

javascript
function downloadText(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);
}

CORS-Enabled APIs

javascript
// PyPI
async function fetchPackage(name) {
  const res = await fetch(`https://pypi.org/pypi/${name}/json`);
  return await res.json();
}

// GitHub raw files
async function fetchGitHubFile(owner, repo, path) {
  const res = await fetch(
    `https://raw.githubusercontent.com/${owner}/${repo}/main/${path}`
  );
  return await res.text();
}

LLM API Direct Access

javascript
async function callClaude(prompt) {
  const apiKey = getApiKey('claude');
  const res = await fetch('https://api.anthropic.com/v1/messages', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-api-key': apiKey,
      'anthropic-version': '2023-06-01'
    },
    body: JSON.stringify({
      model: 'claude-3-haiku-20240307',
      max_tokens: 1024,
      messages: [{ role: 'user', content: prompt }]
    })
  });
  return await res.json();
}

Pyodide for Python

javascript
let pyodide = null;

async function initPyodide() {
  if (!pyodide) {
    pyodide = await loadPyodide();
    await pyodide.loadPackage(['numpy', 'pandas']);
  }
  return pyodide;
}

async function runPython(code) {
  const py = await initPyodide();
  return await py.runPythonAsync(code);
}

Template Structure

See references/template.md for complete HTML template with best practices.

Useful Libraries from CDN

Data Processing:

  • js-yaml, papaparse, jszip, dompurify

Visualization:

  • Chart.js, Plotly, Leaflet, highlight.js

File Processing:

  • PDF.js, Tesseract.js, exif-js, mammoth

Utilities:

  • date-fns, fuse.js, marked, diff

Hosting

Recommended: GitHub Pages

  1. Create repository
  2. Settings → Pages → Deploy from branch
  3. Copy HTML file
  4. Access at https://username.github.io/repo/tool.html

Avoid LLM platforms (restrictive sandboxes, warnings, less reliable)

Development Workflow

1. Prototype in Claude/ChatGPT

Build an artifact that converts CSV to JSON with preview. 
Use PapaParse from CDN. Add download button. No React.

2. Upgrade to Coding Agent

For complex projects: Claude Code or Codex CLI

3. Remix Existing Tools

Look at the pypi-changelog tool. Build a similar tool 
for npm packages showing version diffs.

Checklist

  • Single HTML file (no build step)
  • "No React" in prompts
  • CDN dependencies with versions
  • Code under 500 lines
  • Error handling for APIs
  • API keys in localStorage
  • State in URL or localStorage
  • Mobile responsive
  • Footer with source link

Resources

  • references/template.md - Complete HTML template structure
  • references/patterns.md - Detailed implementation patterns
  • references/libraries.md - Comprehensive CDN library catalog
  • references/examples.md - Real-world tool examples

Expand your agent's capabilities with these related and highly-rated skills.

Didn't find tool you were looking for?

Be as detailed as possible for better results