Components · Actions

Button

The commitment control. Everything a clinician does that changes the record, the workflow or the patient's pathway goes through one of these — so its hierarchy, its size and its wording are all safety decisions.

Stable · v1.0 shadcn/ui IEC 62366-1 WCAG 2.2 AA

Overview

Buttons in consumer software optimise for conversion. In clinical software they optimise for the opposite: making sure the clinician who pressed it meant to. The system therefore constrains two things harder than a general-purpose library would — how many primary actions may exist (one) and how small a target may be (never below the touch floor).

Ranked

Exactly one primary action per view. If two things look equally important, the clinician has to decide which is — and that decision belongs to the designer, not the user.

Reachable

Sized for a gloved fingertip on a cart display, not a mouse on a desk. The floor is a physical measurement, not a pixel count.

Truthful

The label names the outcome, not the interaction. "Activate pathway", never "Submit" — a clinician should be able to predict the consequence without reading the surrounding screen.

Variants

Five variants, mapped onto shadcn's standard names so ecosystem examples drop in unchanged.

All variants · size: default
View guideline
VariantUsePer viewAcuteLine example
default (primary) The one action the screen exists to enable Exactly oneActivate chest-pain pathway
outline (secondary) Supporting actions of equal legitimacy0–3Open ECG · Compare prior
ghost (tertiary) Low-commitment or dismissive actionsAnyDefer · Close
destructive Irreversible loss of data or workRareDiscard draft report
link Navigation that leaves the current taskAnyView local guideline
Governed — no coral

The brand coral #D8593A is not permitted on any button in a product interface. At a glance it is not reliably distinguishable from the IEC high-priority alarm red, so a coral primary button competes with a real alarm for exactly the attention the alarm needs. --primary is teal. See Colour.

destructive is not an alarm

destructive is red because it destroys data. It shares a hue with --alarm-critical and shares nothing else. A destructive button is a control the clinician operates; an alarm is a signal about the patient. Never style an alarm as a button, and never place a destructive button inside an alarm banner.

Sizes and touch targets

The pixel height of a button is meaningless on its own — what matters is the physical size it ends up on the target display. The system specifies both.

size: sm · default · lg
sm · 32 px
default · 36 px
lg · 44 px
SizeHeightUseTouch permitted?
sm32 px Inside dense components — worklist rows, alert banners, toolbars Mouse and keyboard only
default36 px Standard desktop workstation UIMouse and keyboard only
lg44 px Default for any touch surface — carts, bedside, gantry displays Yes
Governed — 10 mm touch floor

On any touch-operated display, the rendered touch target must measure at least 10 mm × 10 mm physically. 44 px satisfies this at typical workstation pixel densities, but on a high-PPI cart display it does not — which is why touch platforms apply an interface scale factor rather than hand-picking larger buttons. See Scaling & displays.

The visible button may be smaller than its hit area. A 32 px control may carry a 44 px hit area via padding — but never the reverse, because a target that looks bigger than it is produces mis-taps the clinician cannot explain.

States

Interaction states · primary
default
hover
focus-visible
disabled
pending

Writing the label

Do

Discard this draft report?

Your interpretation and annotations will be permanently lost.

Both labels name their outcome. Neither can be misread in a hurry.

Don't

Are you sure?

This action cannot be undone.

"Yes" to what? The destructive action is styled as primary, and neither label survives being read out of context.

Grouping and order

Action row · one primary

Do's and don'ts

Do

One primary. The eye lands on the action the screen exists for.

Don't

Three primaries. The clinician now ranks them under time pressure, which is the designer's job.

Do

Pending state blocks re-submission. One press produces one cath lab activation.

Don't

No pending state. On a display with 300 ms latency the clinician presses twice and the lab is paged twice.

Accessibility

Outcomes of use

What this contributes to, in the terms of Usability & context of use. These are attributes believed to contribute to an outcome; the outcome itself is settled by observing real use in a specified context, not by this page.

Clinical safety notes

Risk controls carried by this component

Trace these in your risk file (ISO 14971).

Implementation

The stock shadcn Button with two changes: an enlarged lg size that is the default on touch platforms, and a development-time warning when more than one primary renders in a tree.

const buttonVariants = cva(
  "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-sm " +
  "text-sm font-semibold transition-colors focus-visible:outline-none " +
  "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 " +
  "disabled:pointer-events-none disabled:opacity-50",
  {
    variants: {
      variant: {
        // NB: no coral. --primary is teal by design — see Colour.
        default:     "bg-primary text-primary-foreground hover:brightness-110",
        outline:     "border border-border bg-transparent hover:bg-muted",
        ghost:       "hover:bg-muted",
        destructive: "bg-destructive text-destructive-foreground hover:brightness-110",
        link:        "underline-offset-4 hover:underline text-primary",
      },
      size: {
        sm:      "h-8 px-3",    // pointer input only
        default: "h-9 px-4",    // pointer input only
        lg:      "h-11 px-5",   // touch floor — default on touch platforms
        icon:    "h-11 w-11",   // always touch-sized; requires aria-label
      },
    },
    defaultVariants: { variant: "default", size: "lg" },
  }
)
// Pending state — blocks re-submission of irreversible actions.
<Button
  variant="default"
  size="lg"
  disabled={isPending}
  aria-busy={isPending}
  onClick={notifyCathLab}
>
  {isPending ? "Notifying…" : "Notify cath lab"}
</Button>

Props

PropTypeDefaultNotes
variant default | outline | ghost | destructive | link defaultOne default per view.
sizesm | default | lg | icon lgDefaults to the touch-safe size deliberately.
asChildbooleanfalse Radix Slot — render as <a> for navigation.
disabledbooleanfalse Pair with adjacent text explaining what is missing.
aria-busyboolean Required for any action that reaches a server.
NotJustAnyMed.Tech Design System · Button · v1.0 · draft for review
Reference applications named in this system are fictional; all patient data shown is fabricated.