# Tilted Card

> A 3D card that tilts toward the cursor, with glare, a soft shadow, floating depth layers and a flip side.

A 3D card that leans toward the cursor on springs, catches a moving glare on its face and edges, and lifts a soft shadow. Hover to tilt, click, tap or press Enter to flip it over, and wrap content in `TiltedCardLayer` to float it above the photo with parallax.

<demo-tilted-card>



</demo-tilted-card>

## Installation

```bash
npx shadcn-vue@latest add "https://nxui.geoql.in/r/tilted-card.json"
```

## Usage

```vue
<script setup lang="ts">
  import { TiltedCard, TiltedCardLayer } from '~/components/ui/tilted-card';
</script>

<template>
  <div class="aspect-square w-75">
    <TiltedCard
      image-src="/covers/open-sky.webp"
      alt-text="Open Sky album cover"
      caption-text="Open Sky — Blue Hour"
      container-width="100%"
      container-height="100%"
      image-width="100%"
      image-height="100%"
      @flip="(flipped) => console.log(flipped)"
    >
      <TiltedCardLayer :depth="28">
        <div class="absolute bottom-4 left-4 text-white">Open Sky</div>
      </TiltedCardLayer>

      <template #back>
        <div class="absolute inset-0 bg-card p-4">Track list</div>
      </template>
    </TiltedCard>
  </div>
</template>
```

One spring simulation drives tilt, lift, flip, press and the caption tag, and it sleeps once everything settles. With no pointer, the card can follow the phone's tilt (`gyroscope`), drift on its own (`floating`) or follow the cursor anywhere on the page (`track-window`).

## Props

<table>
<thead>
  <tr>
    <th>
      Prop
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Default
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        image-src
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      —
    </td>
    
    <td>
      Photo that fills the card.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        alt-text
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        'Tilted card image'
      </code>
    </td>
    
    <td>
      Alternative text for the photo.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        caption-text
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      Text in the small tag that follows the cursor and swings as it moves.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        show-tooltip
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Shows the caption tag on hover.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        display-overlay-content
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Shows the <code>
        overlay
      </code>
      
       slot.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        overlay-depth
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        30
      </code>
    </td>
    
    <td>
      How far the overlay floats in front, in px.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        flip-on-click
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Flips the card when it is clicked. Needs the <code>
        back
      </code>
      
       slot.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        flipped
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      —
    </td>
    
    <td>
      Controls which side shows (<code>
        v-model:flipped
      </code>
      
      ). Leave unset to let clicks flip it.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        on-click
      </code>
    </td>
    
    <td>
      <code>
        (event: MouseEvent | KeyboardEvent)
      </code>
    </td>
    
    <td>
      —
    </td>
    
    <td>
      Called when the card is clicked or activated with the keyboard (<code>
        @click
      </code>
      
      ).
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        container-width
      </code>
    </td>
    
    <td>
      <code>
        number | string
      </code>
    </td>
    
    <td>
      <code>
        '100%'
      </code>
    </td>
    
    <td>
      Width of the area that reacts to the cursor.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        container-height
      </code>
    </td>
    
    <td>
      <code>
        number | string
      </code>
    </td>
    
    <td>
      <code>
        '300px'
      </code>
    </td>
    
    <td>
      Height of the area that reacts to the cursor.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        image-width
      </code>
    </td>
    
    <td>
      <code>
        number | string
      </code>
    </td>
    
    <td>
      <code>
        '300px'
      </code>
    </td>
    
    <td>
      Width of the card.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        image-height
      </code>
    </td>
    
    <td>
      <code>
        number | string
      </code>
    </td>
    
    <td>
      <code>
        '300px'
      </code>
    </td>
    
    <td>
      Height of the card.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        rotate-amplitude
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        12
      </code>
    </td>
    
    <td>
      How far the card tilts toward the cursor, in degrees.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        scale-on-hover
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1.05
      </code>
    </td>
    
    <td>
      How much the card grows when hovered.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        perspective
      </code>
    </td>
    
    <td>
      <code>
        number | string
      </code>
    </td>
    
    <td>
      <code>
        800
      </code>
    </td>
    
    <td>
      Viewing distance in px. Lower values give a stronger 3D effect.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        radius
      </code>
    </td>
    
    <td>
      <code>
        number | string
      </code>
    </td>
    
    <td>
      <code>
        15
      </code>
    </td>
    
    <td>
      Corner radius of the card.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        smoothness
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.6
      </code>
    </td>
    
    <td>
      From 0 to 1. Low values snap to the cursor, high values glide with a soft bounce.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        glare
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.5
      </code>
    </td>
    
    <td>
      Strength of the light on the surface and edges as the card tilts, from 0 to 1.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        glare-color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        '#ffffff'
      </code>
    </td>
    
    <td>
      Color of the glare (hex or rgb).
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        shadow
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.5
      </code>
    </td>
    
    <td>
      Strength of the soft shadow that shifts and grows as the card lifts, from 0 to 1.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        parallax
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1
      </code>
    </td>
    
    <td>
      How far layers and the photo shift with the tilt. <code>
        0
      </code>
      
       keeps everything flat.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        axis
      </code>
    </td>
    
    <td>
      <code>
        'both' | 'horizontal' | 'vertical'
      </code>
    </td>
    
    <td>
      <code>
        'both'
      </code>
    </td>
    
    <td>
      Limits the tilt to one direction of cursor movement.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        reverse
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Tilts away from the cursor instead of toward it.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        track-window
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Follows the cursor anywhere on the page, not only while hovered.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        floating
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Drifts and sways gently while idle.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        gyroscope
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Tilts with a phone or tablet. Asks for permission on iOS on the first tap.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        intro
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Rises and settles into place when it first scrolls into view.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        intro-delay
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0
      </code>
    </td>
    
    <td>
      Wait before the intro, in ms. Stagger it across a row of cards.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        class
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      Extra classes for the root <code>
        <figure>
      </code>
      
      .
    </td>
  </tr>
