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.
Pre-flight
Section titled “Pre-flight”Before starting migration on any file:
- Ensure
@arda-cards/design-system/styles/tokens.cssis imported in the app’sglobals.css(after Tailwind imports) for hover/active state tokens. - Ensure the
@theme inlineblock inglobals.cssmaps 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. - Verify the local design system link is active (
npm link @arda-cards/design-system) or the preview package is installed.
Per-file checklist
Section titled “Per-file checklist”1. Swap the import
Section titled “1. Swap the import”// Beforeimport { Button } from '@/components/ui/button';
// Afterimport { Button } from '@arda-cards/design-system/canary';2. Map variants
Section titled “2. Map variants”The local shadcn Button uses variant="default" for primary styling. The canary
Button uses variant="primary".
| Local shadcn | Canary | Notes |
|---|---|---|
default (or no variant) | primary | Orange background, white text |
secondary | secondary | Same name |
ghost | ghost | Same name |
destructive | destructive | Same name |
outline | outline | Same name |
link | No equivalent | Keep local Button for link-styled text, or use asChild with an anchor |
3. Replace hand-rolled loading
Section titled “3. Replace hand-rolled loading”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.
4. Remove inline styles
Section titled “4. Remove inline styles”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">5. Clean up className
Section titled “5. Clean up className”Remove classes that the canary Button already handles:
| Remove | Why |
|---|---|
text-sm | Button 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-lg | Button has built-in border-radius |
gap-2 | Button has built-in gap |
disabled:opacity-50 | Button handles disabled styling |
Keep layout classes like w-full, flex-1, mt-4 that control placement within
the parent.
6. Capture tooltips
Section titled “6. Capture tooltips”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).
7. Verify no functionality loss
Section titled “7. Verify no functionality loss”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-labelare 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
Props reference
Section titled “Props reference”| Local shadcn prop | Canary equivalent | Notes |
|---|---|---|
variant | variant | Map "default" to "primary" |
size | size | Same: sm, md (default), lg, icon, icon-sm |
disabled | disabled | Same |
asChild | asChild | Same — Radix Slot pattern |
className | className | Same — keep layout classes only |
| N/A | loading | boolean | string — replaces manual Loader2 pattern |
| N/A | loadingPosition | 'start' | 'end' — spinner placement |
| N/A | tooltip | string — built-in Radix Tooltip |
| N/A | tooltipSide | 'top' | 'bottom' | 'left' | 'right' |
Split buttons
Section titled “Split buttons”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>Button groups
Section titled “Button groups”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>Toggle buttons
Section titled “Toggle buttons”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>Keyboard shortcuts
Section titled “Keyboard shortcuts”When adding keyboard shortcuts to buttons (shown via tooltip):
- Use
event.code(physical key) instead ofevent.key(character). On Mac,Ctrl+Alt+Nproduces a dead key (event.key === 'Dead'), butevent.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-F12are 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
tooltipwith a delay:<Button tooltip="Add item (Ctrl+Alt+N)" tooltipDelay={300}>Add item</Button>
Related
Section titled “Related”- Button audit — full inventory of buttons to migrate
- Using the Design System — Storybook docs for importing components and styles
- React Component Design — component creation conventions
Copyright: (c) Arda Systems 2025-2026, All rights reserved
Copyright: © Arda Systems 2025-2026, All rights reserved