---
title: "Gallery"
description: "Swiper-based galleries: Gallery, GalleryContent, GalleryWrapper, GalleryItem."
requested_language: hu
language: en
translation_notice: "This page isn't translated yet"
url: https://docs.systhema.app/hu/next/components/gallery
version: unreleased (main)
docs_index: https://docs.systhema.app/hu/next/llms.txt
---
> This page isn't translated yet. Showing English.


`Gallery` is a full-width section with an optional intro and a horizontally scrolling row of images. Slides keep their own width at a fixed height, so mixed portrait and landscape images sit side by side. Below the row, a counter and two buttons navigate it. The slider is [Swiper](https://swiperjs.com/), loaded only on pages that have a gallery.

```tsx preview iframe bleed height=640 title="Gallery"
import {
  Gallery,
  GalleryContent,
  GalleryItem,
  GalleryWrapper,
  Heading,
  Image,
  Paragraph,
} from '@systhemaui/next'

const slides = [
  { src: '/demo-assets/coast.webp', width: 1800, height: 1200, alt: 'A rocky coastline at dusk' },
  { src: '/demo-assets/dunes-portrait.webp', width: 1200, height: 1600, alt: 'Sand dunes' },
  { src: '/demo-assets/workspace.webp', width: 1800, height: 1200, alt: 'A tidy desk' },
  { src: '/demo-assets/forest-portrait.webp', width: 1200, height: 1600, alt: 'A pine forest' },
  { src: '/demo-assets/mountains.webp', width: 1920, height: 1080, alt: 'Mountain ridges' },
]

export default function Demo() {
  return (
    <Gallery>
      <GalleryContent>
        <Heading.h2>Field notes</Heading.h2>
        <Paragraph type="lead">Photos from the spring campaign. Drag, scroll or use the arrows.</Paragraph>
      </GalleryContent>
      <GalleryWrapper>
        {slides.map((slide) => (
          <GalleryItem key={slide.src}>
            <Image src={slide.src} alt={slide.alt} width={slide.width} height={slide.height} sizes="(min-width: 768px) 40vw, 80vw" />
          </GalleryItem>
        ))}
      </GalleryWrapper>
    </Gallery>
  )
}
```

## Import

```tsx
import { Gallery, GalleryContent, GalleryItem, GalleryWrapper } from '@systhemaui/next'
```

In a React app without Next.js, import them from `@systhemaui/react`. The API is the same.

## Parts

| Part             | Renders                                                                                               |
| ---------------- | ----------------------------------------------------------------------------------------------------- |
| `Gallery`        | a `<section class="py-section gallery">` with optional `theme` and `layoutBackground`                 |
| `GalleryContent` | the intro: a grid container with a `richtext` column, inset by `padding` grid columns (0 to 6)        |
| `GalleryWrapper` | the slider on the same grid, inset by `padding`, with the counter and the navigation buttons under it |
| `GalleryItem`    | one slide; its child is set to the slide height with an automatic width                               |

The slider runs in free mode on small screens and snaps to slides from the `md` breakpoint. It answers the keyboard (arrow keys while it is in the viewport), horizontal mouse-wheel and trackpad gestures, and dragging. Slide height, slide gap per breakpoint and every navigation color come from the `gallery` tokens.

## Examples

### Theme and background

`theme` switches the section to a color-system mode (`default` or `dark` with the default tokens), and `layoutBackground` paints it with a layout background (`main` or `alternative`). See [Themes](https://docs.systhema.app/hu/next/concepts/themes.md).

```tsx preview iframe bleed height=560 title="Dark theme"
import { Gallery, GalleryContent, GalleryItem, GalleryWrapper, Heading, Image } from '@systhemaui/next'

const slides = [
  { src: '/demo-assets/city.webp', width: 1920, height: 1080 },
  { src: '/demo-assets/coast.webp', width: 1800, height: 1200 },
  { src: '/demo-assets/workspace.webp', width: 1800, height: 1200 },
  { src: '/demo-assets/mountains.webp', width: 1920, height: 1080 },
]

export default function Demo() {
  return (
    <Gallery theme="dark" layoutBackground="main">
      <GalleryContent>
        <Heading.h2>After dark</Heading.h2>
      </GalleryContent>
      <GalleryWrapper>
        {slides.map((slide) => (
          <GalleryItem key={slide.src}>
            <Image src={slide.src} alt="" width={slide.width} height={slide.height} sizes="50vw" />
          </GalleryItem>
        ))}
      </GalleryWrapper>
    </Gallery>
  )
}
```

### Inset from the edges

`padding` on `GalleryContent` and `GalleryWrapper` insets each from both sides by that many grid columns (`col-mx-0` to `col-mx-6`). Use the same value on both to align the intro with the slides.

```tsx preview iframe bleed height=560 title="padding={2}"
import {
  Gallery,
  GalleryContent,
  GalleryItem,
  GalleryWrapper,
  Heading,
  Image,
} from '@systhemaui/next'

export default function Demo() {
  return (
    <Gallery layoutBackground="alternative">
      <GalleryContent padding={2}>
        <Heading.h3>Inset by two columns</Heading.h3>
      </GalleryContent>
      <GalleryWrapper padding={2}>
        <GalleryItem>
          <Image src="/demo-assets/abstract-square.webp" alt="" width={1200} height={1200} sizes="40vw" />
        </GalleryItem>
        <GalleryItem>
          <Image src="/demo-assets/forest-portrait.webp" alt="" width={1200} height={1600} sizes="40vw" />
        </GalleryItem>
        <GalleryItem>
          <Image src="/demo-assets/coast.webp" alt="" width={1800} height={1200} sizes="40vw" />
        </GalleryItem>
      </GalleryWrapper>
    </Gallery>
  )
}
```

### Translated button labels

The navigation buttons are labelled "Previous slide" and "Next slide". Pass `labels` to `GalleryWrapper` to translate them.

```tsx
import { GalleryItem, GalleryWrapper, Image } from '@systhemaui/next'

export function GalerieFotos({ photos }: { photos: { src: string; alt: string }[] }) {
  return (
    <GalleryWrapper labels={{ previousSlide: 'Vorheriges Bild', nextSlide: 'Nächstes Bild' }}>
      {photos.map((photo) => (
        <GalleryItem key={photo.src}>
          <Image src={photo.src} alt={photo.alt} width={1800} height={1200} />
        </GalleryItem>
      ))}
    </GalleryWrapper>
  )
}
```

`GalleryWrapper` also takes `className`, which is added to every slide, and `disableAnimation`, which drops the configured `animationClasses`.

## Loading

`GalleryWrapper` is a client component, and Swiper (about 120 KB) sits behind a lazy import in its own chunk. Until the chunk arrives, the slides render as a static row with the same `.swiper` markup and layout, and the server render already contains the real markup. See [Swiper is loaded on demand](https://docs.systhema.app/hu/next/concepts/client-and-server.md#swiper-is-loaded-on-demand).

## Props

### `Gallery`

`Gallery` takes every `<section>` attribute.

<!-- generated:props @systhemaui/react GalleryProps -->

| Prop                            | Type                                       | Default | Description |
| ------------------------------- | ------------------------------------------ | ------- | ----------- |
| `theme`                         | `ColorSystem` (`dark`, `default`)          | -       |             |
| `layoutBackground`              | `LayoutBackground` (`main`, `alternative`) | -       |             |
| …and all `<section>` attributes |                                            |         |             |

<!-- /generated -->

### `GalleryContent`

`GalleryContent` takes every `<div>` attribute; `padding` defaults to `0`.

<!-- generated:props @systhemaui/react GalleryContentProps -->

| Prop                        | Type     | Default | Description |
| --------------------------- | -------- | ------- | ----------- |
| `padding`                   | `number` | `0`     |             |
| …and all `<div>` attributes |          |         |             |

<!-- /generated -->

## HTML and CSS

The section and its navigation are plain classes. The slider itself needs Swiper (or the markup below, which renders as a static row):

```html
<section class="py-section gallery bg-layout-alternative">
  <div class="grid-default container">
    <div class="richtext col-mx-0">
      <h2 class="text-h2 color-heading">Field notes</h2>
    </div>
  </div>
  <div class="grid-default container">
    <div class="gallery-wrapper col-mx-0">
      <div class="swiper" id="gallery-1-swiper">
        <div class="swiper-wrapper">
          <div class="swiper-slide"><img class="media" src="/coast.webp" alt="" /></div>
          <div class="swiper-slide"><img class="media" src="/dunes.webp" alt="" /></div>
        </div>
      </div>
      <div class="gallery-navigation">
        <div class="gallery-navigation-pagination" role="status">1 / 2</div>
        <div class="gallery-navigation-buttons">
          <button class="gallery-navigation-button-prev" aria-controls="gallery-1-swiper">
            <span class="gallery-navigation-button-icon gallery-navigation-button-icon-prev"></span>
            <span class="sr-only!">Previous slide</span>
          </button>
          <button class="gallery-navigation-button-next" aria-controls="gallery-1-swiper">
            <span class="sr-only!">Next slide</span>
            <span class="gallery-navigation-button-icon gallery-navigation-button-icon-next"></span>
          </button>
        </div>
      </div>
    </div>
  </div>
</section>
```

Empty icon spans draw the default arrows; change them with `blocks.gallery.prevIcon` and `blocks.gallery.nextIcon` (see [Component CSS blocks](https://docs.systhema.app/hu/next/styling/blocks.md)).

<!-- generated:utilities gallery -->

| Class                                 | Styles                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `gallery`                             | `& { overflow: clip; width: 100%; display: flex; flex-direction: column; row-gap: var(--gallery-space-y, 48px); }`<br>`& .gallery-wrapper { user-select: none; display: grid; row-gap: var(--gallery-wrapper-gap-y, 16px); }`<br>`.gallery .gallery-wrapper { .swiper-slide { > * { width: auto; height: var(--gallery-slide-height, 300px); } } }`<br>+ 12 more rules                                                                                                                                               |
| `gallery-navigation-pagination`       | `.posts-list-pagination .gallery-navigation-pagination { font-family: var(--font-gallery-navigation-pagination-font-family); font-style: var(--font-gallery-navigation-pagination-font-style); font-weight: var(--font-gallery-navigation-pagination-font-weight); font-size: var(--gallery-navigation-pagination-font-size, 14px); line-height: var(--gallery-navigation-pagination-line-height, 100%); letter-spacing: var(--gallery-navigation-pagination-letter-spacing, 0px); color: var(--…`                   |
| `gallery-navigation-button-prev`      | `.posts-list-pagination .gallery-navigation-button-prev { --tw-shadow: var(--tw-shadow-color); --gallery-navigation-button-shadow-x: var(--gallery-navigation-button-shadow-x, 0px); --gallery-navigation-button-shadow-y: var(--gallery-navigation-button-shadow-y, 0px); --gallery-navigation-button-shadow-blur: var(--gallery-navigation-button-shadow-blur, 0px); --gallery-navigation-button-shadow-spread: var(--gallery-navigation-button-shadow-spread, 0px); --tw-shadow-color: var(--…`<br>+ 2 more rules |
| `gallery-navigation-button-next`      | `.posts-list-pagination .gallery-navigation-button-next { --tw-shadow: var(--tw-shadow-color); --gallery-navigation-button-shadow-x: var(--gallery-navigation-button-shadow-x, 0px); --gallery-navigation-button-shadow-y: var(--gallery-navigation-button-shadow-y, 0px); --gallery-navigation-button-shadow-blur: var(--gallery-navigation-button-shadow-blur, 0px); --gallery-navigation-button-shadow-spread: var(--gallery-navigation-button-shadow-spread, 0px); --tw-shadow-color: var(--…`<br>+ 2 more rules |
| `gallery-navigation-button-icon`      | `.posts-list-pagination .gallery-navigation-button-icon { display: inline-block; overflow: visible; object-fit: contain; object-position: center; flex-shrink: 0; flex-grow: 0; transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, box-shadow, opacity; width: var(--gallery-navigation-button-icon-size, 16px); height: var(--gallery-navigation-button-icon-size, 16px); color: var(--color-gallery-navigation-button-normal-icon, inherit); }`<br>+ 1 more rule     |
| `gallery-navigation-button-icon-prev` | `.posts-list-pagination span.gallery-navigation-button-icon-prev:empty { display: inline-block; mask-image: url("data:image/svg+xml,…"); mask-size: var(--gallery-navigation-button-icon-size, 16px) var(--gallery-navigation-button-icon-size, 16px); mask-repeat: no-repeat; mask-position: center; background-color: var(--color-gallery-navigation-button-normal-icon, inherit); transition-property: background-color; transition-duration: inherit; transition-timing-function: inherit; }`<br>+ 1 more rule   |
| `gallery-navigation-button-icon-next` | `.posts-list-pagination span.gallery-navigation-button-icon-next:empty { display: inline-block; mask-image: url("data:image/svg+xml,…"); mask-size: var(--gallery-navigation-button-icon-size, 16px) var(--gallery-navigation-button-icon-size, 16px); mask-repeat: no-repeat; mask-position: center; background-color: var(--color-gallery-navigation-button-normal-icon, inherit); transition-property: background-color; transition-duration: inherit; transition-timing-function: inherit; }`<br>+ 1 more rule   |

<!-- /generated -->

## Next.js

`@systhemaui/next` re-exports the gallery components from `@systhemaui/react` unchanged. Use the Next `Image` inside `GalleryItem` for optimized slides, and pass a `sizes` value that matches the slide width.

## Accessibility

- The `{current} / {total}` counter is a `role="status"` region and the gallery's only slide announcement; Swiper's own live messages are switched off so nothing is announced twice. It counts images: `total` is the number of `GalleryItem`s and `current` is the left-most image on screen. Because several images are visible at once, the counter can stop short of `N / N` when the last images all fit; the disabled next button marks the end.
- The navigation buttons are icon-only, each with one visually hidden label. A disabled button is dimmed and shows a not-allowed cursor.
- Give every slide image a meaningful `alt`, or `alt=""` when the images are decorative.

See [Lists and counters](https://docs.systhema.app/hu/next/concepts/accessibility.md#lists-and-counters).

## Related

- [Carousel](https://docs.systhema.app/hu/next/components/carousel.md)
- [Gallery block](https://docs.systhema.app/hu/next/payload/blocks/gallery.md)
- [Image](https://docs.systhema.app/hu/next/components/image.md)
