Tilt anything toward the pointer
A tilt, glare, and shadow hover effect for React 19 and Next.js 16. Compound parts, variants, and theme tokens, in the conventions of HeroUI v3.
Installation
Requires React 19, Tailwind CSS v4, and @heroui/styles 3 or later.
npm install holo-card-tiltImport the stylesheet after HeroUI's.
@import "tailwindcss";
@import "@heroui/styles";
@import "holo-card-tilt/css"; Usage
Wrap any content in a rotator and add a glare. HoloCardTilt renders from Server Components, and its children can be Server Components.
Quarterly report
Revenue grew 18% over the previous quarter.
import {Card} from "@heroui/react";
import {HoloCardTilt} from "holo-card-tilt";
export function TiltedCard() {
return (
<HoloCardTilt className="w-80 rounded-3xl" shadow="md">
<HoloCardTilt.Rotator>
<Card className="rounded-3xl">
<Card.Header>
<Card.Title>Quarterly report</Card.Title>
<Card.Description>Revenue grew 18% over the previous quarter.</Card.Description>
</Card.Header>
</Card>
<HoloCardTilt.Glare />
</HoloCardTilt.Rotator>
</HoloCardTilt>
);
} Anatomy
HoloCardTilt tracks the pointer and owns every CSS variable. Rotator is the element that turns. Glare is the light, placed last so it sits on top. Layer lifts content toward the viewer. Set the corner radius on HoloCardTilt; the rotator and glare inherit it.
import {HoloCardTilt} from "holo-card-tilt";
<HoloCardTilt>
<HoloCardTilt.Rotator>
{/* your content */}
<HoloCardTilt.Layer />
<HoloCardTilt.Glare />
</HoloCardTilt.Rotator>
</HoloCardTilt> Variants
Every variant is a tailwind-variants slot backed by a BEM class, so they extend and override the same way HeroUI components do.
Variant
The shape of the light that follows the pointer.
<HoloCardTilt variant="glare" />
<HoloCardTilt variant="spotlight" />
<HoloCardTilt variant="sheen" /> Color
The glare and shadow take their tint from your theme tokens, in light mode, dark mode, and custom design systems.
<HoloCardTilt color="default" shadow="lg" />
<HoloCardTilt color="accent" shadow="lg" />
<HoloCardTilt color="success" shadow="lg" />
<HoloCardTilt color="warning" shadow="lg" />
<HoloCardTilt color="danger" shadow="lg" /> Tilt
How far the element rotates. tiltFactor multiplies it.
<HoloCardTilt tilt="sm" />
<HoloCardTilt tilt="md" />
<HoloCardTilt tilt="lg" /> Shadow
A shadow that shifts with the pointer and fades in with the effect. It is tinted by color, and --holo-card-tilt-shadow-color sets it to any color.
<HoloCardTilt shadow="sm" />
<HoloCardTilt shadow="md" />
<HoloCardTilt shadow="lg" /> Extending
holoCardTiltVariants is exported for use with tv.
import {tv} from "@heroui/styles";
import {HoloCardTilt, holoCardTiltVariants} from "holo-card-tilt";
const tiltVariants = tv({
extend: holoCardTiltVariants,
variants: {
tilt: {xl: {base: "[--holo-card-tilt-rotation:28deg]"}},
},
});
const {base} = tiltVariants({tilt: "xl"});
<HoloCardTilt className={base()}>…</HoloCardTilt> Recipes
Depth
Layers lift toward the viewer as the effect activates. Elements between the rotator and a layer need transform-3d.
<HoloCardTilt className="rounded-3xl" scaleFactor={1.05} tilt="lg">
<HoloCardTilt.Rotator>
<Tile className="h-56 w-56 items-center justify-center transform-3d">
<HoloCardTilt.Layer depth={30}>
<div className="size-32 rounded-2xl bg-white/15" />
</HoloCardTilt.Layer>
<HoloCardTilt.Layer className="absolute" depth={80}>
<span className="text-2xl font-semibold">Depth</span>
</HoloCardTilt.Layer>
</Tile>
<HoloCardTilt.Glare />
</HoloCardTilt.Rotator>
</HoloCardTilt> Custom Gradient
Replace the glare with any background built from the pointer variables.
const PRISM = {
"--holo-card-tilt-custom-gradient": `conic-gradient(
from var(--holo-card-tilt-angle) at var(--holo-card-tilt-gradient-x) var(--holo-card-tilt-gradient-y),
var(--accent), var(--success), var(--warning), var(--danger), var(--accent))`,
} as CSSProperties;
<HoloCardTilt blendMode="soft-light" className="rounded-3xl" style={PRISM}>
…
</HoloCardTilt> Glare Mask
Confine the glare to a pattern, a logo, or the foil areas of a card with any CSS mask-image.
const STRIPES = "repeating-linear-gradient(45deg, black 0 6px, transparent 6px 14px)";
<HoloCardTilt
blendMode="plus-lighter"
className="rounded-3xl"
glareIntensity={1.5}
glareMask={STRIPES}
variant="spotlight"
>
…
</HoloCardTilt> Active State
data-active is set while the effect runs. Style against it with Tailwind's data variant.
<HoloCardTilt className="rounded-3xl saturate-50 transition-[filter] duration-300 data-[active=true]:saturate-150">
…
</HoloCardTilt> useHoloCardTilt
The hook behind HoloCardTilt drives the same CSS variables on any element.
"use client";
import {useHoloCardTilt} from "holo-card-tilt";
export function Spotlight() {
const {ref, pointerProps} = useHoloCardTilt({exitDelay: 0});
return (
<div
ref={ref}
{...pointerProps}
className="rounded-3xl border p-10"
style={{
backgroundImage: `radial-gradient(
circle at calc(var(--holo-card-tilt-x, 0.5) * 100%) calc(var(--holo-card-tilt-y, 0.5) * 100%),
oklch(from var(--accent) l c h / calc(var(--holo-card-tilt-opacity, 0) * 0.35)),
transparent 60%)`,
}}
>
A spotlight with no tilt
</div>
);
} API
HoloCardTilt
Also supports all native div attributes.
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "glare" | "spotlight" | "sheen" | "glare" | Shape of the light that follows the pointer. |
color | "default" | "accent" | "success" | "warning" | "danger" | "default" | Theme color the glare and shadow are tinted with. |
tilt | "none" | "sm" | "md" | "lg" | "md" | How far the element rotates toward the pointer. |
shadow | "none" | "sm" | "md" | "lg" | boolean | "none" | Depth of the shadow that shifts with the pointer. true is "md". |
tiltFactor | number | 1 | Multiplies the horizontal rotation set by tilt. |
tiltFactorY | number | tiltFactor | Multiplies the vertical rotation. |
scaleFactor | number | 1 | Scale applied while active. |
springOptions | SpringOptions | {stiffness: 0.2, damping: 0.8} | Physics for the glare, shadow, and scale. |
tiltSpringOptions | SpringOptions | springOptions | Physics for the rotation. |
enterDelay | number | 0 | Milliseconds the pointer must stay inside before the effect starts. |
exitDelay | number | 200 | Milliseconds after the pointer leaves before returning to rest. |
isDisabled | boolean | false | Disables pointer tracking, leaving the element flat and unlit. |
shouldBlockScroll | boolean | true | Whether a touch on the element holds the page still, so a drag tilts it instead of scrolling. Pinch-zoom still works. |
onActiveChange | (isActive: boolean) => void | - | Called when the element becomes active or returns to rest. |
glareIntensity | number | 1 | Glare strength. |
glareHue | number | 270 | Glare hue when color is "default". |
blendMode | mix-blend-mode | "overlay" | Blend mode of the glare. |
shadowBlur | number | - | Shadow blur in pixels. Overrides the shadow size. |
shadowIntensity | number | 1 | Shadow strength. |
glareMask | string | - | CSS mask-image confining the glare. |
glareMaskMode | "match-source" | "luminance" | "alpha" | "match-source" | CSS mask-mode for glareMask. |
glareMaskComposite | "add" | "subtract" | "exclude" | "intersect" | "add" | CSS mask-composite for glareMask. |
HoloCardTilt.Layer
| Prop | Type | Default | Description |
|---|---|---|---|
depth | number | 24 | Pixels the layer lifts toward the viewer while active. |
CSS Variables
Set these on HoloCardTilt to restyle it without overriding classes.
| Variable | Value | Default | Description |
|---|---|---|---|
--holo-card-tilt-perspective | length | 600px | 3D perspective depth. |
--holo-card-tilt-rotation | angle | 10deg | Maximum rotation. Set by tilt. |
--holo-card-tilt-color | color | set by color | Base color of the glare. |
--holo-card-tilt-shadow-color | color | black | Shadow color. Set by color. |
--holo-card-tilt-shadow-opacity | number | 0.125, 0.5 in dark mode | Shadow opacity at full activation. |
--holo-card-tilt-custom-gradient | image | - | Replaces the glare background-image. |
--holo-card-tilt-custom-shadow | shadow | - | Replaces the rotator box-shadow. |
Runtime Variables
Driven by the pointer and read-only. Use them in custom gradients, shadows, and transforms.
| Variable | Range | Default | Description |
|---|---|---|---|
--holo-card-tilt-x | 0 – 1 | 0.5 | Pointer position from the left edge. |
--holo-card-tilt-y | 0 – 1 | 0.5 | Pointer position from the top edge. |
--holo-card-tilt-opacity | 0 – 1 | 0 | Activation of the effect. |
--holo-card-tilt-scale | number | 1 | Current scale. |
--holo-card-tilt-angle | 0deg – 360deg | 0deg | Clockwise angle from the center to the pointer. |
--holo-card-tilt-from-center | length | 0px | Distance from the center to the pointer. |
--holo-card-tilt-at-edge | 0 – 1 | 0 | Proximity of the pointer to the nearest edge. |
--holo-card-tilt-gradient-x | percentage | 50% | --holo-card-tilt-x as a percentage. |
--holo-card-tilt-gradient-y | percentage | 50% | --holo-card-tilt-y as a percentage. |