A metallic rainbow foil that shifts and shimmers as the pointer moves, like a holographic trading card.
Founding member
No. 0042
Move your pointer across
Customize
Usage
import { HolographicCard } from "@/components/motionkit/holographic-card";
<HolographicCard className="w-72">
<p>Founding member</p>
<h3>No. 0042</h3>
</HolographicCard>Motion
The exact curves this component runs, plotted with Motion's own maths.
Tilt and foil
Pointer moves over the card
Works in the App Router as is. Requires Tailwind CSS v4.
npm install motionSave as components/motionkit/holographic-card.tsx.
"use client";
import type { PointerEvent, ReactNode } from "react";
import {
motion,
useMotionTemplate,
useMotionValue,
useReducedMotion,
useSpring,
useTransform,
} from "motion/react";
type HolographicCardProps = {
children?: ReactNode;
/** 0–1. Strength of the rainbow foil. */
intensity?: number;
/** Maximum rotation in degrees. 0 keeps it flat. */
tilt?: number;
className?: string;
};
// a soft spectrum, repeated so the bands slide past as the pointer moves
const SPECTRUM =
"repeating-linear-gradient(115deg, #ff7eb3 0%, #ffcf6e 8%, #8dffb4 16%, #6ed2ff 24%, #b88bff 32%, #ff7eb3 40%)";
// fine diagonal foil lines, like the grain of a printed hologram
const FOIL = "repeating-linear-gradient(55deg, transparent 0 5px, rgb(255 255 255 / 0.07) 5px 6px)";
export function HolographicCard({ children, intensity = 0.6, tilt = 10, className = "" }: HolographicCardProps) {
const reduceMotion = useReducedMotion();
// pointer position across the card, 0–1 on each axis
const px = useMotionValue(0.5);
const py = useMotionValue(0.5);
const spring = { stiffness: 150, damping: 18 };
const sx = useSpring(px, spring);
const sy = useSpring(py, spring);
const rotateX = useTransform(sy, [0, 1], [tilt, -tilt]);
const rotateY = useTransform(sx, [0, 1], [-tilt, tilt]);
// 0–100% of a 300%-wide image slides the bands two card-widths for one pointer sweep, which is
// what makes foil look alive; going past 100% would pull the image's edge into view as a seam
const bandX = useTransform(sx, (v) => v * 100);
const bandY = useTransform(sy, (v) => v * 100);
const glareX = useTransform(sx, (v) => v * 100);
const glareY = useTransform(sy, (v) => v * 100);
const spectrumPosition = useMotionTemplate`${bandX}% ${bandY}%`;
const lightPool = useMotionTemplate`radial-gradient(circle at ${glareX}% ${glareY}%, rgb(0 0 0 / ${intensity * 0.7}), transparent 55%)`;
const glare = useMotionTemplate`radial-gradient(farthest-corner circle at ${glareX}% ${glareY}%, rgb(255 255 255 / 0.32), transparent 45%)`;
function handleMove(event: PointerEvent<HTMLDivElement>) {
if (reduceMotion) return;
const rect = event.currentTarget.getBoundingClientRect();
px.set((event.clientX - rect.left) / rect.width);
py.set((event.clientY - rect.top) / rect.height);
}
function handleLeave() {
px.set(0.5);
py.set(0.5);
}
return (
<div style={{ perspective: 900 }} className={className}>
<motion.div
onPointerMove={handleMove}
onPointerLeave={handleLeave}
style={reduceMotion ? undefined : { rotateX, rotateY }}
className="group relative isolate h-full overflow-hidden rounded-2xl border border-white/15 bg-gradient-to-br from-[#1D2727] via-[#141B1B] to-[#0C1010] text-[#F6F1E8] shadow-2xl shadow-black/50"
>
{/* a faint wash of colour over the whole card, so it reads as foil even at rest */}
<motion.div
aria-hidden
className="pointer-events-none absolute inset-0 -z-10 mix-blend-color-dodge"
style={{ backgroundImage: SPECTRUM, backgroundSize: "300% 300%", backgroundPosition: spectrumPosition, opacity: intensity * 0.18 }}
/>
<div aria-hidden className="pointer-events-none absolute inset-0 -z-10 mix-blend-overlay" style={{ backgroundImage: FOIL }} />
{/* the bright bands only appear where the light catches the surface, around the pointer */}
<motion.div
aria-hidden
className="pointer-events-none absolute inset-0 -z-10 opacity-0 mix-blend-color-dodge transition-opacity duration-500 group-focus-within:opacity-100 group-hover:opacity-100"
style={{
backgroundImage: SPECTRUM,
backgroundSize: "300% 300%",
backgroundPosition: spectrumPosition,
maskImage: lightPool,
WebkitMaskImage: lightPool,
}}
/>
<motion.div
aria-hidden
className="pointer-events-none absolute inset-0 opacity-0 mix-blend-overlay transition-opacity duration-300 group-focus-within:opacity-100 group-hover:opacity-100"
style={{ background: glare }}
/>
<div className="relative p-6 [text-shadow:0_1px_2px_rgb(0_0_0/0.4)]">{children}</div>
</motion.div>
</div>
);
}
| Prop | Type | Default | Description |
|---|---|---|---|
| intensity | number | 0.6 | 0–1. Strength of the rainbow foil. |
| tilt | number | 10 | Maximum rotation in degrees. 0 keeps it flat. |
| children | ReactNode | — | Card content. |
Launch kit
Tilt me to see the layers.
Depth Card
A card that tilts toward the cursor while its layers float at different depths, so each moves at its own speed.
Realtime sync
Move your cursor across this card.
Spotlight Card
A card with a soft glow that tracks the pointer across its surface.
Edge functions
Tilt me with your cursor.
Tilt Card
A 3D card that tilts toward the pointer, with an optional moving glare.