Skip to content

Button Migration — Canary Component Adoption

Step-by-step pattern for migrating buttons in arda-frontend-app from local shadcn Button to the canary Button from @arda-cards/design-system. Follow this checklist for each file to ensure no functionality is lost.

Before starting migration on any file:

  1. Ensure @arda-cards/design-system/styles/tokens.css is imported in the app’s globals.css (after Tailwind imports) for hover/active state tokens.
  2. Ensure the @theme inline block in globals.css maps all required tokens: --color-primary-hover, --color-primary-active, --color-secondary-hover, --color-secondary-active, --color-destructive-hover, --color-destructive-active, --color-accent-hover, --color-accent-active.
  3. Verify the local design system link is active (npm link @arda-cards/design-system) or the preview package is installed.
// Before
import { Button } from '@/components/ui/button';
// After
import { Button } from '@arda-cards/design-system/canary';

The local shadcn Button uses variant="default" for primary styling. The canary Button uses variant="primary".

Local shadcnCanaryNotes
default (or no variant)primaryOrange background, white text
secondarysecondarySame name
ghostghostSame name
destructivedestructiveSame name
outlineoutlineSame name
linkNo equivalentKeep local Button for link-styled text, or use asChild with an anchor

The local shadcn Button has no loading prop. Most files implement loading with a conditional that switches between a Loader2 spinner and the label text.

// Before — manual loading
<Button disabled={isLoading}>
{isLoading ? (
<div className='flex items-center justify-center gap-2'>
<Loader2 className='w-4 h-4 animate-spin' />
Saving...
</div>
) : (
'Save'
)}
</Button>
// After — built-in loading
<Button loading={isLoading}>
Save
</Button>

When the loading text differs from the button label:

// Before
{isLoading ? 'Setting password...' : 'Set New Password'}
// After — string loading replaces the label
<Button loading={isLoading ? 'Setting password...' : false}>
Set New Password
</Button>

After replacing, check if Loader2 is still used elsewhere in the file. Remove the import if it is now unused.

Replace style={{ backgroundColor: '...', color: '...' }} with the appropriate variant prop. The canary Button uses design tokens for all colors.

// Before
<Button
style={{
backgroundColor: 'var(--base-primary, #FC5A29)',
color: 'var(--base-primary-foreground, #FAFAFA)',
}}
>
// After
<Button variant="primary">

Remove classes that the canary Button already handles:

RemoveWhy
text-smButton md size uses text-sm by default
h-9, h-10, px-4 py-2, etc.Use size prop instead (sm, md, lg)
rounded-md, rounded-lgButton has built-in border-radius
gap-2Button has built-in gap
disabled:opacity-50Button handles disabled styling

Keep layout classes like w-full, flex-1, mt-4 that control placement within the parent.

If the button has a tooltip wrapper, replace it with the tooltip prop:

// Before — 7 lines of wrapping
<Tooltip>
<TooltipTrigger asChild>
<Button disabled={!orderable}>Place Order</Button>
</TooltipTrigger>
<TooltipContent>Configure a supplier URL first</TooltipContent>
</Tooltip>
// After — 1 prop
<Button disabled={!orderable} tooltip="Configure a supplier URL first">
Place Order
</Button>

The canary Button handles disabled-button pointer events automatically (no extra <span> wrapper needed).

Before moving to the next file, check:

  • Button renders with the correct color variant
  • Hover state is visible (slightly lighter for primary)
  • Loading state shows spinner and disables the button
  • Loading text replacement works (if applicable)
  • Tooltip appears on hover (if applicable)
  • Tooltip appears on disabled buttons (if applicable)
  • onClick, type, disabled, aria-label are preserved
  • Layout classes (w-full, flex-1) still work
  • Update all Unit Tests for the affected page/component
  • Update all e2e Tests for the affected page/component
Local shadcn propCanary equivalentNotes
variantvariantMap "default" to "primary"
sizesizeSame: sm, md (default), lg, icon, icon-sm
disableddisabledSame
asChildasChildSame — Radix Slot pattern
classNameclassNameSame — keep layout classes only
N/Aloadingboolean | string — replaces manual Loader2 pattern
N/AloadingPosition'start' | 'end' — spinner placement
N/Atooltipstring — built-in Radix Tooltip
N/AtooltipSide'top' | 'bottom' | 'left' | 'right'

For buttons with a dropdown chevron (e.g., “Add to Order” + alternate actions), use SplitButton:

import { SplitButton } from '@arda-cards/design-system/canary';
import { DropdownMenuItem } from '@arda-cards/design-system/canary';
<SplitButton
variant="primary"
menuContent={
<>
<DropdownMenuItem>Add to order queue</DropdownMenuItem>
<DropdownMenuItem>Print kanban card</DropdownMenuItem>
</>
}
showDivider
onClick={handlePrimaryAction}
>
Add to Order
</SplitButton>

For grouped action buttons (e.g., Cancel / Save), use ButtonGroup:

import { ButtonGroup } from '@arda-cards/design-system/canary';
<ButtonGroup>
<Button variant="outline">Cancel</Button>
<Button variant="primary">Save</Button>
</ButtonGroup>

For on/off state buttons (e.g., Bold/Italic formatting, filter pills), use Toggle or ToggleGroup:

import { ToggleGroup, ToggleGroupItem } from '@arda-cards/design-system/canary';
<ToggleGroup type="multiple" variant="outline" size="sm">
<ToggleGroupItem value="active">Active</ToggleGroupItem>
<ToggleGroupItem value="draft">Draft</ToggleGroupItem>
</ToggleGroup>

When adding keyboard shortcuts to buttons (shown via tooltip):

  • Use event.code (physical key) instead of event.key (character). On Mac, Ctrl+Alt+N produces a dead key (event.key === 'Dead'), but event.code === 'KeyN' works reliably across platforms.
  • Avoid browser/OS conflicts. Cmd/Ctrl+N (new window), Cmd/Ctrl+T (new tab), Cmd/Ctrl+W (close tab), F1-F12 are all intercepted by the browser before they reach the page.
  • Prefer Ctrl+Alt+<key> for app-level shortcuts.
  • Show the shortcut in the button’s tooltip with a delay:
    <Button tooltip="Add item (Ctrl+Alt+N)" tooltipDelay={300}>
    Add item
    </Button>

Copyright: (c) Arda Systems 2025-2026, All rights reserved