Velora UI
All components

Blur Fade

The workhorse entrance — blur, fade and drift into view.

0.8 KB gzippedDeps: motion/reactReduced-motion safeBase UI & Radix compatible
0s
0.15s
0.3s

Installation

npx shadcn@latest add https://velora.colorlib.com/r/blur-fade.json

Installs with: motion

Source

"use client";

import { motion, useReducedMotion } from "motion/react";

import { cn } from "@/lib/utils";

interface BlurFadeProps {
  children: React.ReactNode;
  className?: string;
  /** Seconds before the animation starts */
  delay?: number;
  duration?: number;
  /** Direction the element enters from */
  direction?: "up" | "down" | "left" | "right" | "none";
  /** Distance travelled in px */
  offset?: number;
  /** Animate only the first time it enters the viewport */
  once?: boolean;
  inView?: boolean;
}

const axis = { up: "y", down: "y", left: "x", right: "x" } as const;
const sign = { up: 1, down: -1, left: 1, right: -1 } as const;

export function BlurFade({
  children,
  className,
  delay = 0,
  duration = 0.5,
  direction = "up",
  offset = 16,
  once = true,
}: BlurFadeProps) {
  const reducedMotion = useReducedMotion();

  const hidden =
    direction === "none"
      ? { opacity: 0, filter: "blur(6px)" }
      : {
          opacity: 0,
          filter: "blur(6px)",
          [axis[direction]]: sign[direction] * offset,
        };

  // Keep `initial` identical on server and client (the server can't know the
  // motion preference — branching it mismatches on hydration). Under reduced
  // motion the reveal still happens, just instantly.
  return (
    <motion.div
      data-slot="blur-fade"
      className={cn(className)}
      initial={hidden}
      whileInView={{ opacity: 1, filter: "blur(0px)", x: 0, y: 0 }}
      viewport={{ once, margin: "0px 0px -10% 0px" }}
      transition={
        reducedMotion
          ? { duration: 0 }
          : { delay, duration, ease: [0.21, 0.47, 0.32, 0.98] }
      }
    >
      {children}
    </motion.div>
  );
}