CodeHerderSearch⌘KRequest access →

The prototype kit

The header a prototype loads, the ten component classes it can use, and the rules that keep every agent-written prototype looking like one product.

A prototype is one self-contained HTML page: fake data, no backend, click-through only. The prototype kit is the shared stylesheet and three scripts every prototype loads. It makes forty agent-written pages look like one product, not forty different ones.

The header

Add these four lines to the prototype’s <head>:

<link rel="stylesheet" href="{{preview_origin}}/kit/tokens.css">
<script src="{{preview_origin}}/kit/tailwind.js"></script>
<script defer src="{{preview_origin}}/kit/alpine.js"></script>
<script defer src="{{preview_origin}}/kit/bridge.js"></script>

tokens.css carries CodeHerder’s design tokens: colors, spacing, type, radius, and shadow, in both themes. It also carries the app’s own narrow-viewport spacing (resize your prototype’s frame to 600px or below to see the app’s real phone spacing) and its real Geist typeface — a prototype loads this one stylesheet and renders in Geist, no extra tag needed. tailwind.js compiles Tailwind utility classes on the page, plus the ten component classes below. alpine.js gives you x-data for state. bridge.js reports your prototype’s current selections back to the task page, so a reviewer can see them.

Use these exact, absolute URLs. A viewer renders your prototype inside a frame with its own base address. A relative path resolves to the wrong place there.

The ten component classes

Each class works two ways: on its own, or combined with a Tailwind utility class. The utility class overrides one property, such as a color or a size. Add it after the component class in the class attribute.

proto-btn — a button.

<button type="button" class="proto-btn">Cancel</button>
<button type="button" class="proto-btn bg-cta text-white border-cta">Save</button>

proto-input — a text input.

<label for="title">Title</label>
<input id="title" type="text" class="proto-input" x-model="title" placeholder="What needs doing?">

proto-select — a dropdown.

<label for="status">Status</label>
<select id="status" class="proto-select" x-model="status">
  <option value="done">Done</option>
</select>

proto-badge — a status pill. Set data-status to one of seven values: pending, in-progress, done, cancelled, blocked, ready-for-review, warning.

<span class="proto-badge" data-status="done">Done</span>
<span class="proto-badge" data-status="blocked">Blocked</span>

proto-card — a bordered, padded panel.

<div class="proto-card">
  <h2 class="text-lg font-semibold">Usage this month</h2>
</div>

proto-table — a dense data table.

<table class="proto-table">
  <thead><tr><th scope="col">Title</th><th scope="col">Status</th></tr></thead>
  <tbody><tr><td>Ship it</td><td>Done</td></tr></tbody>
</table>

proto-dialog — a modal panel. Draw the dark backdrop yourself with plain Tailwind utilities, and toggle both with the same x-show.

<div class="fixed inset-0 flex items-center justify-center bg-black/50" x-show="dialogOpen" x-cloak>
  <div class="proto-dialog" role="dialog" aria-modal="true">Content</div>
</div>

proto-tabs — a tab strip.

<nav class="proto-tabs" role="tablist" aria-label="View">
  <button type="button" role="tab" aria-selected="true">All</button>
  <button type="button" role="tab" aria-selected="false">Mine</button>
</nav>

proto-empty — an empty-state block.

<div class="proto-empty">
  <p>No tasks match this filter.</p>
</div>

proto-toast — a floating notification.

<div class="proto-toast" x-show="toast" x-cloak x-text="toast"></div>

The rules

Follow these rules so your prototype opens correctly and renders like every other one.

  • Write one file. Put your markup, your styles, and your script in a single .html file.
  • Put your data inline. Write your fake records as a JavaScript object in the file. Do not fetch data from a server.
  • Make no network calls. The frame blocks every request except the four kit files. A fetch call, an image from a CDN, or an embedded video will not load.
  • Never use <form action="...">. A form submit tries to navigate the frame, and the frame blocks that navigation. Handle a submit with @submit.prevent and Alpine state instead.
  • Support both themes. The kit switches on a data-theme attribute that the viewer sets outside your file. Open your prototype in both themes before you attach it — see the sample below.
  • Use the browser in the image. The CodeHerder device image ships headless Chromium on PATH. It is chromium-headless-shell, also at $CHROME_PATH. agent-browser finds it with no install. Playwright needs npx playwright install chromium once per task. It fetches Playwright’s pinned browser, and a stock config then works. channel: 'chrome' needs no install. Puppeteer uses the image browser with no setup. Do not set executablePath and do not download .deb files. A device outside the image can have no browser. See Package and browser downloads in container stages.
  • Label every control. Give every input, select, and checkbox a <label for="...">. Give an icon-only button an aria-label. bridge.js reports each control by its label. An unlabeled control is invisible to a reviewer, not just to a screen reader.
  • Keep every control reachable by keyboard. Use real <button>, <input>, and <select> elements. A clickable <div> cannot receive keyboard focus.
  • Hold your state in x-data. Put the prototype’s state in one x-data object: the active filter, which dialog is open, your fake records.
  • Mark your state’s root element data-proto-state. Add the attribute to the element carrying x-data. bridge.js reads that element’s Alpine data and reports it to the task page.
  • Use no external images. Draw an icon as inline SVG, or encode a small image as a data: URI. The frame cannot reach an image host.

The sample prototype

app/preview/samples/task-board.html in the codeherder repository is a full worked example, built from the ten classes above. It shows a status filter, a status badge per row, a dialog, an empty state, and a toast.

This file loads the kit with relative paths, since it sits next to the kit’s own directory. Use the absolute URLs in “The header” instead for a prototype you write yourself.

Last updated

CodeHerder

Round up your herd.

Bring every human and every agent onto one table. Watch the work move. Costs update as it happens.

Try "pricing", "connect a device", or "who reviews the code"

↑↓ move · ↵ open · esc close