FluxUI Pro is live - modern UI, powerful animations, zero hassle.
Components
Button

Button

Ultra-modern button component with 3D effects, multiple variants, loading states, and smooth animations for enhanced user interaction.

Basic Variants

Sizes

With Icons

Loading States

Disabled States

Interactive Demo

Click the buttons below to see the 3D effects and animations in action!

Installation

1

Install the packages

npm i motion clsx tailwind-merge lucide-react
2

Add util file

lib/util.ts
import { ClassValue, clsx } from "clsx"; import { twMerge } from "tailwind-merge"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); }
3

Copy and paste the following code into your project

button.tsx
"use client"; import { motion, AnimatePresence } from "motion/react"; import React from "react"; import { cn } from "@/lib/utils"; import { Loader2 } from "lucide-react"; interface ButtonProps { variant?: "primary" | "secondary" | "ghost" | "outline" | "destructive"; size?: "sm" | "md" | "lg" | "xl"; loading?: boolean; disabled?: boolean; icon?: React.ReactNode; iconPosition?: "left" | "right"; children?: React.ReactNode; onClick?: () => void; className?: string; } const Button: React.FC<ButtonProps> = ({ variant = "primary", size = "md", loading = false, disabled = false, icon, iconPosition = "left", children, onClick, className, }) => { const isDisabled = disabled || loading; const baseClasses = "relative inline-flex items-center justify-center font-medium transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:cursor-not-allowed overflow-hidden"; const variantClasses = { primary: "bg-gradient-to-r from-blue-500 to-purple-600 text-white shadow-lg hover:shadow-xl hover:from-blue-600 hover:to-purple-700 focus:ring-blue-500", secondary: "bg-gradient-to-r from-gray-100 to-gray-200 text-gray-900 shadow-md hover:shadow-lg hover:from-gray-200 hover:to-gray-300 focus:ring-gray-500 dark:from-gray-800 dark:to-gray-700 dark:text-white dark:hover:from-gray-700 dark:hover:to-gray-600", ghost: "bg-transparent text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:ring-gray-500 dark:text-gray-300 dark:hover:bg-gray-800 dark:hover:text-white", outline: "border-2 border-gray-300 bg-transparent text-gray-700 hover:bg-gray-50 hover:border-gray-400 focus:ring-gray-500 dark:border-gray-600 dark:text-gray-300 dark:hover:bg-gray-800 dark:hover:border-gray-500", destructive: "bg-gradient-to-r from-red-500 to-pink-600 text-white shadow-lg hover:shadow-xl hover:from-red-600 hover:to-pink-700 focus:ring-red-500", }; const sizeClasses = { sm: "px-3 py-1.5 text-sm rounded-lg gap-1.5", md: "px-4 py-2 text-base rounded-lg gap-2", lg: "px-6 py-3 text-lg rounded-xl gap-2.5", xl: "px-8 py-4 text-xl rounded-2xl gap-3", }; const buttonVariants = { primary: { scale: [1, 0.98, 1], transition: { duration: 0.2 } }, secondary: { scale: [1, 0.98, 1], transition: { duration: 0.2 } }, ghost: { scale: [1, 1.02, 1], transition: { duration: 0.2 } }, outline: { scale: [1, 0.98, 1], transition: { duration: 0.2 } }, destructive: { scale: [1, 0.95, 1], transition: { duration: 0.3 } }, }; return ( <motion.button className={cn( baseClasses, variantClasses[variant], sizeClasses[size], isDisabled && "opacity-60 cursor-not-allowed", className )} onClick={isDisabled ? undefined : onClick} disabled={isDisabled} whileHover={!isDisabled ? { y: -2 } : {}} whileTap={!isDisabled ? buttonVariants[variant] : {}} initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.3 }} > {/* 3D Shadow Effect */} <motion.div className="absolute inset-0 bg-black/10 rounded-inherit" initial={{ y: 0 }} whileHover={{ y: 1 }} transition={{ duration: 0.2 }} /> {/* Ripple Effect */} <motion.div className="absolute inset-0 bg-white/20 rounded-inherit" initial={{ scale: 0, opacity: 1 }} whileTap={{ scale: 4, opacity: 0 }} transition={{ duration: 0.4 }} /> {/* Content */} <AnimatePresence mode="wait"> {loading ? ( <motion.div key="loading" initial={{ opacity: 0, scale: 0.8 }} animate={{ opacity: 1, scale: 1 }} exit={{ opacity: 0, scale: 0.8 }} className="flex items-center gap-2" > <Loader2 className="w-4 h-4 animate-spin" /> <span>Loading...</span> </motion.div> ) : ( <motion.div key="content" initial={{ opacity: 0, scale: 0.8 }} animate={{ opacity: 1, scale: 1 }} exit={{ opacity: 0, scale: 0.8 }} className="flex items-center relative z-10" > {icon && iconPosition === "left" && ( <motion.span initial={{ x: -10, opacity: 0 }} animate={{ x: 0, opacity: 1 }} transition={{ delay: 0.1 }} > {icon} </motion.span> )} {children && ( <motion.span initial={{ y: 10, opacity: 0 }} animate={{ y: 0, opacity: 1 }} transition={{ delay: 0.15 }} > {children} </motion.span> )} {icon && iconPosition === "right" && ( <motion.span initial={{ x: 10, opacity: 0 }} animate={{ x: 0, opacity: 1 }} transition={{ delay: 0.1 }} > {icon} </motion.span> )} </motion.div> )} </AnimatePresence> {/* Glow Effect for Primary */} {variant === "primary" && ( <motion.div className="absolute inset-0 bg-gradient-to-r from-blue-400/20 to-purple-400/20 rounded-inherit blur-xl" initial={{ opacity: 0 }} whileHover={{ opacity: 1 }} transition={{ duration: 0.3 }} /> )} </motion.button> ); }; export default Button;
4

Update the import paths to match your project setup

Props

PropTypeDefaultDescription
variant'primary' | 'secondary' | 'ghost' | 'outline' | 'destructive''primary'The visual style variant of the button.
size'sm' | 'md' | 'lg' | 'xl''md'The size of the button.
loadingbooleanfalseShows loading spinner and disables interaction.
disabledbooleanfalseDisables the button interaction.
iconReactNodeundefinedIcon to display in the button.
iconPosition'left' | 'right''left'Position of the icon relative to text.
childrenReactNodeundefinedThe button content/text.
onClick() => void() => {}Click handler function.