Button
ImplementedPrimary interactive button — variants: primary (teal CTA), secondary (outlined), ghost (text-only), danger (destructive). Sizes: sm/md/lg all enforce WCAG 2.5.5 44px tap-min (var(--ep-tap-min)); size changes padding and font-size, not height.
patterns/atoms/button.htmlNative action control with four visual variants (primary, secondary, ghost, danger), three size scales, and a loading state. D-HOME-P3e 2026-08-11: primary uses white text on teal for stronger subjective contrast. FR-CA: Bouton d'action — principal, secondaire, discret, destructif.
Button
Interactive atom (Family 1 — Tier 1)
A native <button> with consistent variants, sizes, and loading state. Inherits all standard React.ButtonHTMLAttributes (onClick, disabled, type, aria-*, data-*, etc.).
This component supports light and dark mode.
Usage
import { Button } from '@epasslive/design-system'
<Button variant="primary" onClick={save}>Enregistrer</Button>
<Button variant="secondary" onClick={cancel}>Annuler</Button>
<Button variant="ghost" onClick={goBack}>Retour</Button>
<Button variant="danger" onClick={handleDelete}>Supprimer</Button>
// Sizes
<Button size="sm">Compact</Button>
<Button size="md">Default</Button>
<Button size="lg">Large</Button>
// States
<Button loading>En cours...</Button>
<Button disabled>Indisponible</Button>
<Button fullWidth>Passer à l'étape suivante</Button>
// Form integration
<Button type="submit" variant="primary">Soumettre</Button>When to use
- primary — submit, save, CTA. One per view max.
- secondary — cancel, back, alternate flow.
- ghost — tertiary actions, link-styled helpers.
- danger — delete, refund, revoke. Always pair with a confirm dialog.
Variants
primary—bg-ep-btn-primary text-ep-text-inverse hover:bg-ep-btn-primary-hover. White text on teal (D-HOME-P3e 2026-08-11).secondary— outlined teal border + teal text + teal/10 hover bg.ghost— teal text only + teal/10 hover bg. No border.danger—bg-ep-semantic-error-bg text-ep-text-inverse. Red surface, white text.
Sizes
All sizes are pinned to min-h-[var(--ep-tap-min)] (44px) for WCAG 2.5.5 (44×44px minimum touch target). The visual difference is padding and font-size only:
sm—px-3 text-ep-sm(12px horizontal padding, 14px text)md—px-4 text-ep-sm(16px horizontal padding, 14px text) — defaultlg—px-5 text-ep-base(20px horizontal padding, 16px text)
States
- default — interactive, full opacity.
- hover — primary darkens via
bg-ep-btn-primary-hover; secondary/ghost gainbg-ep-text-teal/10overlay; danger reduces opacity 10%. - focus — 2px teal focus ring + 2px ring offset (
focus-visible:ring-2 focus-visible:ring-ep-teal focus-visible:ring-offset-2). - disabled —
disabled:opacity-70 disabled:cursor-not-allowed. Interaction is blocked via the nativedisabledattribute. - loading — sets
aria-busy="true"+ disables interaction. Visual spinner placeholder (DS-INT backlog).
Accessibility
The root is a native <button> with a visible focus ring (focus-visible:ring-2 focus-visible:ring-ep-teal focus-visible:ring-offset-2). When loading is true the component sets aria-busy="true" and disables interaction. Contrast meets WCAG 2.2 AA in both light and dark modes (D-HOME-P3e 2026-08-11: primary now uses white text on teal for stronger subjective contrast).
Dark mode
Primary uses bg-ep-btn-primary (teal, theme-stable) with text-ep-text-inverse (white in both themes since D-HOME-P3e). Danger uses bg-ep-semantic-error-bg with text-ep-text-inverse. Both tuned for WCAG 2.2 AA.
Inherited props
As a native <button> wrapping React.ButtonHTMLAttributes, the component accepts all standard HTML button attributes: onClick, type (button/submit/reset),disabled, name, value, form, formAction, autoFocus,aria-*, data-*, etc. children is the only prop excluded from the inheritance (typed explicitly to React.ReactNode).
Props
variant'primary' | 'secondary' | 'ghost' | 'danger'primaryVisual intent of the action.
size'sm' | 'md' | 'lg'mdPadding scale (px-3/4/5) and font-size (text-ep-sm/base). All sizes are pinned to min-h-[var(--ep-tap-min)] (44px) for WCAG 2.5.5 — the visual difference is density, not height.
children*React.ReactNodeVisible label content (text or node). The component is a native <button> so the label is announced by screen readers.
loadingbooleanfalseWhen true: sets aria-busy="true" on the button, disables interaction, and the rendered content shows a spinner placeholder. (Note: spinner rendering is currently a label-only stub — visual spinner is tracked in DS-INT backlog.)
fullWidthbooleanfalseStretches the button to fill its container width (w-full).
Props
Button
variant'primary' | 'secondary' | 'ghost' | 'danger''primary'size'sm' | 'md' | 'lg''md'childrenReact.ReactNode—onClickReact.MouseEventHandler<HTMLButtonElement>—disabledbooleanFalseloadingbooleanFalsetype'button' | 'submit' | 'reset''button'fullWidthbooleanFalsetrailingReact.ReactNodeundefinedContent after a decorative divider at the end of the button, e.g. the cart total on Buy now (`Buy now | $39.99`). Part of the accessible name.
<Button />Accessibility
Type
1 ruleARIA
1 ruleUsage
Import
import { Button } from "@epasslive/design-system"Example 1
<Button variant="primary">Submit</Button>Example 2
<Button variant="secondary" size="sm">Cancel</Button>Anti-patterns
— Do not compose atoms directly — use molecules instead
— Avoid inline styles on atoms