Skip to main content

Extending Components

1. Extending with Variants

Add new variants to existing component:

// src/components/ui/button.tsx
const buttonVariants = cva(
"base-classes",
{
variants: {
variant: {
default: "...",
// Add new variant
success: "bg-success text-success-foreground hover:bg-success/90",
warning: "bg-warning text-warning-foreground hover:bg-warning/90",
},
size: {
// Add new size
xs: "h-8 px-2 text-xs",
default: "h-10 px-4 py-2",
// ... existing sizes
},
},
}
);

2. Extending with Props

Add custom props to component:

export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
// Add custom props
loading?: boolean;
icon?: React.ReactNode;
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, loading, icon, children, ...props }, ref) => {
return (
<button
ref={ref}
className={cn(buttonVariants({ variant, size, className }))}
disabled={loading || props.disabled}
{...props}
>
{loading ? (
<Spinner className="mr-2" />
) : (
icon && <span className="mr-2">{icon}</span>
)}
{children}
</button>
);
}
);

3. Extending with Composition

Compose components together:

// src/components/ui/button-group.tsx
import { Button } from "./button";
import { cn } from "@/lib/cn";

export interface ButtonGroupProps {
children: React.ReactNode;
className?: string;
}

export function ButtonGroup({ children, className }: ButtonGroupProps) {
return (
<div className={cn("flex gap-2", className)}>
{React.Children.map(children, (child) => {
if (React.isValidElement(child) && child.type === Button) {
return React.cloneElement(child, {
className: cn("flex-1", child.props.className),
});
}
return child;
})}
</div>
);
}

// Usage
<ButtonGroup>
<Button>Cancel</Button>
<Button>Save</Button>
</ButtonGroup>

4. Extending with Wrapper Components

Create wrapper components:

// src/components/ui/icon-button.tsx
import { Button, ButtonProps } from "./button";
import { LucideIcon } from "lucide-react";

export interface IconButtonProps extends Omit<ButtonProps, "children"> {
icon: LucideIcon;
label: string;
}

export function IconButton({ icon: Icon, label, ...props }: IconButtonProps) {
return (
<Button {...props} aria-label={label}>
<Icon className="h-4 w-4" />
</Button>
);
}

// Usage
<IconButton icon={Plus} label="Add item" variant="outline" />

5. Extending Tailwind Config

Add custom utilities:

// tailwind.config.ts
export default {
theme: {
extend: {
// Add custom colors
colors: {
brand: {
50: "#f0f9ff",
100: "#e0f2fe",
// ... more shades
},
},
// Add custom spacing
spacing: {
"18": "4.5rem",
"88": "22rem",
},
// Add custom breakpoints
screens: {
"3xl": "1920px",
},
},
},
};

6. Extending CSS Variables

Add new theme tokens:

/* src/app/globals.css */
:root {
/* Existing variables */
--background: 0 0% 100%;

/* Add new variables */
--brand: 217 91% 60%;
--brand-foreground: 0 0% 100%;
--custom-spacing: 1.5rem;
}

.dark {
--brand: 217 91% 50%;
--brand-foreground: 0 0% 100%;
}

Use in Tailwind:

// tailwind.config.ts
colors: {
brand: {
DEFAULT: "hsl(var(--brand))",
foreground: "hsl(var(--brand-foreground))",
},
}