Animated Gradient Component Integration Guide

A comprehensive guide to integrating the AnimatedGradient component into your shadcn-based React project

Overview

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.

Note: This guide assumes you're using a React project with TypeScript. If you're not using TypeScript, you can still follow along but may need to remove type annotations.

Prerequisites

Before we begin, ensure your project has the following:

Step-by-Step Integration

1

Project Structure Setup

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
2

Install Required Dependencies

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
3

Initialize TypeScript (if not already set up)

npx tsc --init

Ensure your tsconfig.json has path aliases configured:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    },
    // other options...
  }
}
4

Set up Tailwind CSS (if not already set up)

npx tailwindcss init -p
5

Create the Utility Function

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

Create the Custom Hook

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

Create the Component Files

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

Update Tailwind Configuration

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: [],
}
9

Using the Component

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

Component Analysis

Props and State

  1. AnimatedGradient Component:
    • Props:
      • colors: Array of color strings (required)
      • speed: Number controlling animation speed (default: 5)
      • blur: "light" | "medium" | "heavy" (default: "light")
    • State:
      • Uses useDimensions hook to track container dimensions
  2. BentoCard Component:
    • Props:
      • title: String (required)
      • value: String or number (required)
      • subtitle: Optional string
      • colors: Array of color strings for the gradient (required)
      • delay: Number for animation delay (required)
    • Animation:
      • Uses framer-motion for entrance animations with staggered children

Responsive Behavior

The components are designed to be responsive:

Best Places to Use

This component works well in:

  1. Dashboards
  2. Analytics pages
  3. Feature showcase sections
  4. Marketing pages
  5. App landing pages

The animated gradient provides visual interest while the card layout presents information in an organized, modern way.

Troubleshooting

Common Issues:
  1. Animation not working: Make sure the Tailwind config is properly updated with the animation and keyframes.
  2. TypeScript errors: Ensure all types are properly defined and imported.
  3. Path resolution issues: Check that your tsconfig.json has proper path aliases set up:
    {
      "compilerOptions": {
        "baseUrl": ".",
        "paths": {
          "@/*": ["./src/*"]
        }
      }
    }
  4. Styling issues: Make sure your Tailwind theme has the necessary colors defined (background, foreground, etc.)

Conclusion

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.