You can point an AI coding agent — Claude, Cursor, Copilot — at i80 and have it build screens that look and behave like the rest of your product. i80 is made for this: the whole system is a stylesheet plus <i80-*> tags, and there is a machine-readable list of every tag the agent can read before it writes a line of markup.
This page is the human version of that brief. The agent reads /llms.txt and /elements/components.json; you read this.
Give your agent the guide
Paste this into your agent, or put it in a project rule file:
Build the UI with the i80 Design System. Follow https://design.i80.dk/llms.txt:
load components.css and i80.js (pinned to a version), add the box-sizing reset,
and use only tags and attributes from https://design.i80.dk/elements/components.json.
Write native elements (button, input, form, table) inside the tags so server-rendered
markup keeps its names, ids and CSRF. Do not add any font or script from another origin.
Two files back it up, both meant to be read by a tool:
/llms.txt— the short brief: how to load i80, the rules that matter, the content rules, and the links. Start here./elements/components.json— every tag with its attributes, their allowed values and defaults. This is the source of truth for what exists.
The rules it has to follow
These are the few things that go wrong when an agent guesses instead of reading, so they are worth stating up front:
- Load two files, pin a version.
components.cssandi80.jsfrom/v/x.y.z/; a pinned version never changes,/latest/follows main. - Add the box-sizing reset. The component styles assume
*, *::before, *::after { box-sizing: border-box; }and the stylesheet does not set it. Without it, padding is added on top of widths. - Only use documented tags and attributes. If it is not in
components.json, it does not exist. An unknown tag renders as an empty inline element and an unknown attribute is ignored silently — no error, just nothing. - Write native elements inside the tags. A
<button>,<input>,<form>or<table>inside an<i80-*>tag is kept and only styled, so a server-rendered form keeps its field names, ids, values and CSRF token. - Nothing leaves design.i80.dk. The fonts ship with the system; never add Google Fonts or a chart or script from another CDN.
- Use the tokens and type scale, not hand-set colours, fonts or spacing, so themes (light, dark, zplan) keep working.
How it should build
A reliable order for a build:
- Read
components.jsonand pick the tags the screen needs. - Load the files once at the top of the page, with the reset and
base.cssfor the page font. - Compose from tags, writing native elements inside them for anything the server renders.
- Lay the page out with the primitives:
<i80-flex>and<i80-grid>to arrange,<i80-app-frame>for a whole screen (header, side nav, main, footer),<i80-page-header>at the top of a view. - Keep the content rules: one
<h1>matching the title, a real heading outline, sentence-case headings, short lines. They are listed in/llms.txtand in full under Content and page structure. - Check the result: every
<i80-*>on the page is a real tag, and the reset is present.
A worked example
Prompt: A "New plan" form — a name field, an owner, and Save / Cancel.
A good agent reads the attributes for i80-text-input, i80-select and i80-button, then writes:
<form method="post" action="/plans">
<i80-flex direction="column" gap="16">
<i80-text-input label="Plan name" name="name" placeholder="Q3 launch" required></i80-text-input>
<i80-select label="Owner" name="owner" value="ada">
<option value="ada">Ada</option>
<option value="grace">Grace</option>
</i80-select>
<i80-button-set>
<i80-button color="primary"><button type="submit">Save plan</button></i80-button>
<i80-button color="secondary">Cancel</i80-button>
</i80-button-set>
</i80-flex>
</form>
It submits like a plain form: the <input>, <select> and submit <button> are real elements, i80 only styles them. In a Django or Jinja template the field is the server's own:
<i80-text-input label="Plan name" error="{{ form.name.errors.0 }}">
{{ form.name }}
</i80-text-input>
When the output is wrong
Most mistakes trace back to not reading components.json:
- A tag renders as nothing. The tag name is wrong or not an i80 tag — check it against
components.json. - An attribute does nothing. It is not one the element knows; it was ignored. Use the listed name.
- Padding makes boxes too wide. The box-sizing reset is missing.
- Text outside a tag is in the wrong font. Load
base.css, or setfont-family: var(--i80-typography-font-stack-text)on:root. - A form field submits empty or unlabelled. Write the native
<input>/<select>(with itsname) inside the tag rather than relying on attributes alone when the server owns the field.
The machine-readable files
/llms.txt— the brief, for the agent./elements/components.json— every tag, attribute, value and default.- Every tag with live examples and a whole app screen.