Skip to content
SuperCoach

Components · Actions

Button

Beta

Starts an action, such as saving a program or sending a check-in.

When to use

  • To start an action on the current page: save a program, send a check-in, add a client.
  • To submit a form.
  • To open a dialog or sheet for a task.

When not to use

  • To go to another page: use a link. A button that navigates looks like it acts.
  • For a setting that turns on or off right away: use a Switch.
  • For one of several mutually exclusive options: use a radio group or toggle group.

Anatomy

  1. 1. Container: fill, border and radius from the variant
  2. 2. Icon (optional): 16px, before the label, aria-hidden
  3. 3. Label: a verb that says what happens

Variants

VariantUse for
defaultThe one primary action in a view: Save program, Send check-in.
secondaryA supporting action next to the primary one. In SuperCoach it is Electric Green.
outlineSecondary actions in toolbars and card footers, where several sit side by side.
ghostLow-emphasis actions inside dense UI: table rows, toolbars.
destructiveActions that delete or cannot be undone. Usually inside a confirmation.
linkAn action that must look like text, inside a sentence.

States

Default: hover, tab to and press it

Disabled

Loading

StateWhat changesToken
HoverFill darkens slightlybg-primary/90 (variant color at 90%)
Focus-visibleA ring outside the button, keyboard onlyring-ring/50, 3px
ActiveSame as hover; no extra movement—
DisabledHalf opacity, no pointer eventsopacity-(--opacity-disabled)
LoadingDisabled, spinner replaces the icon, aria-busy, label says what is happeninganimate-spin

Sizes

default (36px) for most UI. sm (32px) in dense tables and toolbars. lg (40px) for the main action on mobile and onboarding. xs (24px) only inside compact components such as a set logger. Icon-only buttons have matching icon sizes and always need an aria-label.

Usage guidelines

Do.One primary action per view, with secondary actions in outline.
Don't.Three primary buttons. Nothing stands out, so nothing is the next step.
Do.Destructive styling for the action that removes data, in the confirmation dialog.
Don't.Red for a harmless action. It makes cancelling look dangerous.

Content guidelines

  • Start with a verb and name the object: "Save program", "Send invoice". Two or three words.
  • Sentence case. No punctuation, except "…" when more input follows ("Assign to…").
  • The same action has the same label everywhere: "Remove client" in the menu and in the dialog.
  • Arabic labels use the verbal noun or imperative consistently: "حفظ البرنامج", "إرسال الفاتورة".

Accessibility

KeyDoes
TabMoves focus to the button
Enter or SpaceActivates it
  • Renders a native <button>. Use asChild with a <Link> for navigation, so it is announced as a link.
  • Icon-only buttons need aria-label; icons beside text are aria-hidden.
  • Loading: keep the button in place, set aria-busy="true" and change the label ("Saving…"). A screen reader announces "Saving…, button, dimmed".
  • Coach primary buttons fail contrast today (DECISIONS #3).

RTL

  • The icon sits before the label, so it moves to the right in RTL. No extra CSS.
  • Directional icons (arrows, chevrons) mirror with rtl:-scale-x-100. A "+" or trash icon does not.
  • Button groups reverse order: the primary action stays at the end of the row.

Tokens used

Read from components/ui/button.tsx at build time, so this list changes when the component does. Opacity modifiers such as /90 are shadcn hover and focus tints on top of the token.

Semantic color tokens used by this component
TokenUtilitiesSuperCoachCoach
--accentbg-accentbrand-50orange-50
--accent-foregroundtext-accent-foregroundbrand-700orange-700
--backgroundbg-backgroundwhitewhite
--destructiveborder-destructive ring-destructive/20 bg-destructive bg-destructive/90red-600red-600
--destructive-foregroundtext-destructive-foregroundwhitewhite
--primarybg-primary bg-primary/90 text-primarybrand-600orange-500
--primary-foregroundtext-primary-foregroundwhitewhite
--ringborder-ring ring-ring/50brand-600orange-500
--secondarybg-secondary bg-secondary/80lime-300neutral-100
--secondary-foregroundtext-secondary-foregroundneutral-950neutral-800
  • Opacityopacity-(--opacity-disabled)
  • Radiusrounded-md
  • Shadowshadow-xs
  • Typefont-medium
  • Typetext-sm
  • Typetext-xs

Props

<Button>

Also takes every prop of <button>

Button props
PropTypeDefault
variant"default" | "destructive" | "outline" | "secondary" | "ghost" | "link""default"
size"default" | "xs" | "sm" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg""default"
asChild?booleanfalse

Replaces in coach-admin

From the read-only audit of coach-admin: 134 style values, 179 uses.

The audit also found the typo props color="secodnary" and color="secondry", which silently render the default style.

Classes and selectors

  • .btn-primary42
  • .btn33
  • .btn-secondary10
  • .btn:hover2
  • .btn-danger2
  • .btn-green2
  • .btn-green:hover2
  • .btn-primary, .btn-primary:hover2
  • .btn-add::after1
  • .btn-add1
  • .btn.btn-danger.array-item-remove, .btn.btn-default.array-it1
  • .btn-lg1

Files

  • src/reusableComponents/formComponents/button/button.scss79

The inventory lists the first 12 locations of each value, so file counts are a lower bound.

SCSS variables in these styles

  • $invoice-ink-secondary→ --foreground-secondary
  • $border-color→ --border
  • $invoice-surface-bg→ nearest neutral-50 (no semantic token)
  • $body-colornot a color: spacing, size or layout
  • $text-mutednot a color: spacing, size or layout
  • $dropdown-header-colornot a color: spacing, size or layout
  • $input-btn-padding-ynot a color: spacing, size or layout
  • $input-btn-padding-y-lgnot a color: spacing, size or layout
  • $input-btn-padding-y-smnot a color: spacing, size or layout
  • $input-btn-padding-xnot a color: spacing, size or layout
  • $invoice-section-line→ nearest neutral-200 (no semantic token)

Switch, Dialog, Content, Accessibility.