Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.encoreos.io/llms.txt

Use this file to discover all available pages before exploring further.

Version: 1.0.0
Last Updated: 2026-03-13
Scope: All Button usage from @/shared/ui/button

Import

Use the shared Button component only:
import { Button } from '@/shared/ui/button';
Do not use legacy paths (e.g. @/components/ui/button).

Variants and Sizes

Authority: Variant and size API and usage guidance are defined in src/shared/ui/button.tsx. Summary:
VariantUse for
defaultPrimary CTAs, form submit buttons
destructiveDelete, reject, and other dangerous actions
outlineSecondary actions, toolbar buttons
secondaryTertiary or muted actions
ghostIcon buttons, toolbars, minimal chrome
linkInline navigation, text-style links
successApprove, complete, positive confirmation
warningCaution actions, escalation, review needed
softSubtle emphasis, dashboard quick actions (same semantic as Badge info)
SizeUse for
defaultStandard buttons (h-10)
smCompact buttons in toolbars (h-9)
xsVery compact: table row actions, inline (h-7)
lgHero CTAs, prominent actions (h-11)
iconIcon-only buttons; 44px min touch target (use with aria-label)
Semantic consistency: Use destructive for delete/reject actions, success for approve/complete actions, and warning for caution. Prefer the loading prop for async submit actions (see Loading State).

Icon-Only Buttons (Accessibility)

Icon-only buttons must have an aria-label so screen readers can announce the action.
// ✅ CORRECT
<Button variant="ghost" size="icon" aria-label="Edit item" onClick={onEdit}>
  <Pencil className="h-4 w-4" aria-hidden="true" />
</Button>
<Button variant="ghost" size="icon" aria-label="Go back" onClick={onBack}>
  <ArrowLeft className="h-4 w-4" aria-hidden="true" />
</Button>

// ❌ WRONG: No accessible name
<Button variant="ghost" size="icon" onClick={onEdit}>
  <Pencil className="h-4 w-4" />
</Button>
When the icon is decorative (button has visible text), use aria-hidden="true" on the icon.

Loading State

Use the Button loading prop for submit and other async actions so the button shows a spinner and is disabled automatically.
// ✅ CORRECT
<Button type="submit" loading={isPending}>Save</Button>
<Button onClick={handleDelete} variant="destructive" loading={deleteMutation.isPending}>Delete</Button>

// ❌ AVOID: Manual disabled without built-in spinner
<Button type="submit" disabled={isPending}>Save</Button>

When using asChild with Link, the link must provide the accessible name (visible text or aria-label).
// ✅ CORRECT
<Button variant="ghost" size="icon" asChild>
  <Link to="/charges" aria-label="Back to charges">
    <ArrowLeft className="h-4 w-4" aria-hidden="true" />
  </Link>
</Button>

// ✅ CORRECT: Visible text
<Button variant="outline" asChild>
  <Link to="/dashboard">Go to dashboard</Link>
</Button>

In DialogFooter, place Cancel (or secondary) on the left, primary action on the right.
<DialogFooter>
  <Button variant="outline" onClick={() => onOpenChange(false)}>Cancel</Button>
  <Button type="submit" loading={isPending}>Save</Button>
</DialogFooter>

References