Sliding Reveal Card
The Sliding Reveal Card is an interactive UI component that lets users uncover hidden content by dragging a divider across the card. Built with smooth motion animations and responsive behavior, it creates an engaging reveal effect — ideal for comparisons, transformations, layered text, or dynamic content presentation in modern interfaces.
lucide-reactframer-motion
Slide to reveal the hidden content
✨ Hidden Content
Installation
Install the required dependencies:
npm install lucide-react framer-motioncomponents/ui/sliding-reveal-card.tsx
"use client";
import { motion, useMotionValue, useTransform, useSpring } from "framer-motion";
import { ChevronsLeftRight } from "lucide-react";
import { useRef, useEffect, useState, ReactNode } from "react";
type TextRevealCardProps = {
leftContent?: ReactNode;
rightContent?: ReactNode;
height?: number | string;
};
export default function SlidingRevealCard({
leftContent,
rightContent,
height = 220,
}: TextRevealCardProps) {
const containerRef = useRef<HTMLDivElement>(null);
const rawX = useMotionValue(0);
const x = useSpring(rawX, { stiffness: 250, damping: 30 }); // smooth drag
const [width, setWidth] = useState(0);
// Responsive width detection
useEffect(() => {
const updateWidth = () => {
if (containerRef.current) {
setWidth(containerRef.current.offsetWidth);
}
};
updateWidth();
window.addEventListener("resize", updateWidth);
return () => window.removeEventListener("resize", updateWidth);
}, []);
const clipPath = useTransform(x, (val) => {
const clamped = Math.max(0, Math.min(val, width));
const percent = ((width - clamped) / width) * 100;
return `inset(0 ${percent}% 0 0)`;
});
return (
<div
ref={containerRef}
className="relative w-full max-w-2xl mx-auto my-24 rounded-2xl overflow-hidden
border border-zinc-200 dark:border-zinc-700
shadow-xl bg-white dark:bg-zinc-900"
style={{ height }}
>
{/* LEFT CONTENT (Revealed) */}
<div
className="absolute inset-0 p-8 flex items-center justify-center
text-2xl md:text-3xl font-medium
text-zinc-400 dark:text-zinc-600
bg-gradient-to-br from-white to-zinc-100
dark:from-zinc-900 dark:to-zinc-800"
>
{leftContent || "Slide to reveal the hidden content"}
</div>
{/* RIGHT CONTENT (Initially Visible) */}
<motion.div
style={{ clipPath }}
className="absolute inset-0 p-8 flex items-center justify-center
text-2xl md:text-3xl font-semibold
text-black dark:text-white
bg-gradient-to-br from-zinc-100 to-white
dark:from-zinc-800 dark:to-zinc-900"
>
{rightContent || "✨ Hidden Content"}
</motion.div>
{/* DRAG HANDLE */}
<motion.div
drag="x"
dragConstraints={containerRef}
style={{ x }}
className="absolute top-0 bottom-0 w-1 z-30 cursor-ew-resize"
>
{/* Gradient divider */}
<div className="w-full h-full bg-gradient-to-b from-amber-400 via-amber-500 to-amber-600 shadow-xl" />
<div
className="absolute top-1/2 left-[0.5] -translate-y-1/2 -translate-x-1/2
w-8 h-8 rounded-full
bg-white dark:bg-zinc-800
border border-zinc-300 dark:border-zinc-600
flex items-center justify-center
shadow-lg"
>
<ChevronsLeftRight className="w-4 h-4 text-amber-600" />
</div>
</motion.div>
</div>
);
}