Loader

PreviousNext

Animated versatile loader with different variants, customizable size and content.

Docs
scrollxuicomponent

Preview

Loading preview…
components/ui/loader.tsx
"use client";
import { ReactNode, useMemo } from "react";
import { motion } from "framer-motion";
import { cn } from "@/lib/utils";

interface LoaderProps {
  children?: ReactNode;
  className?: string;
  variant?: "default" | "cube" | "dual-ring" | "magnetic-dots";
  size?: number;
}

export function Loader({
  children,
  className = "",
  variant = "default",
  size,
}: LoaderProps) {
  const finalSize = useMemo(() => size ?? 24, [size]);

  return (
    <div className={cn("flex gap-2", className)}>
      <div
        className="relative flex items-center justify-center"
        style={{
          height: finalSize,
          width: finalSize,
        }}
      >
        {variant === "default" && (
          <>
            <div className="absolute inset-0 rounded-full border-t-[1.5px] border-b-[1.5px] border-black/30 dark:border-white/30" />
            <motion.div
              className="absolute inset-0 rounded-full border-t-[1.5px] border-b-[1.5px] border-black dark:border-white"
              animate={{ rotate: 360 }}
              transition={{ repeat: Infinity, duration: 1, ease: "linear" }}
            />
          </>
        )}

        {variant === "cube" && (
          <motion.div
            className="absolute inset-0 bg-black dark:bg-white shadow-[0_0_4px_rgba(255,255,255,0.6)] dark:shadow-[0_0_4px_rgba(255,255,255,0.6)]"
            animate={{ rotateX: [0, 180, 0], rotateY: [0, 180, 0] }}
            transition={{ repeat: Infinity, duration: 1.1, ease: "linear" }}
          />
        )}

        {variant === "dual-ring" && (
          <>
            <div className="absolute inset-0 rounded-full border-[1.5px] border-black/20 dark:border-white/20 shadow-[0_0_4px_rgba(0,0,0,0.3)] dark:shadow-[0_0_4px_rgba(255,255,255,0.3)]" />
            <motion.div
              className="absolute inset-0 rounded-full border-t-[1.5px] border-black border-b-transparent dark:border-white dark:border-b-transparent shadow-[0_0_6px_rgba(0,0,0,0.5)] dark:shadow-[0_0_6px_rgba(255,255,255,0.7)]"
              animate={{ rotate: 360 }}
              transition={{ repeat: Infinity, duration: 1, ease: "linear" }}
            />
          </>
        )}

        {variant === "magnetic-dots" && (
          <div className="relative flex items-center justify-center h-full w-full">
            <motion.div
              className="absolute rounded-full bg-black dark:bg-white"
              style={{
                height: finalSize / 3,
                width: finalSize / 3,
              }}
              animate={{ x: [-(finalSize / 3), 0, -(finalSize / 3)] }}
              transition={{
                repeat: Infinity,
                duration: 1.2,
                ease: "easeInOut",
                times: [0, 0.5, 1],
              }}
            />
            <motion.div
              className="absolute rounded-full bg-black dark:bg-white"
              style={{
                height: finalSize / 3,
                width: finalSize / 3,
              }}
              animate={{ x: [finalSize / 3, 0, finalSize / 3] }}
              transition={{
                repeat: Infinity,
                duration: 1.2,
                ease: "easeInOut",
                times: [0, 0.5, 1],
              }}
            />
            <motion.div
              className="absolute rounded-full bg-black dark:bg-white opacity-0"
              style={{
                height: finalSize / 3,
                width: finalSize / 3,
              }}
              animate={{ opacity: [0, 1, 0] }}
              transition={{
                repeat: Infinity,
                duration: 1.2,
                ease: "easeInOut",
                times: [0.45, 0.5, 0.55],
              }}
            />
          </div>
        )}
      </div>

      {children && <div className="text-sm">{children}</div>}
    </div>
  );
}

Installation

npx shadcn@latest add @scrollxui/loader

Usage

import { Loader } from "@/components/loader"
<Loader />