---
title: Ripple Loader
description: A customizable animated ripple loader component with layered ripple effects and a centered icon, built using React and inline CSS-in-JSX. Accepts dynamic icons through props.
---

<ComponentPreview name="ripple-loader-demo" />

## Installation

<Tabs defaultValue="cli">

<TabsList>
  <TabsTrigger value="cli">CLI</TabsTrigger>
  <TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">

```bash
npx lightswind@latest add ripple-loader
```

</TabsContent>

<TabsContent value="manual">

<Steps>

<Step>Copy and paste the following code into your project.</Step>

```tsx
"use client";
import React from "react";
import { motion } from "framer-motion";

type RippleLoaderProps = {
  icon?: React.ReactNode;
  size?: number;
  duration?: number; // in seconds
  logoColor?: string;
  className?: string;
};

const RippleLoader: React.FC<RippleLoaderProps> = ({
  icon,
  size = 250,
  duration = 2, // use number for easier calculations
  logoColor = "grey",
  className = "",
}) => {
  const baseInset = 40;
  const rippleBoxes = Array.from({ length: 5 }, (_, i) => ({
    inset: `${baseInset - i * 10}%`,
    zIndex: 99 - i,
    delay: i * 0.2,
    opacity: 1 - i * 0.2,
  }));

  return (
    <div
      className={`relative ${className}`}
      style={{ width: size, height: size }}
    >
      {rippleBoxes.map((box, i) => (
        <motion.div
          key={i}
          className="absolute rounded-full border-t backdrop-blur-[5px]"
          style={{
            inset: box.inset,
            zIndex: box.zIndex,
            borderColor: `rgba(100,100,100,${box.opacity})`,
            background:
              "linear-gradient(0deg, rgba(50, 50, 50, 0.2), rgba(100, 100, 100, 0.2))",
          }}
          animate={{
            scale: [1, 1.3, 1],
            boxShadow: [
              "rgba(0, 0, 0, 0.3) 0px 10px 10px 0px",
              "rgba(0, 0, 0, 0.3) 0px 30px 20px 0px",
              "rgba(0, 0, 0, 0.3) 0px 10px 10px 0px",
            ],
          }}
          transition={{
            repeat: Infinity,
            duration,
            delay: box.delay,
            ease: "easeInOut",
          }}
        />
      ))}

      {/* ICON CONTAINER 
        Set a specific z-index (e.g., 100) to ensure it's above all ripples.
        Removed p-[20%] to give the icon max available space inside the center.
      */}
      <div
        className="absolute inset-0 grid place-content-center"
        style={{ zIndex: 100, padding: `${baseInset / 2}%` }}
      >
        <motion.span
          className="w-full h-full"
          animate={{ color: [logoColor, "#ffffff", logoColor] }}
          transition={{
            repeat: Infinity,
            duration,
            delay: 0.1,
            ease: "easeInOut",
          }}
        >
          <span
            className="w-full h-full text-foreground"
            style={{ display: "inline-block", width: "100%", height: "100%" }}
          >
            {icon &&
              React.cloneElement(icon as React.ReactElement<any>, {
                className: "w-full h-full",
              })}
          </span>
        </motion.span>
      </div>
    </div>
  );
};

export default RippleLoader;
```

</Steps>

</TabsContent>

</Tabs>

## Usage

```tsx
import RippleLoader from '@/components/lightswind/ripple-loader';
```

```tsx
import RippleLoader from '@/components/lightswind/ripple-loader';
import { FaReact } from 'react-icons/fa';

<RippleLoader
  icon={<FaReact size="100%" />}
  size={200}
  duration="2s"
  logoColor="dodgerblue"
/>;
```
