Skip to content
Build your own tools

← Build guide

What to deliver

Always. Read this before writing any code — it constrains every other section.

Section 1 Plain text (.md)

Who you are building for

Someone doing this alongside their actual job — an administrator, an organiser, a volunteer. No development environment, no terminal, no code editor, no package manager, no admin rights on the laptop. They have a browser and a chat window with you. Their time budget is one evening.

Anything that requires them to install software, run a command, or start a local server has failed before it starts. This is not a preference — it is the constraint that decides whether the tool gets used.

The deliverable

A folder the user saves. index.html is what they double-click.

How many files that folder holds is your choice. What matters is that it runs from file:// without anything being installed or started.

RuleWhy
index.html runs by double-clickNo server, no npx, no terminal
No build step, no bundler, no npmThey cannot run any of it
No <script type="module">, no importBlocked on file:// — the tool silently does nothing
No fetch() of a local fileBlocked on file:// — put data in a .js file instead
Classic <script src> and <link rel=stylesheet>These do work across files, including subfolders
Libraries from CDN, global (UMD) build, pinned versionNo install; a floating version breaks the tool later
UI text in the language the tool will be used inNot necessarily the language they wrote to you in — see below

Which language the interface is in

The language of the use, not the language of the request. Someone may write to you in English about a tool for a Flemish town council — that tool belongs in Dutch, because that is who will sit in front of it.

Derive it from where the tool will be used: the municipality, the institution, the audience for its output. When the request itself and the place disagree, and you cannot tell which one governs, ask once instead of guessing.

What follows the interface language, and is easy to forget:

Two things stay in English regardless, because they are not the interface: this guide, and the rules file you leave for the next assistant (CLAUDE.md / AGENTS.md). Those are read by machines and developers.

One file or several?

Both are fine. Choose by handover cost, not by tidiness.

Start with one file when the tool is small and you are handing it over through a chat. It is the easiest thing to save out of a conversation, to email, and to keep track of. For a calculator, a form, a checklist, a single analysis — one file is right, and splitting it is busywork.

If you can write files into a folder yourself — an agentic assistant, a workspace with file output — that argument disappears: there is no handover to get wrong, so split whenever it aids readability. Read How you are delivering next; it also covers the trap that comes with having a terminal the user does not have.

Split into several files when the tool genuinely earns it: a few hundred lines of logic, a sizeable data table, a stylesheet worth reading on its own. A flat folder is the usual shape:

mein-werkzeug/
├── index.html
├── app.js
├── daten.js
└── stil.css
<link rel="stylesheet" href="stil.css">
...
<script src="daten.js"></script>
<script src="app.js"></script>

Scripts run in document order, so a global defined in daten.js is available in app.js. Verified working from a double-clicked file, subfolders included.

Keep the folder flat when you split. Nested directories work technically, but every extra path is another chance for the user to save a file in the wrong place. index.html + two or three siblings is the sweet spot.

Say how to save it. When you hand over more than one file in a chat, name each file explicitly and say they all go in the same folder. That is the only part of multi-file delivery that is genuinely harder for the user — and it does not apply when you write the folder yourself.

Data belongs in a .js file, not a .json file

fetch('daten.json') fails from file:// — Chrome reports URL scheme “file” is not supported. Do not use it, and do not work around it with a local server.

Instead, write the data as JavaScript that assigns a global:

// daten.js
window.DATEN = {
  gemeinden: [
    { name: 'Oberndorf', einwohner: 5700 },
    { name: 'Herzogenburg', einwohner: 8300 }
  ]
};

Same content, loads with a plain <script src="daten.js">, and the user can still open and edit it in any text editor.

What DOES work from a double-clicked file

Verified in Chromium: window.isSecureContext is true for a local file, so the following are all available without a server.

What does NOT work

The skeleton to start from

<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Tool name</title>
<link rel="stylesheet" href="https://design-system.flomotlik.me/design-system.css">
<style>
  body {
    margin: 0;
    padding: 1.5rem;
    background: var(--fm-color-surface);
    color: var(--fm-color-text);
    font-family: var(--fm-font-copy);
  }
  main { max-width: 60rem; margin: 0 auto; }
</style>
</head>
<body>
<main>
  <h1>Tool name</h1>
  <!-- UI here -->
</main>
<script>
  // Logic here, or in a sibling file via <script src>. No imports.
</script>
</body>
</html>

The body rules are not optional: the design system deliberately styles no HTML tags, so without them the page renders as unstyled serif text and looks broken. See Making it look right.

How to scope the first version

Build the smallest thing that actually answers their question, then stop and let them use it.

Smallest is measured against the question, not against a screen count. Test every control you are about to add:

Can they answer the question they asked me without this element? If yes, leave it out. If no, it belongs in the first version.

A year selector in a tool for comparing years is not feature creep — without it the tool does not answer the question. A settings panel nobody asked for is. The difference is whether the element follows from the request or from your own enthusiasm.

Then ship it before adding anything else. What they still miss after using it three times is a real feature; what occurred to you while building usually is not.

When this stops being enough

Only when the user needs a public address, several linked pages, or other people contributing. That is a different shape of project — see Going further.