Tim Wickstrom

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.

React 19 · Next.js 16
Holo Card TiltMove your pointer

Installation

Requires React 19, Tailwind CSS v4, and @heroui/styles 3 or later.

npm install holo-card-tilt

Import the stylesheet after HeroUI's.

globals.css
@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.

tsx
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.

tsx
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.

glare
spotlight
sheen
tsx
<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.

default
accent
success
warning
danger
tsx
<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.

sm
md
lg
tsx
<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.

sm
md
lg
tsx
<HoloCardTilt shadow="sm" />
<HoloCardTilt shadow="md" />
<HoloCardTilt shadow="lg" />

Extending

holoCardTiltVariants is exported for use with tv.

tsx
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.

Depth
tsx
<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.

Theme prism
tsx
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.

Masked glare
tsx
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.

data-active
tsx
<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.

A spotlight with no tilt
tsx
"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.