Button

Interactive buttons with different styles and sizes.

button-demo.tsx
1import { Button } from '@/components/ui/button';
2
3export function ButtonDemo() {
4 return <Button>Click me</Button>;
5}

Installation

Copy and paste the following code in your project.

button-source.tsx
'use client';
import { cva, type VariantProps } from 'class-variance-authority';
import { Loader2 } from 'lucide-react';
import { AnimatePresence, HTMLMotionProps, motion } from 'motion/react';
import React, { forwardRef } from 'react';
import { cn } from '../../../lib/cn';
const buttonVariants = cva(
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-xl text-sm font-medium outline-none select-none relative overflow-hidden disabled:pointer-events-none disabled:opacity-50 disabled:cursor-not-allowed aria-invalid:ring-2 aria-invalid:ring-destructive/50 aria-invalid:border-destructive',
{
variants: {
variant: {
default:
'bg-primary text-primary-foreground shadow-md hover:shadow-lg hover:shadow-primary/20',
destructive:
'bg-destructive text-destructive-foreground shadow-md hover:shadow-lg hover:shadow-destructive/20',
outline: 'border-2 border-input bg-background hover:bg-muted',
secondary:
'bg-secondary text-secondary-foreground shadow-md hover:shadow-lg hover:shadow-secondary/20',
ghost: 'hover:bg-muted',
link: 'text-primary underline-offset-4 hover:underline',
},
size: {
default: 'h-10 px-4 py-2',
sm: 'h-9 px-3 text-xs',
lg: 'h-11 px-8 text-base',
icon: 'size-9',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
},
);
interface ButtonProps extends HTMLMotionProps<'button'>, VariantProps<typeof buttonVariants> {
loading?: boolean;
loader?: React.ReactNode;
leftIcon?: React.ReactNode;
rightIcon?: React.ReactNode;
fullWidth?: boolean;
asChild?: boolean;
children?: React.ReactNode;
}
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
(
{
className,
variant = 'default',
size = 'default',
loading = false,
loader,
leftIcon,
rightIcon,
fullWidth = false,
disabled,
children,
type = 'button',
...props
},
ref,
) => {
const isDisabled = disabled || loading;
return (
<motion.button
ref={ref}
type={type}
disabled={isDisabled}
className={cn(buttonVariants({ variant, size }), fullWidth && 'w-full', className)}
whileHover={{ scale: 1.02, y: -1 }}
whileTap={{ scale: 0.96 }}
transition={{ type: 'spring', stiffness: 400, damping: 17 }}
{...props}
>
<AnimatePresence mode="popLayout" initial={false}>
{loading ? (
<motion.span
key="loader"
initial={{ opacity: 0, scale: 0.8, filter: 'blur(4px)' }}
animate={{ opacity: 1, scale: 1, filter: 'blur(0px)' }}
exit={{ opacity: 0, scale: 0.8, filter: 'blur(4px)' }}
transition={{ duration: 0.2 }}
className="absolute flex items-center justify-center"
>
{loader ?? <Loader2 className="size-4 animate-spin" />}
</motion.span>
) : (
<motion.div
key="content"
className="flex items-center gap-2"
initial={{ opacity: 0, y: 5, filter: 'blur(4px)' }}
animate={{ opacity: 1, y: 0, filter: 'blur(0px)' }}
exit={{ opacity: 0, y: -5, filter: 'blur(4px)' }}
transition={{ duration: 0.2 }}
>
{leftIcon && <span className="shrink-0">{leftIcon}</span>}
<span>{children}</span>
{rightIcon && <span className="shrink-0">{rightIcon}</span>}
</motion.div>
)}
</AnimatePresence>
</motion.button>
);
},
);
Button.displayName = 'Button';
interface ButtonGroupProps extends React.HTMLAttributes<HTMLDivElement> {
children: React.ReactNode;
orientation?: 'horizontal' | 'vertical';
attached?: boolean;
}
const ButtonGroup = forwardRef<HTMLDivElement, ButtonGroupProps>(
({ className, children, orientation = 'horizontal', attached = false, ...props }, ref) => {
return (
<div
ref={ref}
role="group"
className={cn(
'inline-flex',
orientation === 'horizontal' ? 'flex-row' : 'flex-col',
attached
? orientation === 'horizontal'
? '[&>button:not(:first-child)]:-ml-px [&>button:not(:first-child)]:rounded-l-none [&>button:not(:last-child)]:rounded-r-none'
: '[&>button:not(:first-child)]:-mt-px [&>button:not(:first-child)]:rounded-t-none [&>button:not(:last-child)]:rounded-b-none'
: 'gap-2',
className,
)}
{...props}
>
{children}
</div>
);
},
);
ButtonGroup.displayName = 'ButtonGroup';
export { Button, ButtonGroup, buttonVariants };
export type { ButtonGroupProps, ButtonProps };

Make sure to update the import paths according to your project structure.

Usage

import { Button } from '@/components/ui/button';
<Button variant="outline">Button</Button>

Examples

Sizes

button-sizes-demo.tsx
1import { Plus, Settings } from 'lucide-react';
2import { Button } from '@/components/ui/button';
3
4export function ButtonSizesDemo() {
5 return (
6 <div className="flex flex-col gap-6">
7 <div className="flex flex-wrap items-center gap-4">
8 <Button size="sm">Small</Button>
9 <Button size="default">Default</Button>
10 <Button size="lg">Large</Button>
11 <Button size="icon" aria-label="Settings">
12 <Settings className="size-4" />
13 </Button>
14 </div>
15 <div className="flex flex-wrap items-center gap-4">
16 <Button size="sm" leftIcon={<Plus className="size-4" />}>
17 Add small
18 </Button>
19 <Button size="default" leftIcon={<Plus className="size-4" />}>
20 Add default
21 </Button>
22 <Button size="lg" leftIcon={<Plus className="size-5" />}>
23 Add large
24 </Button>
25 </div>
26 </div>
27 );
28}

Secondary

button-secondary-demo.tsx
1import { Button } from '@/components/ui/button';
2
3export function ButtonSecondaryDemo() {
4 return <Button variant="secondary">Secondary</Button>;
5}

Destructive

button-destructive-demo.tsx
1import { Button } from '@/components/ui/button';
2
3export function ButtonDestructiveDemo() {
4 return <Button variant="destructive">Destructive</Button>;
5}

Outline

button-outline-demo.tsx
1import { Button } from '@/components/ui/button';
2
3export function ButtonOutlineDemo() {
4 return <Button variant="outline">Outline</Button>;
5}

Ghost

button-ghost-demo.tsx
1import { Button } from '@/components/ui/button';
2
3export function ButtonGhostDemo() {
4 return <Button variant="ghost">Ghost</Button>;
5}
button-link-demo.tsx
1import { Button } from '@/components/ui/button';
2
3export function ButtonLinkDemo() {
4 return <Button variant="link">Link</Button>;
5}

API Reference

Button

PropTypeDefaultDescription
variant"default" | "destructive" | "outline" | "secondary" | "ghost" | "link""default"The button style.
size"default" | "sm" | "lg" | "icon""default"The button size.
loadingbooleanfalseWhether the button is in a loading state.
loaderReact.ReactNode<Loader2 />Custom loader component to be displayed when loading.
leftIconReact.ReactNodeIcon to display on the left side of the button.
rightIconReact.ReactNodeIcon to display on the right side of the button.
fullWidthbooleanfalseWhether the button should take up the full width of its container.
disabledbooleanDisables the button.
classNamestringAdditional CSS class names.
type"button" | "submit" | "reset""button"The type of the button element.
childrenReact.ReactNodeThe button content (text or other elements).

ButtonGroup

PropTypeDefaultDescription
orientation"horizontal" | "vertical""horizontal"The layout of the button group, either horizontal or vertical.
attachedbooleanfalseWhether the buttons in the group are attached to each other (i.e., no border-radius between buttons).
classNamestringAdditional CSS class names.
childrenReact.ReactNodeThe buttons inside the group.