Button
The primary action trigger. Renders a real <button>, or a locale-aware link when given href — so a navigation that looks like a button still behaves like a link.
import Button from '@/components/ui/Button';Source: src/components/ui/Button.tsx
Examples
Colours
Five, and they are not interchangeable: red is the signup/commit action, blue the general one, green confirms, navy recedes, and facebook is the real brand blue (#1877F2) because a social login button in our cyan reads as a phishing attempt.
Emphasis: filled, outline, text
One filled button per view, ideally. Outline for the secondary action beside it; text for a low-emphasis action repeated many times down a page, where even an outline becomes visual noise.
Sizes
sm 35px, md 50px, lg 50px with a 300px minimum — lg is the full-width form submit, not a bigger button.
As a link
href routes through the locale-aware Link — prefetch, client navigation, translated pathnames. An external URL (https:, //, mailto:, tel:) opens in a new tab instead. Combined with disabled it renders an inert element, not a dead link.
Disabled and shadow
Props
<Button> props
| Prop | Type | Default | Description |
|---|---|---|---|
color | 'blue' | 'red' | 'green' | 'navy' | 'facebook' | 'blue' | — |
size | 'sm' | 'md' | 'lg' | 'md' | — |
outline | boolean | false | Border + text only, white background — matches the style guide's "outline" variant. |
text | boolean | false | Label only — no border or fill, a shade darker than the filled color, light-grey wash on hover. For low-emphasis actions (e.g. repeated card CTAs) where even an outline is too much chrome. Wins over `outline`. |
shadow | boolean | false | Adds the drop shadow used on the style guide's "shadow" button examples. |
href | InternalHref | string | — | Renders the button as a link: internal routes go through the locale-aware next-intl Link (prefetch + client navigation + translated pathnames), external URLs (https:, //, mailto:, tel:) open in a new tab. Combined with `disabled`, renders an inert element instead of a link. |
target | '_blank' | '_self' | — | Only meaningful with an internal `href`: opens the route in a new tab (`_blank`) instead of navigating this one. External hrefs already open in a new tab regardless. Sets `rel="noopener"` alongside `_blank` so the new tab has no `window.opener` back into this one — not a security necessity on a same-origin link, but free, and it keeps the new tab from poking the bonusway tab by accident. Use it for a route whose JOB is to leave the site (the waiting page), never as a default for internal links. |
Also accepts everything in ButtonHTMLAttributes<HTMLButtonElement> — so native attributes (id, onClick, aria-*, data-*) pass straight through.
Accessibility
- Renders a native <button> or <a>, so Enter/Space, focus order and the disabled semantics come for free.
- The keyboard-only focus ring is blue-800 — the lightest brand blue that clears 3:1 on white and on the grey-50 hover wash.
- An icon-only action belongs in IconButton, which requires an aria-label. A Button with no text has no accessible name.
Usage
Do
- Give the button a verb: “Save changes”, not “OK”.
- Use href for anything that navigates. A <button> that pushes a route breaks middle-click, ⌘-click and “open in new tab”.
- Pass target="_blank" only for a route whose job is to leave the site — the waiting page, which hands the visitor to a merchant. External hrefs already open in a new tab on their own.
- Keep one filled button per view so the primary action stays obvious.
Don’t
- Don’t use color to mean size or importance — that is what filled/outline/text are for.
- Don’t put a Button inside a Button href, or a link inside either.
- Don’t reach for facebook outside social login; it is a brand colour, not a palette entry.
- Don’t open ordinary internal links in a new tab; target="_blank" is for the one route that exists to leave.