Skip to content

Button

The primary action control. Renders a <button>, or an <a> when given href. Orange darkens on hover/press β€” no scale, no bounce.

As link
import Button from '@repo/ui/Button';
<Button variant="primary" onClick={submit}>Submit order</Button>
<Button variant="primary" loading={isSubmitting}>Submitting…</Button>
<Button variant="primary" patch>Get started</Button> // patch corner β€” brand moment
<Button href="/inbox" variant="outline">Open inbox</Button>
Prop Type Default Notes
variant primary | secondary | outline | ghost | destructive primary primary is the only orange one β€” keep it to ~one per view
size sm | md | lg md 32 / 40 / 48px tall
patch boolean false Asymmetric patch corner (8px 8px 8px 0). Reserve for brand moments
loading boolean false Shows a leading <Spinner />, sets aria-busy, and auto-disables <button>
href string β€” Renders an <a> instead of <button>
className / class string β€” Merged via tailwind-merge

Plus all native <button> / <a> attributes (onClick, disabled, type, aria-*, …).

All states are token-driven: default (--om-orange-cta) β†’ hover (--om-orange-cta-hover) β†’ active (--om-orange-cta-press) β†’ :focus-visible (--om-orange-press ring) β†’ disabled / loading (50% opacity, no pointer).

  • Do use one primary per view β€” orange is ~2% of the layout.
  • Do use patch sparingly for hero CTAs.
  • Don’t add scale/bounce on hover, or use orange for secondary actions.
  • Don’t put body-length text in a button; it’s an action, sentence case, no β€œ!”.
  • Keyboard-operable as a native button/link; visible :focus-visible ring.
  • Icon-only buttons must pass an aria-label.
  • Verified with jest-axe (zero violations). Contrast note: primary uses --om-orange-cta with --om-fg-on-orange for 4.91:1 WCAG AA contrast, and outline/ghost focus rings use --om-orange-press for β‰₯3:1 on light surfaces. See Color foundations.