Components · Actions
Button
BetaStarts an action, such as saving a program or sending a check-in.
- Source
components/ui/button.tsx - shadcn/ui Button
- Figma: not linked yet (Q13)
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. Container: fill, border and radius from the variant
- 2. Icon (optional): 16px, before the label, aria-hidden
- 3. Label: a verb that says what happens
Variants
| Variant | Use for |
|---|---|
default | The one primary action in a view: Save program, Send check-in. |
secondary | A supporting action next to the primary one. In SuperCoach it is Electric Green. |
outline | Secondary actions in toolbars and card footers, where several sit side by side. |
ghost | Low-emphasis actions inside dense UI: table rows, toolbars. |
destructive | Actions that delete or cannot be undone. Usually inside a confirmation. |
link | An action that must look like text, inside a sentence. |
States
Default: hover, tab to and press it
Disabled
Loading
| State | What changes | Token |
|---|---|---|
| Hover | Fill darkens slightly | bg-primary/90 (variant color at 90%) |
| Focus-visible | A ring outside the button, keyboard only | ring-ring/50, 3px |
| Active | Same as hover; no extra movement | — |
| Disabled | Half opacity, no pointer events | opacity-(--opacity-disabled) |
| Loading | Disabled, spinner replaces the icon, aria-busy, label says what is happening | animate-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
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
| Key | Does |
|---|---|
| Tab | Moves focus to the button |
| Enter or Space | Activates it |
- Renders a native
<button>. UseasChildwith a<Link>for navigation, so it is announced as a link. - Icon-only buttons need
aria-label; icons beside text arearia-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.
| Token | Utilities | SuperCoach | Coach |
|---|---|---|---|
| --accent | bg-accent | brand-50 | orange-50 |
| --accent-foreground | text-accent-foreground | brand-700 | orange-700 |
| --background | bg-background | white | white |
| --destructive | border-destructive ring-destructive/20 bg-destructive bg-destructive/90 | red-600 | red-600 |
| --destructive-foreground | text-destructive-foreground | white | white |
| --primary | bg-primary bg-primary/90 text-primary | brand-600 | orange-500 |
| --primary-foreground | text-primary-foreground | white | white |
| --ring | border-ring ring-ring/50 | brand-600 | orange-500 |
| --secondary | bg-secondary bg-secondary/80 | lime-300 | neutral-100 |
| --secondary-foreground | text-secondary-foreground | neutral-950 | neutral-800 |
- Opacity
opacity-(--opacity-disabled) - Radius
rounded-md - Shadow
shadow-xs - Type
font-medium - Type
text-sm - Type
text-xs
Props
<Button>
Also takes every prop of <button>
| Prop | Type | Default |
|---|---|---|
| variant | "default" | "destructive" | "outline" | "secondary" | "ghost" | "link" | "default" |
| size | "default" | "xs" | "sm" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg" | "default" |
| asChild? | boolean | false |
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)