Feedback.
How the site answers back: a request in flight, one that failed, one that landed, a thing copied, a process running. Each says what happened in plain words, in place, and changes nothing else.
Playground.
A lead form through its four states, with the parts every form on the site shares.
<Button type="submit" pending={sending}>Request a demo</Button>Pending.
The site has one spinner: a ring with a quarter open, in the color of whatever holds it. On a button it leads the label, which stays. Under reduced motion it stands still, and an open ring still reads as working.
Show codeHide code
import { Spinner } from "#/ui/site/spinner";
<Spinner /> // takes the current text color
<Button type="submit" pending={sending}>Send the request</Button>Error.
One box under the fields, in the redline’s tint, read out as it appears (role="alert"). It says what went wrong and what to do, and the form keeps everything that was typed.
Something went wrong sending that. Try again in a moment, or write to info@drafterinc.com.
Show codeHide code
import { FormError } from "#/ui/site/form";
{error ? <FormError className="mt-6">{error}</FormError> : null}Sent.
The form becomes its own receipt: the same vellum sheet, a kicker naming what was received, and one line on what happens next. No confetti, no redirect.
Request received
Thanks. We will be in touch shortly.
Someone from the team will reach out to set up a time.
Show codeHide code
import { FormSent } from "#/ui/site/form";
<FormSent kicker="Request received" title="Thanks. We will be in touch shortly.">
Someone from the team will reach out to set up a time.
</FormSent>Copied.
A confirmation is the past tense of the action, where the action was, for about a second and a half: Copy link becomes Copied, Save preferences says Saved. Try it.
Share these numbers
The link keeps every number you changed.
Show codeHide code
// Confirmations are the past tense of the action, in place:
// "Copy link" becomes "Copied" for a beat, then returns.Status lights.
Three states, each with one meaning everywhere: running is the redline on a quick pulse, done is a still green, and live is green on a slow pulse, for a real, running thing rather than a mock. A light always sits beside words that say the same.
import { StatusLight } from "#/ui/site/status-light";
<span className="flex items-center gap-2 ...">
<StatusLight state="running" />
Running
</span>Empty.
An empty state says so plainly and offers the next useful thing, as the careers page does with no roles open. It never pretends to be full.
We do not have a posting up right now. We still hire when we meet the right person, so the note below is the way in.
Best practices.
Do
- Say what happened in plain words, where it happened.
- Keep the label on a pending button, so it still says what it is doing.
- Keep everything the visitor typed when something fails.
- Pair every status light with words.
Don't
- Do not add a toast for something the page already shows.
- Do not use green for anything but done and live, or the redline for anything but running among lights.
- Do not redirect on success; let the form become its receipt.
- Do not animate a confirmation beyond a beat.