Button Unification
Standardize the button component for consistent heights, padding, and transitions across all variants.
Workflow
- •Find Button Component - Locate
website/components/ui/button.tsx - •Audit Current Styles - Review existing buttonVariants
- •Standardize Heights - Unified height per size
- •Standardize Padding - Consistent padding per size
- •Standardize Typography - Same font size and weight and variant
- •Unify Border-Radius - Same radius for all variants
- •Unify Transitions - Same transition for all variants
- •Background - Ensure background styles are consistent for each variant
- •Icon Sizes - Standardize icon button dimensions for each size
- •Text Color - Ensure text colors are consistent per variant
- •Verify Consistency - Test all variant + size combinations
Size Specifications
Heights (Unified)
| Size | Height | Class |
|---|---|---|
| default | 36px | h-9 |
| sm | 32px | h-8 |
| lg | 40px | h-10 |
| icon | 36px | size-9 |
| icon-sm | 32px | size-8 |
| icon-lg | 40px | size-10 |
Padding (Unified)
| Size | Horizontal | With Icon | Gap |
|---|---|---|---|
| default | px-4 | has-[>svg]:px-3 | gap-2 |
| sm | px-3 | has-[>svg]:px-2.5 | gap-1.5 |
| lg | px-6 | has-[>svg]:px-4 | gap-2 |
Base Styles (All Variants)
All button variants share these base styles:
tsx
// Base class (applied to ALL variants) "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm text-white font-medium transition-all"
Key unified properties:
- •
rounded-md- Consistent border radius - •
text-sm font-medium text-white- Consistent typography - •
transition-all- Consistent transitions - •
gap-2(or gap-1.5 for sm) - Consistent spacing
Updated buttonVariants
See references/button-patterns.md for the complete CVA configuration.
tsx
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-white hover:bg-destructive/90",
outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
sm: "h-8 gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 px-6 has-[>svg]:px-4",
icon: "size-9",
"icon-sm": "size-8",
"icon-lg": "size-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
);
Visual Consistency Check
After updating, verify:
- •Same Height - Buttons in a row align perfectly
- •Same Padding - Text has consistent spacing
- •Same Radius - Corners match across variants
- •Same Transition - Hover effects are smooth and consistent
- •Same Font - Text appears identical size
- •Icon Sizes - Icon buttons have correct dimensions
Button with Next.js Link
When a button needs to navigate, use the asChild prop with Next.js <Link>:
tsx
import Link from "next/link";
import { Button } from "@/components/ui/button";
// Correct pattern - Button wraps Link with asChild
<Button asChild>
<Link href="/login">Login</Link>
</Button>
<Button asChild variant="outline" size="sm">
<Link href="/contact">Contact</Link>
</Button>
<Button asChild variant="ghost" size="icon">
<Link href="/settings">
<Settings className="size-4" />
</Link>
</Button>
Key points:
- •Always use
asChildprop when wrapping Link - •Link is the child, Button provides styling
- •Works with all variants and sizes
- •Preserves Next.js client-side navigation
DO NOT do this:
tsx
// WRONG - Button onClick with router.push
<Button onClick={() => router.push('/login')}>Login</Button>
// WRONG - Link wrapping Button
<Link href="/login"><Button>Login</Button></Link>
Testing
Test all variant + size combinations:
tsx
// All variants at same size should have identical height <div className="flex gap-2"> <Button variant="default">Default</Button> <Button variant="outline">Outline</Button> <Button variant="secondary">Secondary</Button> <Button variant="ghost">Ghost</Button> </div>
Checklist
- • All sizes have unified heights (h-9, h-8, h-10)
- • All sizes have unified padding (px-4, px-3, px-6)
- • All variants use rounded-md
- • All variants use transition-all
- • All sizes use text-sm font-medium
- • Icon sizes use square dimensions (size-9, size-8, size-10)
- • Gap is consistent per size (gap-2, gap-1.5)
- • Focus ring styles are consistent across variants
- • Navigation buttons use
asChildwith<Link>