Gold Standard

Button

The foundational interactive element. Every variant, size, and state is defined here and serves as the template for all future component entries.

Preview

Sizes

Three sizes cover the full range of UI contexts: compact lists, standard forms, and prominent hero actions.

With Icons

Icons amplify intent. Use a left icon to set context, a right icon to suggest directionality.

States

All interactive states are handled. Loading replaces content with a spinner; disabled dims and removes pointer events.

Implementation

Full implementations for every platform. The React version uses a forwardRef pattern for ref forwarding. The SwiftUI version uses a custom press-state modifier for spring feedback.

Button.tsx
tsx
import { Button } from "@/components/ui/Button";
import { ArrowRight, Download } from "lucide-react";

// Primary — the default call-to-action
<Button variant="primary">Get Started</Button>

// Secondary — for supporting actions
<Button variant="secondary">Learn More</Button>

// Ghost — for low-emphasis, inline actions
<Button variant="ghost">Cancel</Button>

// Danger — destructive actions
<Button variant="danger">Delete Account</Button>

// Glass — for overlays and hero sections
<Button variant="glass">Explore</Button>

// With icons
<Button leftIcon={<Download className="w-4 h-4" />}>
  Download
</Button>
<Button rightIcon={<ArrowRight className="w-4 h-4" />}>
  Continue
</Button>

// Sizes
<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
<Button size="lg">Large</Button>

// Loading state
<Button loading>Saving...</Button>

// Disabled
<Button disabled>Unavailable</Button>

Props

PropTypeDefaultDescription
variant"primary" | "secondary" | "ghost" | "danger" | "glass""primary"Controls the visual style of the button.
size"sm" | "md" | "lg" | "icon""md"Sets the height, padding, and font size.
loadingbooleanfalseShows a spinner and disables interaction.
leftIconReactNodeIcon rendered to the left of the label.
rightIconReactNodeIcon rendered to the right of the label.
disabledbooleanfalsePrevents all interaction and dims the button.

Motion

Buttons use spring physics for press feedback — not a linear scale. This preserves energy and feels alive.

InteractionPropertyValueEasing
Pressscale0.97spring(500, 40)
Releasescale1.0spring(500, 40)
Hoveropacity / shadow0.9 / reduced150ms ease
Focusring2px accentinstant

Mobile

Buttons on touch screens need larger hit areas and thoughtful sizing. Use the viewport toggle above any preview to see how buttons reflow at 390px.

9:41

Checkout

Review your order

Pro plan

$12/mo

Extra storage

$2/mo

Total$14/mo
ScenarioGuidance
Touch targetMinimum 44×44 pt. The md size (h-10 / 40px) is borderline — prefer lg for primary CTAs on mobile.
Primary CTAUse w-full on the primary action at the bottom of a form or screen. Full-width buttons are easier to tap and establish visual hierarchy.
Icon-only buttonsUse size="icon" (44px) not a smaller size. Add a tooltip or visible label nearby since hover tooltips don't work on touch.
Haptic feedbackOn iOS/Android, trigger UIImpactFeedbackGenerator or Vibration.vibrate() on press for tactile confirmation on destructive or primary actions.
Button groupsAvoid more than 2 side-by-side buttons on narrow screens. Stack vertically or use a split-button to consolidate.

ARIA roles

ElementRoleKey attributes
Rootbutton (implicit)aria-busy, aria-disabled, aria-label

Keyboard

KeyAction
Enter / SpaceActivate the button
TabMove focus to the next focusable element
Shift+TabMove focus to the previous focusable element

Accessibility

  • Uses a native <button> element — keyboard navigable and screen-reader accessible by default.
  • Focus ring is visible only with keyboard navigation (focus-visible).
  • Loading state disables the button and announces via aria-busy="true".
  • Danger variant maintains WCAG AA contrast (4.8:1 on white).
  • Icon-only buttons should include aria-label for assistive technology.