Button

Botones interactivos con distintos estilos y tamaños.

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

Instalación

Copia y pega el siguiente codigo en tu proyecto.

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 };

Asegurate de actualizar las rutas de importación según la estructura de tu proyecto.

Modo de uso

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

Ejemplos

Tamaños

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}

Referencia de API

Button

PropTipoDefaultDescripción
variant"default" | "destructive" | "outline" | "secondary" | "ghost" | "link""default"Estilo del botón.
size"default" | "sm" | "lg" | "icon""default"Tamaño del botón.
loadingbooleanfalseIndica si el botón está en estado de carga.
loaderReact.ReactNode<Loader2 />Componente loader personalizado para mostrar durante la carga.
leftIconReact.ReactNodeÍcono que aparece a la izquierda del contenido.
rightIconReact.ReactNodeÍcono que aparece a la derecha del contenido.
fullWidthbooleanfalseHace que el botón ocupe todo el ancho disponible.
disabledbooleanDesactiva el botón.
classNamestringClases adicionales.
type"button" | "submit" | "reset""button"Tipo de botón.
childrenReact.ReactNodeContenido del botón (texto, íconos, etc.).

ButtonGroup

PropTipoDefaultDescripción
orientation"horizontal" | "vertical""horizontal"Dirección del grupo de botones.
attachedbooleanfalseUne los botones (sin bordes redondeados entre ellos).
classNamestringClases adicionales.
childrenReact.ReactNodeBotones dentro del grupo.