A comprehensive guide to integrating the AnimatedGradient component into your shadcn-based React project
This guide will walk you through the process of integrating the AnimatedGradient component into your React project. The component creates beautiful, animated gradient backgrounds that can be used in various UI elements like cards, headers, or sections.
Before we begin, ensure your project has the following:
First, ensure your project follows the shadcn/ui convention with the following structure:
src/
├── components/
│ ├── ui/ # For shadcn UI components
│ └── hooks/ # For custom hooks
├── lib/
│ └── utils.ts # Utility functions including the cn helper
If your project doesn't have this structure, create the necessary directories:
mkdir -p src/components/ui
mkdir -p src/components/hooks
mkdir -p src/lib
The component requires several dependencies:
# Install shadcn CLI if not already installed
npm install -g shadcn-ui
# Install core dependencies
npm install tailwindcss postcss autoprefixer typescript @types/react @types/node
# Install framer-motion for animations
npm install framer-motion
# Install clsx and tailwind-merge (used by the cn utility)
npm install clsx tailwind-merge
npx tsc --init
Ensure your tsconfig.json has path aliases configured:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
// other options...
}
}
npx tailwindcss init -p
Create the file src/lib/utils.ts:
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
Create the file src/components/hooks/use-debounced-dimensions.ts:
import { useState, useEffect, RefObject } from 'react';
interface Dimensions {
width: number;
height: number;
}
export function useDimensions(ref: RefObject<HTMLElement | SVGElement>): Dimensions {
const [dimensions, setDimensions] = useState<Dimensions>({ width: 0, height: 0 });
useEffect(() => {
let timeoutId: NodeJS.Timeout;
const updateDimensions = () => {
if (ref.current) {
const { width, height } = ref.current.getBoundingClientRect();
setDimensions({ width, height });
}
};
const debouncedUpdateDimensions = () => {
clearTimeout(timeoutId);
timeoutId = setTimeout(updateDimensions, 250); // Wait 250ms after resize ends
};
// Initial measurement
updateDimensions();
window.addEventListener('resize', debouncedUpdateDimensions);
return () => {
window.removeEventListener('resize', debouncedUpdateDimensions);
clearTimeout(timeoutId);
};
}, [ref]);
return dimensions;
}
Create the file src/components/ui/animated-gradient-with-svg.tsx:
import React, { useMemo, useRef } from "react";
import { cn } from "@/lib/utils";
import { useDimensions } from "@/components/hooks/use-debounced-dimensions";
interface AnimatedGradientProps {
colors: string[];
speed?: number;
blur?: "light" | "medium" | "heavy";
}
const randomInt = (min: number, max: number) => {
return Math.floor(Math.random() * (max - min + 1)) + min;
};
const AnimatedGradient: React.FC<AnimatedGradientProps> = ({
colors,
speed = 5,
blur = "light",
}) => {
const containerRef = useRef<HTMLDivElement>(null);
const dimensions = useDimensions(containerRef);
const circleSize = useMemo(
() => Math.max(dimensions.width, dimensions.height),
[dimensions.width, dimensions.height]
);
const blurClass =
blur === "light"
? "blur-2xl"
: blur === "medium"
? "blur-3xl"
: "blur-[100px]";
return (
<div ref={containerRef} className="absolute inset-0 overflow-hidden">
<div className={cn(`absolute inset-0`, blurClass)}>
{colors.map((color, index) => (
<svg
key={index}
className="absolute animate-background-gradient"
style={
{
top: `${Math.random() * 50}%`,
left: `${Math.random() * 50}%`,
"--background-gradient-speed": `${1 / speed}s`,
"--tx-1": Math.random() - 0.5,
"--ty-1": Math.random() - 0.5,
"--tx-2": Math.random() - 0.5,
"--ty-2": Math.random() - 0.5,
"--tx-3": Math.random() - 0.5,
"--ty-3": Math.random() - 0.5,
"--tx-4": Math.random() - 0.5,
"--ty-4": Math.random() - 0.5,
} as React.CSSProperties
}
width={circleSize * randomInt(0.5, 1.5)}
height={circleSize * randomInt(0.5, 1.5)}
viewBox="0 0 100 100"
>
<circle
cx="50"
cy="50"
r="50"
fill={color}
className="opacity-30 dark:opacity-[0.15]"
/>
</svg>
))}
</div>
</div>
);
};
export { AnimatedGradient };
Create the demo file src/components/ui/demo.tsx:
import React from "react"
import { motion } from "framer-motion"
import { AnimatedGradient } from "@/components/ui/animated-gradient-with-svg"
interface BentoCardProps {
title: string
value: string | number
subtitle?: string
colors: string[]
delay: number
}
const BentoCard: React.FC<BentoCardProps> = ({
title,
value,
subtitle,
colors,
delay,
}) => {
const container = {
hidden: { opacity: 0 },
show: {
opacity: 1,
transition: {
staggerChildren: 0.1,
delayChildren: delay + 0.3,
},
},
}
const item = {
hidden: { opacity: 0 },
show: { opacity: 1, transition: { duration: 0.5 } },
}
return (
<motion.div
className="relative overflow-hidden h-full bg-background dark:bg-background/50"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.5, delay }}
>
<AnimatedGradient colors={colors} speed={0.05} blur="medium" />
<motion.div
className="relative z-10 p-3 sm:p-5 md:p-8 text-foreground backdrop-blur-sm"
variants={container}
initial="hidden"
animate="show"
>
<motion.h3
className="text-sm sm:text-base md:text-lg text-foreground"
variants={item}
>
{title}
</motion.h3>
<motion.p
className="text-2xl sm:text-4xl md:text-5xl font-medium mb-4 text-foreground"
variants={item}
>
{value}
</motion.p>
{subtitle && (
<motion.p
className="text-sm text-foreground/80"
variants={item}
>
{subtitle}
</motion.p>
)}
</motion.div>
</motion.div>
)
}
const AnimatedGradientDemo: React.FC = () => {
return (
<div className="w-full bg-background h-full">
<div className="grid grid-cols-1 md:grid-cols-3 grow h-full">
<div className="md:col-span-2">
<BentoCard
title="Total Revenue"
value="$1,234,567"
subtitle="15% increase from last month"
colors={["#3B82F6", "#60A5FA", "#93C5FD"]}
delay={0.2}
/>
</div>
<BentoCard
title="New Users"
value={1234}
subtitle="Daily signups"
colors={["#60A5FA", "#34D399", "#93C5FD"]}
delay={0.4}
/>
<BentoCard
title="Conversion Rate"
value="3.45%"
subtitle="0.5% increase from last week"
colors={["#F59E0B", "#A78BFA", "#FCD34D"]}
delay={0.6}
/>
<div className="md:col-span-2">
<BentoCard
title="Active Projects"
value={42}
subtitle="8 completed this month"
colors={["#3B82F6", "#A78BFA", "#FBCFE8"]}
delay={0.8}
/>
</div>
<div className="md:col-span-3">
<BentoCard
title="Customer Satisfaction"
value="4.8/5"
subtitle="Based on 1,000+ reviews from verified customers across all product categories"
colors={["#EC4899", "#F472B6", "#3B82F6"]}
delay={1}
/>
</div>
</div>
</div>
)
}
export { AnimatedGradientDemo }
Update your tailwind.config.js file to include the required animations:
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
'./src/app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
animation: {
"background-gradient":
"background-gradient var(--background-gradient-speed, 15s) cubic-bezier(0.445, 0.05, 0.55, 0.95) infinite",
},
keyframes: {
"background-gradient": {
"0%, 100%": {
transform: "translate(0, 0)",
animationDelay: "var(--background-gradient-delay, 0s)",
},
"20%": {
transform:
"translate(calc(100% * var(--tx-1, 1)), calc(100% * var(--ty-1, 1)))",
},
"40%": {
transform:
"translate(calc(100% * var(--tx-2, -1)), calc(100% * var(--ty-2, 1)))",
},
"60%": {
transform:
"translate(calc(100% * var(--tx-3, 1)), calc(100% * var(--ty-3, -1)))",
},
"80%": {
transform:
"translate(calc(100% * var(--tx-4, -1)), calc(100% * var(--ty-4, -1)))",
},
},
},
},
},
darkMode: "class",
plugins: [],
}
Now you can use the component in your application. Here's an example of how to import and use it:
import { AnimatedGradientDemo } from "@/components/ui/demo";
export default function HomePage() {
return (
<div className="container mx-auto p-4 h-screen">
<h1 className="text-3xl font-bold mb-6">Dashboard</h1>
<div className="h-[80vh]">
<AnimatedGradientDemo />
</div>
</div>
);
}
colors: Array of color strings (required)speed: Number controlling animation speed (default: 5)blur: "light" | "medium" | "heavy" (default: "light")useDimensions hook to track container dimensionstitle: String (required)value: String or number (required)subtitle: Optional stringcolors: Array of color strings for the gradient (required)delay: Number for animation delay (required)The components are designed to be responsive:
This component works well in:
The animated gradient provides visual interest while the card layout presents information in an organized, modern way.
tsconfig.json has proper path aliases set up:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}
You've successfully integrated the AnimatedGradient component into your project! This component provides a beautiful, animated background that can enhance various parts of your UI. The demo component shows how to use it in a dashboard-like layout, but feel free to customize it for your specific needs.