Components
Button Highly interactive, multipurpose button component with support for multiple variants and states. Copy Markdown
Explore six distinct visual variants designed to fit any section of your application.
Fluid, rounded design with soft shadows. Ideal for primary CTAs.
Subtle backdrop-blur with a semi-transparent background. Perfect for dark or vibrant backgrounds.
Sharp corners, bold borders, and an offset shadow for a high-impact, brutalist look.
Minimalist button without background or borders, showing only on hover.
Classic outlined style that fills on interaction. Great for secondary actions.
A very subtle, clean design with a light background and soft border. Perfect for low-priority actions or secondary information.
By default, these buttons provide subtle audible feedback on interaction. You can control this behavior globally using the Sound Toggle component or disable it per-instance via the disableSound prop.
Easily add icons, trigger a loading state, or toggle audible feedback.
Launch App Next Step Processing
< Button leftIcon = {< Rocket size = { 16 } />}>Launch App</ Button >
< Button isLoading >Processing</ Button >
Available in small, medium, and large.
Small Medium Large
The fastest way to get started is using the shadcn CLI.
Before using the @nurav-ui alias , add this to your components.json:
{
"registries" : {
"@nurav-ui" : "https://nurav-ui.vercel.app/r"
}
} Or skip alias setup entirely and use the direct URL below.
npx shadcn@latest add @nurav-ui/button
Alternatively , install via direct URL (no components.json changes needed):
npx shadcn@latest add https://nurav-ui.vercel.app/r/button.json
If you prefer manual configuration, follow these steps:
Install Dependencies Install the necessary animation and icon libraries:
npm install motion lucide-react clsx tailwind-merge Create/Customize Component Copy the source code into your project (e.g., components/nurav-ui/Button.tsx). You can modify this code to create your own custom button designs:
"use client" ;
import React from "react" ;
import { motion, HTMLMotionProps } from "motion/react" ;
import { cn } from "@/lib/utils" ;
import { Loader2 } from "lucide-react" ;
import { useSound } from "@/hooks/use-sound" ;
interface ButtonProps extends Omit < HTMLMotionProps < "button" >, "children" > {
children ?: React . ReactNode ;
variant ?: "v1" | "v2" | "v3" | "v4" | "v5" | "v6" ;
size ?: "sm" | "md" | "lg" | "icon" ;
isLoading ?: boolean ;
leftIcon ?: React . ReactNode ;
rightIcon ?: React . ReactNode ;
disableSound ?: boolean ;
}
export const Button = React. forwardRef < HTMLButtonElement , ButtonProps >(
(
{
className,
variant = "v1" ,
size = "md" ,
isLoading = false ,
leftIcon,
rightIcon,
children,
disabled,
disableSound = false ,
onClick,
... props
},
ref,
) => {
const { mouseClick } = useSound ();
const sizeStyles = {
sm: "h-9 px-3 text-xs gap-1.5" ,
md: "h-11 px-5 text-sm gap-2" ,
lg: "h-13 px-8 text-base gap-2.5" ,
icon: "h-10 w-10 flex items-center justify-center p-0" ,
};
const getVariantStyles = () => {
switch (variant) {
case "v1" :
return "rounded-full bg-foreground text-background shadow-[0_4px_14px_0_rgba(0,0,0,0.1)] hover:shadow-[0_6px_20px_rgba(0,0,0,0.15)] hover:bg-foreground/90 active:scale-95 transition-all duration-200" ;
case "v2" :
return "rounded-xl bg-foreground/5 backdrop-blur-md border text-foreground active:scale-95 transition-all duration-200" ;
case "v3" :
return "rounded-none border-2 border-foreground bg-background text-foreground font-bold uppercase shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] dark:shadow-[4px_4px_0px_0px_rgba(255,255,255,0.3)] hover:translate-x-[1px] hover:translate-y-[1px] hover:shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] dark:hover:shadow-[2px_2px_0px_0px_rgba(255,255,255,0.2)] active:translate-x-[2px] active:translate-y-[2px] active:shadow-none transition-all duration-100" ;
case "v4" :
return "rounded-lg bg-transparent text-foreground hover:bg-foreground/10 active:scale-95 transition-all duration-200" ;
case "v5" :
return "rounded-xl border-2 border-foreground/20 bg-transparent text-foreground hover:border-foreground hover:bg-foreground/5 active:scale-95 transition-all duration-200" ;
case "v6" :
return "rounded-md border border-foreground/10 text-foreground bg-foreground/2" ;
default :
return "" ;
}
};
return (
< motion.button
ref = {ref}
disabled = {isLoading || disabled}
className = { cn (
"inline-flex items-center justify-center whitespace-nowrap font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" ,
sizeStyles[size],
getVariantStyles (),
className,
)}
onClick = {( e ) => {
if ( ! disableSound) mouseClick ();
if (onClick) onClick (e);
}}
{ ... props}
>
{isLoading && < Loader2 className = "mr-2 h-4 w-4 animate-spin" />}
{ ! isLoading && leftIcon && (
< span className = "flex shrink-0" >{leftIcon}</ span >
)}
< span className = "truncate" >{children}</ span >
{ ! isLoading && rightIcon && (
< span className = "flex shrink-0" >{rightIcon}</ span >
)}
</ motion.button >
);
},
);
Button.displayName = "Button" ;
Last Updated: April 11, 2026