---
title: Gallery
description: A showcase wall with collections, three layouts, and artwork details.
category: TemplateContent
status: beta
since: 0.6.0
tags: [template, gallery, media, upload]
syncStatus: reviewed
verified: false
---

## Usage

### Showcase wall
:::TuffDemoWrapper{demo="TemplateGalleryDemo" code-lang="vue" description="Switch collections, thumbnail size, and layout; open a piece for details. Artwork is generated in code."}
---
code: |
  <script setup lang="ts">
  import { ref } from 'vue'
  import TemplateFrame from './TemplateFrame.vue'

  // TxStagger reads `appear` only when it mounts: a new key replays the
  // entrance. It first plays when the stage scrolls into view, never under
  // reduced motion.
  const entranceKey = ref(0)
  const playAppear = ref(false)
  function play() {
    playAppear.value = !matchMedia('(prefers-reduced-motion: reduce)').matches
    entranceKey.value += 1
  }
  </script>

  <template>
    <TemplateFrame title="Gallery" :height="580" @enter="play">
      <TxFilterChips v-model="collection" :items="collections" role="tablist" aria-label="Collections" />

      <section class="wall" :style="{ '--thumb-min': `${thumbMin}px` }">
        <!-- The glass floats over the scroller; sorting uses a dropdown that teleports, so the glass cannot clip it -->
        <TxGlassSurface width="100%" :height="46" :border-radius="14" :background-opacity="0.62">
          <TxSegmentedSlider v-model="size" :segments="sizes" :show-labels="false" />
          <TxFlatRadio v-model="layout" size="sm">
            <TxFlatRadioItem value="grid" icon="i-carbon-grid" aria-label="Grid" />
            <TxFlatRadioItem value="masonry" icon="i-carbon-column" aria-label="Masonry" />
            <TxFlatRadioItem value="list" icon="i-carbon-list" aria-label="List" />
          </TxFlatRadio>
          <TxDropdownMenu>…</TxDropdownMenu>
        </TxGlassSurface>

        <TxEdgeFadeMask axis="vertical">
          <TxStagger :key="entranceKey" tag="ul" :appear="playAppear">
            <li v-for="art in visible" :key="art.id">
              <button type="button" :aria-label="art.title" @click="open(art)">
                <img :src="art.url" alt="">
              </button>
              <TxIconButton
                :icon="art.liked ? 'i-carbon-favorite-filled' : 'i-carbon-favorite'"
                :pressed="art.liked"
                :label="`Favourite ${art.title}`"
                @click="toggleLike(art)"
              />
            </li>
          </TxStagger>
        </TxEdgeFadeMask>
      </section>

      <TxModal v-model="detailOpen" :title="current.title" width="min(960px, calc(100vw - 48px))">
        <TxRating v-model="current.myRating" :star-label="n => `Rate ${n} stars`" />
        <TxImageGallery :items="variants" preview-title="Variant preview" />
      </TxModal>
    </TemplateFrame>
  </template>

  <style scoped>
  .wall :deep(.tx-stagger) {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(var(--thumb-min), 1fr));
  }

  @container template (max-width: 639px) {
    .wall :deep(.tx-stagger) { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  }
  </style>
---
:::

## Overview

The Tuff community showcase, where wallpapers, CoreBox themes, and plugin covers are browsed, favorited, and rated on one wall.

- Every artwork is an SVG generated in code, plus three JPGs shipped with the site; nothing is fetched from outside.
- The staggered entrance plays once in view and replays after a change of collection, layout, sort order, or Favorites only; reduced motion skips it, along with hover zoom.
- Thumbnail size sets the column width of both grid and masonry through the CSS variable `--thumb-min`; below 640px the wall is two columns.
- In the details, ← / → step between pieces inside the dialog only; ratings and Set as wallpaper report inside the dialog.
- Uploads are previewed on this page only; the template owns each file's object URL and revokes it on reset and unmount.

## Customization

- Real artworks need only a `url`, an orientation, and a palette; serve small server-cut thumbnails and load the large image in the details.
- For a card that flips open into its details, swap in TxFlipOverlay, using the version that teleports to body (rendered in place inside a `content-visibility` container, it lands in the wrong spot).
- When favorites and ratings go through an API, update the counts optimistically, then roll back on failure and say why.
- Masonry is CSS multi-column and reads down each column first; use the grid when items must read row by row in time order.

## Related components

| Region | Components | Role |
|---|---|---|
| Header | [TxBadge](/docs/dev/components/badge), [TxAvatarGroup](/docs/dev/components/avatar), [TxIconButton](/docs/dev/components/button), [TxTooltip](/docs/dev/components/tooltip), [TxPopover](/docs/dev/components/popover), [TxImageUploader](/docs/dev/components/image-uploader) | Count, favorites filter, upload |
| Collections | [TxFilterChips](/docs/dev/components/filter-chips) | Collection tabs with counts |
| Toolbar | [TxGlassSurface](/docs/dev/components/glass-surface), [TxSegmentedSlider](/docs/dev/components/segmented-slider), [TxFlatRadio](/docs/dev/components/flat-radio), [TxDropdownMenu](/docs/dev/components/dropdown-menu) | Thumbnail size, layout, sorting |
| Wall | [TxEdgeFadeMask](/docs/dev/components/edge-fade-mask), [TxStagger](/docs/dev/components/stagger), [TxRating](/docs/dev/components/rating), [TxAvatar](/docs/dev/components/avatar), [TxTag](/docs/dev/components/tag) | Edge fades, entrance, card details |
| Empty state | [TxEmptyState](/docs/dev/components/empty-state) | No favorites yet |
| Feedback | [TxToastPanel](/docs/dev/components/toast-panel) | Favorite and upload results |
| Artwork details | [TxModal](/docs/dev/components/modal), [TxRating](/docs/dev/components/rating), [TxButton](/docs/dev/components/button), [TxImageGallery](/docs/dev/components/image-gallery), [TxToastPanel](/docs/dev/components/toast-panel) | Large view, rating, palette, variants |
| Collection panel (expanded) | [TxAvatarGroup](/docs/dev/components/avatar), [TxTag](/docs/dev/components/tag), [TxImageUploader](/docs/dev/components/image-uploader) | Blurb, tags, and upload area |