</tbody>
</table>

### TiltedCardLayer

<table>
<thead>
  <tr>
    <th>
      Prop
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Default
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        depth
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        20
      </code>
    </td>
    
    <td>
      Parallax depth. Higher values float further; negative values sink behind.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        class
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      Extra classes for the layer.
    </td>
  </tr>
</tbody>
</table>

## Events

<table>
<thead>
  <tr>
    <th>
      Event
    </th>
    
    <th>
      Payload
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        flip
      </code>
    </td>
    
    <td>
      <code>
        flipped: boolean
      </code>
    </td>
    
    <td>
      Fired with the new side on every flip.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        update:flipped
      </code>
    </td>
    
    <td>
      <code>
        flipped: boolean
      </code>
    </td>
    
    <td>
      Enables <code>
        v-model:flipped
      </code>
      
       for a controlled card.
    </td>
  </tr>
</tbody>
</table>

## Slots

<table>
<thead>
  <tr>
    <th>
      Slot
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        default
      </code>
    </td>
    
    <td>
      Content on the face of the card, over the photo or instead of it.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        back
      </code>
    </td>
    
    <td>
      Back side of the card. Its presence makes the card flippable.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        overlay
      </code>
    </td>
    
    <td>
      Content lifted in front of the card (<code>
        display-overlay-content
      </code>
      
      ).
    </td>
  </tr>
</tbody>
</table>

Tune the look per theme with CSS variables on the root: `--tc-shadow-color`, `--tc-rim-color` and `--tc-rim-light`.

## Accessibility

- When the card can flip or has an `on-click` handler, it becomes a focusable `role="button"` with `aria-pressed` for the flip state; Enter and Space activate it.
- Keyboard focus lifts the card and draws a visible ring on its rim. The hidden face gets `aria-hidden`.
- With `prefers-reduced-motion: reduce`, the tilt drops to 30%, the intro and idle drift are skipped, and the caption no longer swings.
- A drag that moves more than 6px does not count as a click, so scrolling on touch never flips the card.
- Ported from [React Bits](https://reactbits.dev/components/tilted-card) by David Haz.
