# Infinite Menu

> A draggable WebGL sphere of image tiles that snaps the nearest tile to the front and shows its title, description and link.

A sphere of rounded image tiles drawn in one instanced WebGL2 call. Drag to spin it; when you let go it keeps turning, then snaps the nearest tile to the front and shows that item's title and description. Click another tile to bring it forward, click the front tile (or its arrow button) to open it, or focus the menu and use the arrow keys and Enter. It steps to the next tile on its own every few seconds and pauses while you hover or focus it.

<demo-infinite-menu>



</demo-infinite-menu>

## Installation

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

## Usage

```vue
<script setup lang="ts">
  import InfiniteMenu from '~/components/ui/infinite-menu/InfiniteMenu.vue';
  import type { MenuItem } from '~/components/ui/infinite-menu/types';

  const items: MenuItem[] = [
    {
      image:
        'https://images.unsplash.com/photo-1505118380757-91f5f5632de0?w=600',
      link: 'https://nxui.geoql.in',
      title: 'Shoreline',
      description: 'Surf folding onto pale sand, seen straight from above.',
    },
    {
      image:
        'https://images.unsplash.com/photo-1419242902214-272b3f66ee7a?w=600',
      title: 'Night Sky',
      description: 'Stars over a violet horizon long after dusk.',
    },
  ];

  function onActiveChange(item: MenuItem, index: number) {
    console.log(index, item.title);
  }
</script>

<template>
  <div class="relative h-140 w-full">
    <InfiniteMenu
      :items="items"
      :count="160"
      :tile-size="0.63"
      :autoplay="3"
      theme="dark"
      @active-change="onActiveChange"
    />
  </div>
</template>
```

Items repeat around the sphere when there are fewer items than tiles; neighbouring tiles never show the same image. Images are drawn into one texture atlas, so they must be served with CORS headers.

## 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>
        MenuItem[]
      </code>
    </td>
    
    <td>
      <code>
        []
      </code>
    </td>
    
    <td>
      Items to show. They repeat around the sphere when there are fewer items than tiles.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        count
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        160
      </code>
    </td>
    
    <td>
      Number of tiles on the sphere (6–400).
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        tile-size
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.63
      </code>
    </td>
    
    <td>
      Tile size relative to the space around it. Lower values leave wider gaps.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        roundness
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.2
      </code>
    </td>
    
    <td>
      Tile shape, from 0 for squares to 1 for circles.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        zoom
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1.65
      </code>
    </td>
    
    <td>
      How close the camera sits to the active tile when the sphere is at rest.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        pull-back
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1
      </code>
    </td>
    
    <td>
      How far the camera pulls back while you drag. 0 keeps it still.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        stretch
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.6
      </code>
    </td>
    
    <td>
      How much the tiles stretch with fast motion.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        inertia
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.6
      </code>
    </td>
    
    <td>
      How long the sphere keeps spinning after you let go, from 0 to 1.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        autoplay
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        3
      </code>
    </td>
    
    <td>
      Seconds between automatic steps to the next tile. 0 turns it off. Pauses on hover and focus.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        grayscale
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Shows every tile except the active one in grayscale.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        dim
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.5
      </code>
    </td>
    
    <td>
      Fades the tiles around the active one toward the background once the sphere settles (0–1).
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        intro
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Spins the sphere into place when it loads.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        show-info
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Shows the title, description and link button of the active item.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        theme
      </code>
    </td>
    
    <td>
      <code>
        'dark' | 'light'
      </code>
    </td>
    
    <td>
      <code>
        'dark'
      </code>
    </td>
    
    <td>
      Colors of the text, the button and the placeholder tiles.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        accent-color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      —
    </td>
    
    <td>
      Background of the link button. Defaults to the theme's text color.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        background-color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      —
    </td>
    
    <td>
      Background behind the sphere. Transparent by default.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        on-item-click
      </code>
    </td>
    
    <td>
      <code>
        (item, index) => void
      </code>
    </td>
    
    <td>
      —
    </td>
    
    <td>
      Called when the active tile or its button is clicked, or Enter is pressed. Replaces opening the link. Bind it as <code>
        @item-click
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        class
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      Extra classes for the container.
    </td>
  </tr>
</tbody>
</table>

### MenuItem

<table>
<thead>
  <tr>
    <th>
      Field
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        image
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      Image URL (needs CORS headers).
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        title
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      Title shown beside the active tile.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        description
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      Short text shown on the other side.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        link
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      Opened by the button, a click on the front tile, or Enter.
    </td>
  </tr>
</tbody>
</table>

## Events

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

<tbody>
  <tr>
    <td>
      <code>
        active-change
      </code>
    </td>
    
    <td>
      <code>
        (item: MenuItem, index: number)
      </code>
    </td>
    
    <td>
      A new item settled in front.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        item-click
      </code>
    </td>
    
    <td>
      <code>
        (item: MenuItem, index: number)
      </code>
    </td>
    
    <td>
      The front tile, its button or Enter was used. Stops the link from opening.
    </td>
  </tr>
</tbody>
</table>

## Slots

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

<tbody>
  <tr>
    <td>
      <code>
        info
      </code>
    </td>
    
    <td>
      <code>
        { item, index, moving }
      </code>
    </td>
    
    <td>
      Replaces the default title, description and button with your own overlay.
    </td>
  </tr>
</tbody>
</table>

## Accessibility

- The container is focusable (`role="group"`, `aria-roledescription="carousel"`) and is labelled with the active item's title.
- Arrow keys step to the neighbouring tile in that direction; Enter opens the active item.
- Autoplay pauses while the menu is hovered or focused.
- With `prefers-reduced-motion: reduce` the intro spin, motion stretch and autoplay are off, and the tiles fade in at once.
- The renderer sleeps once the sphere settles and stops while the menu is off screen.
- Ported from [React Bits](https://reactbits.dev/components/infinite-menu) by David Haz.
