# 0005 — Tailwind via Topcoat, with components copied rather than depended on

**Status:** accepted · **Date:** 2026-08-11

## Context

Everything was unstyled HTML. Milestone 2 phase 2 adds the first form that needs
success and failure feedback, so a visual language was needed before more pages
inherited the absence of one — three pages is the cheapest this decision ever gets.

Attempt #1 had a complete OKLCH design system for Tailwind and DaisyUI. Attempt #2
dropped Tailwind entirely, for one reason: the npm build step. Topcoat's Tailwind
integration wraps the standalone Tailwind CLI from a Cargo build script — no Node, no
PostCSS — which removes that objection.

Topcoat also ships `topcoat ui`, a component registry that **copies source into the
project** rather than being a library dependency.

## Decision

Tailwind through Topcoat's build-script integration, with `styles.css` as the theme.

Components come from two places:

- **Borrowed primitives**, added with `topcoat ui add` — button, input, label,
  textarea, card. Boilerplate worth not writing.
- **Steid's own**, written by hand — starting with `flash`, which the registry has no
  equivalent for. Anything Steid-specific (profile header, repo row, commit bar) is
  ours.

The distinction is thin, because copied source is ours the moment it lands. A borrowed
component that stops fitting gets edited or deleted, not worked around.

**The theme is ours from the start.** Topcoat's `neutral` theme was the scaffold;
its tokens were retuned to attempt #1's palette — dark-first at hue 260, blue primary
— and extended with `--surface`, `--success`, `--warning` and a monospace family.
Success and warning were absent from `neutral` but are needed for flash messages now
and git diff semantics later.

**The rule that makes this work: components reference tokens, never raw colours.** A
hardcoded colour does not follow a palette change and does not adapt to the colour
scheme. This is the one convention worth enforcing in review.

## Alternatives considered

- **Hand-rolled CSS, no Tailwind.** No build script, no download, full control.
  Rejected: it also means hand-rolling the token structure, the colour-scheme
  switching, and the focus-ring conventions that the theme already solves.
- **Tailwind only, without the `ui` feature.** Skips `components.toml` and the
  registry. Rejected narrowly — the borrowed primitives are genuinely boring code, and
  their house style is a useful model for ours.
- **Depending on a component library.** Rejected: Topcoat is weeks old and expects
  breaking changes. Copied source cannot break underneath us on a version bump, which
  matters more here than in a mature ecosystem.
- **DaisyUI, as attempt #1 planned.** Rejected: a second styling dependency on top of
  Tailwind, and not bundled by Topcoat.

## Consequences

- **Slower builds.** A build script runs Tailwind on every change. The CLI is
  downloaded once and cached under the target directory.
- **`topcoat asset bundle` is required for manual builds** before assets resolve;
  `topcoat dev` does it automatically. A stale bundle serves stale CSS.
- **Utility classes live in markup.** That is the Tailwind bargain.
- **`src/components.rs` is rewritten by `topcoat ui add`**, so hand-written entries can
  be reordered. Keep them alphabetical and expect churn there.
- **A palette change is a one-file edit**, provided the token rule holds.
- **The theme is versioned in `components.toml`** by hash. A registry theme update will
  not silently overwrite local retuning.
- `plans/ui.md` now describes this rather than attempt #1's DaisyUI plan.
