satoriui
componentblur-revealsource: ok · hit
Blur Reveal
Mysterious blur reveal animation, just feels awesome.
npx shadcn add "https://satoriui.site/r/blur-reveal.json"free
Visual style
glass-blur
Interaction
scroll-reveal
Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.
Dependencies
Bundle weight · mediumanimationmotion
Files1 · source cached 10/8/2026, 12:52:34 PM
packages/blur-reveal/blur-reveal.tsx
"use client";
import { AnimatePresence, motion } from "motion/react";
import type React from "react";
export interface BlurRevealProps {
children: string;
className?: string;
delay?: number;
speedReveal?: number;
speedSegment?: number;
trigger?: boolean;
onAnimationComplete?: () => void;
onAnimationStart?: () => void;
as?: keyof React.JSX.IntrinsicElements;
style?: React.CSSProperties;
inView?: boolean;
once?: boolean;
letterSpacing?: string | number;
}
const BlurReveal = ({
children,
className,
delay = 0,
speedReveal = 1.5,
speedSegment = 0.5,
trigger = true,
onAnimationComplete,
onAnimationStart,
as = "p",
style,
inView = false,
once = true,
letterSpacing,
}: BlurRevealProps) => {
const MotionTag = motion[as as keyof typeof motion] as typeof motion.div;
const stagger = 0.03 / speedReveal;
const baseDuration = 0.3 / speedSegment;
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: stagger,
delayChildren: delay,
},
},
exit: {
transition: {
staggerChildren: stagger,
staggerDirection: -1,
},
},
};
const itemVariants = {
hidden: { opacity: 0, filter: "blur(12px)", y: 10 },
visible: {
opacity: 1,
filter: "blur(0px)",
y: 0,
transition: {
duration: baseDuration,
},
},
exit: { opacity: 0, filter: "blur(12px)", y: 10 },
};
return (
<AnimatePresence mode="popLayout">
{trigger && (
<MotionTag
initial="hidden"
whileInView={inView ? "visible" : undefined}
animate={inView ? undefined : "visible"}
exit="exit"
variants={containerVariants}
viewport={{ once }}
className={className}
onAnimationComplete={onAnimationComplete}
onAnimationStart={onAnimationStart}
style={style}
>
<span className="sr-only">{children}</span>
{children &&
children.split(" ").map((word, wordIndex, wordsArray) => (
<span
key={`word-${wordIndex}`}
className="inline-block whitespace-nowrap"
aria-hidden="true"
>
{word.split("").map((char, charIndex) => (
<motion.span
key={`char-${wordIndex}-${charIndex}`}
variants={itemVariants}
className="inline-block"
style={
letterSpacing ? { marginRight: letterSpacing } : undefined
}
>
{char}
</motion.span>
))}
{wordIndex < wordsArray.length - 1 && (
<motion.span
key={`space-${wordIndex}`}
variants={itemVariants}
className="inline-block"
>
</motion.span>
)}
</span>
))}
</MotionTag>
)}
</AnimatePresence>
);
};
export default BlurReveal;