# Circular Gallery

> A curved WebGL image gallery you can drag, scroll or tab through, with cards that open to the full image.

Cards sit on a circular arc and ripple while the gallery moves. Drag, scroll or use the arrow keys to move through them; it settles on the nearest card. Click a card to open it to the full image, then click it again, click outside, press Escape or use the close button to return.

<demo-circular-gallery>



</demo-circular-gallery>

## Installation

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

## Usage

```vue
<script setup lang="ts">
  import CircularGallery from '~/components/ui/circular-gallery/CircularGallery.vue';
  import type { GalleryItem } from '~/components/ui/circular-gallery/types';

  const items: GalleryItem[] = [
    { image: 'https://picsum.photos/seed/1/800/600', text: 'Bridge' },
    { image: 'https://picsum.photos/seed/2/800/600', text: 'Desk Setup' },
    {
      image: 'https://picsum.photos/seed/3/800/600',
      text: 'Waterfall',
      description: 'Shown under the title when the card is open.',
    },
  ];
</script>

<template>
  <div class="relative h-125 w-full">
    <CircularGallery
      :items="items"
      :bend="3"
      text-color="#ffffff"
      :border-radius="0.05"
      :auto-scroll="0"
    />
  </div>
</template>
```

The gallery fills its parent, so give the parent a height. It renders with raw WebGL2 (no extra dependencies): one shader draws each card as a signed-distance rounded rectangle with a soft shadow, so edges stay crisp at any size.

## 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>
        GalleryItem[]
      </code>
    </td>
    
    <td>
      12 sample photos
    </td>
    
    <td>
      Images to show. <code>
        text
      </code>
      
       is the title, <code>
        description
      </code>
      
       appears under it when open, <code>
        alt
      </code>
      
       names the image.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        bend
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        3
      </code>
    </td>
    
    <td>
      Curvature of the arc. Positive arches the cards down at the edges, negative lifts them, <code>
        0
      </code>
      
       lays them flat.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        item-size
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1
      </code>
    </td>
    
    <td>
      Card height relative to the container, from 0.2 to 2.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        item-aspect
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.78
      </code>
    </td>
    
    <td>
      Card width divided by height. Images are cropped to fill the card.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        gap
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.26
      </code>
    </td>
    
    <td>
      Space between cards as a fraction of the card width.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        border-radius
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.05
      </code>
    </td>
    
    <td>
      Corner radius as a fraction of the card size, from 0 (square) to 0.5 (fully round).
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        wave
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1
      </code>
    </td>
    
    <td>
      How much the cards ripple while the gallery moves. <code>
        0
      </code>
      
       keeps them flat.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        fade-edges
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.12
      </code>
    </td>
    
    <td>
      Fades the cards out toward the left and right edges, from 0 to 0.5 of the width.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        shadow
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.25
      </code>
    </td>
    
    <td>
      Strength of the soft shadow under each card.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        text-color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        '#ffffff'
      </code>
    </td>
    
    <td>
      Color of the titles, the keyboard focus ring and the placeholder shown while images load.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        font
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        'bold 30px Figtree'
      </code>
    </td>
    
    <td>
      CSS font shorthand for the titles. 30px is the default size; larger values make the titles bigger.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        font-url
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        undefined
      </code>
    </td>
    
    <td>
      Stylesheet URL (e.g. Google Fonts) or font file (.woff2, .woff, .ttf, .otf). The loaded family replaces the one in <code>
        font
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        show-titles
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Shows the title of each card underneath it.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        scroll-speed
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        2
      </code>
    </td>
    
    <td>
      How far the gallery travels per drag or wheel movement.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        scroll-ease
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.05
      </code>
    </td>
    
    <td>
      How quickly the gallery catches up with the input, from 0.001 (floaty) to 1 (instant).
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        snap
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Settles on the nearest card after dragging or scrolling.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        auto-scroll
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0
      </code>
    </td>
    
    <td>
      Automatic drift speed; 1 moves about one card every four seconds, negative drifts the other way. Pauses on input.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        grayscale
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Shows the images in grayscale and brings the color back on hover, focus or when opened.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        open-on-click
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Clicking a card opens it to its full image. When off, clicking a card centers it.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        intro
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Glides the cards in when the gallery mounts.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        dpr
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      device ratio
    </td>
    
    <td>
      Maximum device pixel ratio, capped at 2.
    </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>

## Events

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

<tbody>
  <tr>
    <td>
      <code>
        item-click
      </code>
    </td>
    
    <td>
      <code>
        (item: GalleryItem, index: number)
      </code>
    </td>
    
    <td>
      A card was clicked.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        active-change
      </code>
    </td>
    
    <td>
      <code>
        (index: number)
      </code>
    </td>
    
    <td>
      A different card became the centered one.
    </td>
  </tr>
</tbody>
</table>

## Accessibility

- The container is a `region` with `aria-roledescription="carousel"`. A visually hidden list holds one button per image with a roving `tabindex`, so Tab reaches the gallery once and the arrow keys move between cards. Home and End jump to the first and last card.
- Keyboard focus draws a ring around the centered card. Enter opens it; Escape closes it. A live region announces the opened image.
- With `prefers-reduced-motion: reduce`, the intro, wave, auto-scroll and easing are turned off and moves happen instantly.
- Ported from [React Bits](https://reactbits.dev/components/circular-gallery) by David Haz.
