# Grid Motion

> A tilted wall of drifting tiles that slides with the pointer and lights up where it rests.

A rotated wall of photo or text tiles fills its container and drifts row by row. Moving the pointer across the page slides the rows against each other, and the tiles under the pointer come back to full brightness (and full color in grayscale mode) while the rest stay dimmed.

<demo-grid-motion>



</demo-grid-motion>

## Installation

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

## Usage

```vue
<script setup lang="ts">
  import GridMotion from '~/components/ui/grid-motion/GridMotion.vue';
  import type { GridMotionItem } from '~/components/ui/grid-motion/types';

  const items: GridMotionItem[] = [
    'https://images.unsplash.com/photo-1705032033999-efa3082e1a4e?w=640',
    'Motion',
    { src: '/work/poster.jpg', alt: 'Poster' },
  ];
</script>

<template>
  <div class="relative h-125 w-full">
    <GridMotion
      :items="items"
      :rows="4"
      :angle="-12"
      :tilt="0"
      :speed="24"
      direction="alternate"
      :spotlight="0.6"
      :dim="0.35"
      theme="dark"
    />
  </div>
</template>
```

The wall sizes itself from its container, so give the parent a height. Rows move with `transform` only and the spotlight writes one `--spot` custom property per tile, so there is no re-render while it animates. The loop pauses when the wall is offscreen.

## Props

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

<tbody>
  <tr>
    <td>
      <code>
        items
      </code>
    </td>
    
    <td>
      <code>
        Array<string | number | { src: string; alt?: string } | null>
      </code>
    </td>
    
    <td>
      <code>
        []
      </code>
    </td>
    
    <td>
      Tile content. Image URLs and paths render as photos, other values as text. Items repeat to fill the wall.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        rows
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        4
      </code>
    </td>
    
    <td>
      Rows of tiles (1–12). More rows means smaller tiles.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        aspect-ratio
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1.33
      </code>
    </td>
    
    <td>
      Width of each tile divided by its height. Below 1 gives portrait tiles.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        gap
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        16
      </code>
    </td>
    
    <td>
      Space between tiles in pixels.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        radius
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        14
      </code>
    </td>
    
    <td>
      Corner radius of the tiles in pixels. Large values give pills.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        angle
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        -12
      </code>
    </td>
    
    <td>
      Rotation of the whole wall in degrees.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        tilt
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0
      </code>
    </td>
    
    <td>
      Leans the wall back in 3D, in degrees (0–75). 0 keeps it flat.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        speed
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        24
      </code>
    </td>
    
    <td>
      Drift speed of the rows in pixels per second. 0 keeps them still.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        direction
      </code>
    </td>
    
    <td>
      <code>
        'alternate' | 'left' | 'right'
      </code>
    </td>
    
    <td>
      <code>
        'alternate'
      </code>
    </td>
    
    <td>
      Which way the rows drift. Alternate sends neighbouring rows in opposite directions.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        parallax
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.5
      </code>
    </td>
    
    <td>
      How far the rows slide against each other as the pointer moves across the page, from 0 to 1.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        spotlight
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.6
      </code>
    </td>
    
    <td>
      Size of the area around the pointer where tiles come back to full color, from 0 to 1. 0 turns it off.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        dim
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.35
      </code>
    </td>
    
    <td>
      Fades the tiles outside the spotlight, which keeps text on top readable. From 0 to 1.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        grayscale
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Shows the tiles in grayscale until the spotlight brings their color back.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        fade
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.5
      </code>
    </td>
    
    <td>
      Fades the edges of the wall into the page, from 0 to 1.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        tile-color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      theme
    </td>
    
    <td>
      Background of text and empty tiles.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        text-color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      theme
    </td>
    
    <td>
      Color of text tiles.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        theme
      </code>
    </td>
    
    <td>
      <code>
        'dark' | 'light'
      </code>
    </td>
    
    <td>
      <code>
        'dark'
      </code>
    </td>
    
    <td>
      Picks the default tile and text colors.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        class
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      Extra classes for the root.
    </td>
  </tr>
</tbody>
</table>

## Slots

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

<tbody>
  <tr>
    <td>
      <code>
        tile
      </code>
    </td>
    
    <td>
      <code>
        { tile: GridMotionTile }
      </code>
    </td>
    
    <td>
      Custom tile content. <code>
        tile.item
      </code>
      
       is the original item, <code>
        tile.kind
      </code>
      
       is <code>
        'image'
      </code>
      
      , <code>
        'text'
      </code>
      
       or <code>
        'empty'
      </code>
      
      .
    </td>
  </tr>
</tbody>
</table>

## Accessibility

- The wall is decorative and marked `aria-hidden`. Put real content above it.
- With `prefers-reduced-motion: reduce`, the rows stop drifting and the parallax is off; the spotlight still follows the pointer.
- Pointer input is read on the window, so content layered over the wall keeps its own clicks.
- Ported from [React Bits](https://reactbits.dev/backgrounds/grid-motion) by David Haz.
