Button.

Starts something that matters: book a demo, send the form, open the Toolbox. One primary per view, square geometry with a slight round, and a redline that means “this one.”

Playground.

Every variant, ground, size and state, on the real component.

Variant
Ground
Size
Trailing
State
import { Button } from "#/ui/site/button";

<Button href="/demo">
  Book a demo
</Button>

Variants.

Primary is the redline fill, the one action the view is for. Secondary is a hairline outline, for the action beside it.

Show code
<Button href="/demo">Book a demo</Button>
<Button href="/toolbox" variant="secondary">Open the Toolbox</Button>

On ink.

On an ink plate the primary takes vellum, not the redline, so the plate keeps one bright thing and it is the action. Set onInk; a reversed page does not need it, because its tokens already invert.

Show code
<Button href="/demo" onInk>Book a demo</Button>
<Button href="/toolbox" variant="secondary" onInk>Open the Toolbox</Button>

Sizes.

md is every call to action on the site. sm is for a control inside a panel or a form row, where a full CTA would shout.

Show code
<Button href="/demo">Book a demo</Button>
<Button href="/demo" size="sm">Book a demo</Button>

Trailing arrow.

The arrow travels up and out on hover. arrow is for an internal link that leaves the page’s subject; external also opens a new tab, as any https: link does on its own.

Show code
<Button href="/redliner" variant="secondary" arrow>Try the Redliner</Button>
<Button href="https://..." variant="secondary" external>Follow on LinkedIn</Button>

Pending.

While a request is in flight, a spinner leads the label and the label stays, so the button still says what it is doing. Clicks are held and aria-busy is set. It is not dimmed: the spinner already says busy.

Show code
<Button type="submit" pending={status === "sending"}>Request a demo</Button>

Disabled.

Sixty percent opacity and a not-allowed cursor, everywhere on the site. Prefer explaining why beside it, as the cookie preferences do under Global Privacy Control, to a button that silently does nothing.

Show code
<Button onClick={save} disabled={gpc}>Save preferences</Button>

Dimensioned.

Hover it. Extension lines drop from its edges and a dimension draws in under it, as if the button were a feature on a drawing. For the hero’s primary CTA and the closing band, nowhere else.

Show code
<Button href="/demo" dimension>Book a demo</Button>

Pen circle.

Hover it. A redline ellipse draws itself around the button, the way a checker circles a callout. It is the Redliner’s mark and only the Redliner’s: every CTA pointing at it sets penCircle, and no other CTA does. It stills the travelling arrow, so there is one hover event, not two.

Show code
<Button href="/redliner" variant="secondary" penCircle arrow>Try the Redliner</Button>

Best practices.

Do

  • One primary per view. Everything else is secondary or a text link.
  • Say what happens: Book a demo, Send the request. Never Submit or Click here.
  • Use pending while a request is in flight, so the label stays and the click is held.
  • Reach for size sm only inside a panel or a form row.

Don't

  • Do not put two primaries side by side.
  • Do not copy Button's classes into a hand-rolled button. Add a prop instead.
  • Do not use the pen circle on anything that does not lead to the Redliner.
  • Do not dim a pending button or swap its label for Sending...