# Vue image gallery component

> Native Vue 3 image gallery and lightbox component, v-model, scoped slots and Teleport, with animated thumbnails, pinch zoom and Nuxt SSR support.

@lightgallery/vue is a native Vue 3 image gallery: every node is rendered by Vue, with `v-model` for the open state and the index, typed emits, scoped slots and a Teleport overlay. Animated thumbnails, pinch and double-tap zoom, captions, rotate, fullscreen and sharing come as tree-shakable plugins. The full component API is in the [Vue package docs](https://www.lightgalleryjs.com/docs/vue/).

Canonical page: https://www.lightgalleryjs.com/demos/vue-image-gallery/

#### Demo

Click any thumbnail. The Vue component renders the same markup and runs the
same gallery logic, the shared headless core guarantees identical behavior
across all four packages.

_Live demo: https://www.lightgalleryjs.com/demos/vue-image-gallery/_

##### Install

```bash
npm install @lightgallery/vue lightgallery
```

Peer range is `vue >=3.4` (the component uses `defineModel`). The CSS ships
from the `lightgallery` package and is shared by every binding, import it
globally, in `main.ts` or your root stylesheet:

```ts
import 'lightgallery/css/lightgallery.css';
import 'lightgallery/css/lg-thumbnail.css';
import 'lightgallery/css/lg-zoom.css';
```

##### Thumbnail grid

Wrap each trigger in ``. Mount order is slide order, and clicking a
trigger opens the lightbox on that slide:

```vue
<script setup lang="ts">
import { LightGallery, LgItem, type LgGalleryItem } from '@lightgallery/vue';
import Thumbnail from '@lightgallery/vue/plugins/thumbnail';
import Zoom from '@lightgallery/vue/plugins/zoom';

const plugins = [Thumbnail, Zoom];

const items: LgGalleryItem[] = [
    {
        src: 'img/1-1600.jpg',
        thumb: 'img/1-240.jpg',
        alt: 'Mountains',
        // enables the zoom-from-origin open animation
        lgSize: '1600-1067',
        caption: 'Mountains',
    },
    {
        src: 'img/2-1600.jpg',
        thumb: 'img/2-240.jpg',
        alt: 'Forest path',
        lgSize: '1600-2400',
        caption: 'Forest path',
    },
];
</script>

<template>
    <LightGallery :plugins="plugins" :zoom="{ scale: 1.5 }">
        <LgItem v-for="item of items" :key="item.src" :item="item">
            <img :src="item.thumb" :alt="item.alt" />
        </LgItem>
    </LightGallery>
</template>
```

##### Settings, plugins and events

Core settings are same-named props (`:mode`, `:speed`, `:loop`,
`:caption-position`, …). Each plugin adds one settings prop named after it,
and events are kebab-case emits with no `on` prefix:

```vue
<LightGallery
    :plugins="[Thumbnail, Zoom]"
    :speed="400"
    :thumbnail="{ animateThumb: true, thumbWidth: 130 }"
    :zoom="{ scale: 1.5, actualSize: true }"
    @after-slide="({ index }) => console.log(index)"
/>
```

Adding a plugin to `:plugins` is what enables it, a bare `:thumbnail` prop
only configures one that is already loaded.

##### Controlled and imperative

`v-model:open` and `v-model:index` hand you the state; a template ref
exposes the same actions the other packages have:

```vue
<script setup>
import { ref } from 'vue';
import { LightGallery } from '@lightgallery/vue';

const open = ref(false);
const index = ref(0);
const lg = ref();
</script>

<template>
    <button @click="open = true">Open gallery</button>
    <button @click="lg.openGallery(2)">Open at slide 3</button>

    <LightGallery
        ref="lg"
        :slides="items"
        v-model:open="open"
        v-model:index="index"
    />
</template>
```

The handle covers `openGallery(index?)`, `closeGallery()`, `goToSlide(i)`,
`nextSlide()`, `prevSlide()` and `refresh()`.

##### Slots

Captions, the counter and the navigation buttons are named scoped slots, `#caption`, `#counter`, `#prev-button`, `#next-button`, so you render them
with your own components instead of HTML strings:

```vue
<LightGallery :slides="items">
    <template #caption="{ item }">
        <h4>{{ item.alt }}</h4>
    </template>
</LightGallery>
```

##### Nuxt and SSR

Every entry imports without browser globals, and the overlay never
server-renders, the `` mounts on the client, so there is no
hydration mismatch and no `` wrapper needed. Your trigger markup
still server-renders, so the grid stays crawlable. Import the CSS through
`nuxt.config`:

```ts
export default defineNuxtConfig({
    css: ['lightgallery/css/lightgallery.css'],
});
```

More in the [Vue package docs](/docs/vue/), the full prop surface, all 14
plugins plus the justified grid, and the accessibility behavior. See also the
[Vue video gallery](/demos/vue-video-gallery/) demo.
