Form.

A form is a sheet laid on the drawing: vellum, a hairline frame, a hard offset shadow, and white fields, so an input is the lightest thing on the page and the only white surface on it. Every part comes from one module, ui/site/form.tsx.

Playground.

Control
State
import { FIELD, LABEL } from "#/ui/site/form";

<label htmlFor={id} className={LABEL}>Work email</label>
<input id={id} type="email" className={cn(FIELD, "mt-2")} />

Field.

White, a graphite hairline that darkens on hover, and the redline border with a soft ring on focus. The label is mono uppercase in graphite, a step darker than ambient annotation, because it labels something the visitor has to find. Every field has a visible label; a placeholder is an example, never the label.

Show code
import { FIELD, LABEL } from "#/ui/site/form";

<label htmlFor={id} className={LABEL}>Company</label>
<input id={id} className={cn(FIELD, "mt-2")} />

Chips.

For a choice of several. Selected is the redline border on a redline tint, the site’s one selected look; each chip is a button with aria-pressed.

Which CAD system do you use?

SolidWorksSiemens NXOnshape
Show code
import { CHOICE_BASE, CHOICE_OFF, CHOICE_ON } from "#/ui/site/form";

<button type="button" aria-pressed={on} className={cn(CHOICE_BASE, on ? CHOICE_ON : CHOICE_OFF)}>
  SolidWorks
</button>

Radio tiles.

For exactly one of several, where the options exclude each other. Native radios inside the chip’s tile, accented in the redline, as checkboxes are too.

Drawings a month

Show code
<label className={cn(CHOICE_BASE, "flex items-center gap-3", on ? CHOICE_ON : CHOICE_OFF)}>
  <input type="radio" name="volume" className="accent-redline" />
  6-20 / month
</label>

Calculator fields.

Where a form is a tool rather than a request.

The calculators set two more, both on the same white box and redline focus: a field whose label rides up inside it once it has a value, on the savings calculator, and a number boxed inside a sentence. They live with their calculators, because a page of numbers is the only place either belongs.

Best practices.

Do

  • Give every field a visible label, and use the placeholder only for an example.
  • Ask for the least you need. The demo form's first step asks for an email and nothing else.
  • Show one error under the fields, in words a person would use, and keep what was typed.
  • Use chips for several choices and radio tiles for one.

Don't

  • Do not copy the field recipe into a form. Import it from ui/site/form.
  • Do not label a field with its placeholder.
  • Do not put a field on paper; a form sits on its vellum sheet.
  • Do not mark a field red before the visitor has tried to send.