JM Family Design System

Button

Buttons trigger actions. Use them when the user is submitting, confirming, opening, or running something in the current workflow.

Guidance for adoption

Start here before choosing examples or props. These notes explain where Button helps, where another pattern is better, and what should stay true in product work.

When to use

  • Submitting forms
  • Confirming decisions
  • Opening dialogs
  • Running page-level actions

When not to use

  • Navigation between pages
  • Static labels
  • Actions that are unavailable or undefined

Accessibility expectations

  • Use a native button element for actions.
  • Provide visible text or an accessible label.
  • Keep target size at least 44 by 44 px.
  • Preserve focus-visible styling and keyboard activation.

Content guidance

  • Start labels with a verb plus object, such as "Save changes", "Approve request", or "Delete record".
  • Match the label to the user outcome, not the implementation event behind the click.
  • For destructive actions, name the specific object or record affected so the risk is clear.
  • Avoid vague labels such as "OK", "Yes", "No", or "Submit" unless surrounding context makes the outcome unmistakable.

Implementation notes

  • The component renders a native button and defaults type to button. Set type="submit" explicitly inside forms.
  • Use one primary button per decision area so the next best action stays clear.
  • Pair disabled buttons with nearby helper text or aria-describedby so users know what is missing.
  • Loading is intentionally not built in. Compose disabled, aria-busy, and visible progress text when a request is pending.

Import

import { Button } from '@/components/ui/Button';

Drop into product code; props mirror the native HTML button.

Variants

Choose the variant by action priority and risk. Visual emphasis should follow the workflow decision, not personal preference.

Primary

The main action in a decision area. Use once per local context.

Secondary

An alternate action that should remain visible but lower emphasis.

Tertiary

A low-emphasis action used inside dense interfaces and secondary workflows.

Destructive

A high-risk action that deletes, removes, or cannot be easily undone.

Anatomy

A button can be visually simple, but it still needs a stable structure so labels, icons, focus, disabled states, loading states, and motion do not shift the layout.

  1. Text label
  2. Optional leading or trailing icon
  3. Minimum target area
  4. Focus ring

Interactive states

Every variant must respond the same way to user input. This matrix shows the visual feedback for each state on the primary variant; the contract applies to all variants.

Default

At rest, awaiting input.

Hover

Pointer is over the control.

Focus

Reachable by keyboard.

Active

Mid-press, before release.

Disabled

Not currently available.

Props

The component accepts every native HTMLButtonElement attribute on top of the design system props below.

PropTypeDefaultDescription
variant'primary' | 'secondary' | 'tertiary' | 'destructive''primary'Visual emphasis. Pick by action priority and risk, not preference.
leadingIconReactNodeIcon rendered before the label. Mark as decorative with aria-hidden.
trailingIconReactNodeIcon rendered after the label, e.g., a forward arrow for primary actions.
disabledbooleanfalseNative HTML attribute. Suppresses hover and active styling; pair with helper text that explains why.
type'button' | 'submit' | 'reset''button'Native HTML button type. Set submit explicitly inside forms.

Usage rules

Use one primary action per decision area

A page, form footer, dialog, or toolbar can have many actions, but only one should look like the next best step.

Do not use buttons for navigation

If the user is moving to another route, section, file, or external destination, use a link with clear destination text.

Keep labels specific

Write action-first labels like "Approve request", "Create vendor", or "Download report". Avoid vague labels like "OK".

Do not make disabled actions the only path forward

If an action is disabled, the interface should explain what is missing or provide validation near the relevant fields.

Known limitations

What Button does not do yet, and what consumers need to compose themselves.

  • No built-in loading state. Submit-then-spinner flows must be composed in product code; the component does not expose a loading prop.
  • The disabled state does not enforce an accompanying explanation. Pair disabled buttons with helper text or aria-describedby so users know why an action is unavailable.

Related anti-patterns

Catalog entries to watch for when using Button. Read these before shipping; they describe the failure modes the system has already documented.

Agent guidance

Agents should use this as the local contract when generating product UI, examples, documentation, or review comments.

Generation rules

  • Use one primary button per decision area.
  • Use secondary buttons for alternate actions and tertiary buttons for low-emphasis actions.
  • Use destructive only for irreversible or high-risk actions.
  • Do not use buttons for navigation; use Link instead.
  • Write action verb plus object labels such as "Save changes" or "Send invite". Avoid "OK", "Submit", or "Yes" alone; vague labels trigger the cute-error-messages catch on copy review.
  • Pair disabled buttons with helper text or aria-describedby explaining what is missing. Leaving the disabled state silent matches the silently-disabled-buttons anti-pattern.
  • For submit-then-spinner flows, compose disabled plus an inline spinner manually. There is no loading prop on Button by design; set aria-busy="true" on the button while the request is in flight.
  • For destructive confirmations, render the destructive variant inside a Dialog and label it with the specific action ("Delete project") rather than a generic "Confirm".

Baseline example

import { Button } from '@/components/ui/Button';
import { ArrowRight } from 'lucide-react';

<Button
  variant="primary"
  type="submit"
  trailingIcon={<ArrowRight size={16} strokeWidth={1.8} aria-hidden="true" />}
>
  Save changes
</Button>
If a flow needs a button behavior not covered here, agents should flag the gap and propose a design system addition instead of inventing a new variant.

Motion standard

Buttons should feel responsive without becoming distracting. Hover may lift the button, deepen its shadow, or nudge a trailing icon. Active state should press the button back toward the surface. Disable motion for users who request reduced motion.