[![npm version](https://img.shields.io/npm/v/holo-card-tilt?label=npm)](https://www.npmjs.com/package/holo-card-tilt)[![Test status](https://img.shields.io/github/actions/workflow/status/twickstrom/holo-card-tilt/ci.yml?branch=main&label=tests)](https://github.com/twickstrom/holo-card-tilt/actions/workflows/ci.yml)[![TypeScript types included](https://img.shields.io/npm/types/holo-card-tilt)](https://www.npmjs.com/package/holo-card-tilt)[![License](https://img.shields.io/npm/l/holo-card-tilt)](https://github.com/twickstrom/holo-card-tilt/blob/main/LICENSE)
# 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.
   [Get Started](#installation) [GitHub](https://github.com/twickstrom/holo-card-tilt) [npm](https://www.npmjs.com/package/holo-card-tilt)    React 19 · Next.js 16Holo Card TiltMove your pointer
## Installation

Requires React 19, Tailwind CSS v4, and @heroui/styles 3 or later.
  npmpnpmyarnbun
```
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.
    glarespotlightsheen    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.
    defaultaccentsuccesswarningdanger    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.
    smmdlg    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.
    smmdlg    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. |