Product gallery
Preview Available in release assets
Show product media without coupling image selection to variant, inventory, or price truth.
Product gallery primary example
<section class="thin-product-gallery" aria-labelledby="gallery-title">
<h2 id="gallery-title">Canvas tote images</h2>
<div class="thin-reel">
<figure data-media-item>
<a href="tote-front-large.jpg" data-lightbox-open="tote-lightbox" data-media-index="0">
<img src="tote-front.jpg" alt="Canvas tote, front view">
</a>
<figcaption>Front</figcaption>
</figure>
<figure data-media-item>
<a href="tote-detail-large.jpg" data-lightbox-open="tote-lightbox" data-media-index="1">
<img src="tote-detail.jpg" alt="Canvas tote stitching detail">
</a>
<figcaption>Stitching detail</figcaption>
</figure>
</div>
<output data-media-status>Image 1 of 2</output>
</section>Show code
<section class="thin-product-gallery" aria-labelledby="gallery-title">
<h2 id="gallery-title">Canvas tote images</h2>
<div class="thin-reel">
<figure data-media-item>
<a href="tote-front-large.jpg" data-lightbox-open="tote-lightbox" data-media-index="0">
<img src="tote-front.jpg" alt="Canvas tote, front view">
</a>
<figcaption>Front</figcaption>
</figure>
<figure data-media-item>
<a href="tote-detail-large.jpg" data-lightbox-open="tote-lightbox" data-media-index="1">
<img src="tote-detail.jpg" alt="Canvas tote stitching detail">
</a>
<figcaption>Stitching detail</figcaption>
</figure>
</div>
<output data-media-status>Image 1 of 2</output>
</section>Variant gallery
Each authored example uses the production assets and includes copyable markup.
Mixed illustration ratios
Front, back and stitching illustrations have different intrinsic ratios. Each remains a native link and the optional coordinator reports only the media index.
<section class="thin-product-gallery" aria-labelledby="product-mixed-title" style="overflow-wrap: anywhere">
<h2 id="product-mixed-title">Canvas tote illustrations</h2>
<p>Gallery navigation changes the media view only. Product price, availability and selected variant remain application-owned.</p>
<div class="thin-reel" tabindex="0" role="group" aria-label="Product illustrations">
<figure aria-current="true" data-media-item>
<a href="/examples/media/front.svg">
<img src="/examples/media/front.svg" alt="Canvas tote illustration, front" width="640" height="480">
</a>
<figcaption>View 1: Canvas tote illustration, front</figcaption>
<span data-image-fallback>Preview unavailable. <a href="/examples/media/front.svg">Open the original illustration</a>.</span>
</figure>
<figure data-media-item>
<a href="/examples/media/back.svg">
<img src="/examples/media/back.svg" alt="Canvas tote illustration, back" width="800" height="400">
</a>
<figcaption>View 2: Canvas tote illustration, back</figcaption>
<span data-image-fallback>Preview unavailable. <a href="/examples/media/back.svg">Open the original illustration</a>.</span>
</figure>
<figure data-media-item>
<a href="/examples/media/detail.svg">
<img src="/examples/media/detail.svg" alt="Canvas tote illustration, stitching detail" width="480" height="640">
</a>
<figcaption>View 3: Canvas tote illustration, stitching detail</figcaption>
<span data-image-fallback>Preview unavailable. <a href="/examples/media/detail.svg">Open the original illustration</a>.</span>
</figure>
</div>
<div data-media-controls>
<button class="thin-button" type="button" data-media-previous>Previous image</button>
<button class="thin-button" type="button" data-media-next>Next image</button>
<output data-media-status>Image 1</output>
</div>
</section>
<script type="module">import { enhanceMedia } from "/behaviors.js"; enhanceMedia(document);</script>Show code
<section class="thin-product-gallery" aria-labelledby="product-mixed-title" style="overflow-wrap: anywhere">
<h2 id="product-mixed-title">Canvas tote illustrations</h2>
<p>Gallery navigation changes the media view only. Product price, availability and selected variant remain application-owned.</p>
<div class="thin-reel" tabindex="0" role="group" aria-label="Product illustrations">
<figure aria-current="true" data-media-item>
<a href="/examples/media/front.svg">
<img src="/examples/media/front.svg" alt="Canvas tote illustration, front" width="640" height="480">
</a>
<figcaption>View 1: Canvas tote illustration, front</figcaption>
<span data-image-fallback>Preview unavailable. <a href="/examples/media/front.svg">Open the original illustration</a>.</span>
</figure>
<figure data-media-item>
<a href="/examples/media/back.svg">
<img src="/examples/media/back.svg" alt="Canvas tote illustration, back" width="800" height="400">
</a>
<figcaption>View 2: Canvas tote illustration, back</figcaption>
<span data-image-fallback>Preview unavailable. <a href="/examples/media/back.svg">Open the original illustration</a>.</span>
</figure>
<figure data-media-item>
<a href="/examples/media/detail.svg">
<img src="/examples/media/detail.svg" alt="Canvas tote illustration, stitching detail" width="480" height="640">
</a>
<figcaption>View 3: Canvas tote illustration, stitching detail</figcaption>
<span data-image-fallback>Preview unavailable. <a href="/examples/media/detail.svg">Open the original illustration</a>.</span>
</figure>
</div>
<div data-media-controls>
<button class="thin-button" type="button" data-media-previous>Previous image</button>
<button class="thin-button" type="button" data-media-next>Next image</button>
<output data-media-status>Image 1</output>
</div>
</section>
<script type="module">import { enhanceMedia } from "/behaviors.js"; enhanceMedia(document);</script>Long product gallery
Eight linked views keep their authored order and native scroll access. Repeated illustrations make the example self-contained without a remote image service.
<section class="thin-product-gallery" aria-labelledby="product-long-title" style="overflow-wrap: anywhere">
<h2 id="product-long-title">Canvas tote illustrations</h2>
<p>Gallery navigation changes the media view only. Product price, availability and selected variant remain application-owned.</p>
<div class="thin-reel" tabindex="0" role="group" aria-label="Product illustrations">
<figure aria-current="true" data-media-item>
<a href="/examples/media/front.svg">
<img src="/examples/media/front.svg" alt="Canvas tote illustration, front" width="640" height="480">
</a>
<figcaption>View 1: Canvas tote illustration, front</figcaption>
<span data-image-fallback>Preview unavailable. <a href="/examples/media/front.svg">Open the original illustration</a>.</span>
</figure>
<figure data-media-item>
<a href="/examples/media/back.svg">
<img src="/examples/media/back.svg" alt="Canvas tote illustration, back" width="800" height="400">
</a>
<figcaption>View 2: Canvas tote illustration, back</figcaption>
<span data-image-fallback>Preview unavailable. <a href="/examples/media/back.svg">Open the original illustration</a>.</span>
</figure>
<figure data-media-item>
<a href="/examples/media/detail.svg">
<img src="/examples/media/detail.svg" alt="Canvas tote illustration, stitching detail" width="480" height="640">
</a>
<figcaption>View 3: Canvas tote illustration, stitching detail</figcaption>
<span data-image-fallback>Preview unavailable. <a href="/examples/media/detail.svg">Open the original illustration</a>.</span>
</figure>
<figure data-media-item>
<a href="/examples/media/front.svg">
<img src="/examples/media/front.svg" alt="Canvas tote illustration, front" width="640" height="480">
</a>
<figcaption>View 4: Canvas tote illustration, front</figcaption>
<span data-image-fallback>Preview unavailable. <a href="/examples/media/front.svg">Open the original illustration</a>.</span>
</figure>
<figure data-media-item>
<a href="/examples/media/back.svg">
<img src="/examples/media/back.svg" alt="Canvas tote illustration, back" width="800" height="400">
</a>
<figcaption>View 5: Canvas tote illustration, back</figcaption>
<span data-image-fallback>Preview unavailable. <a href="/examples/media/back.svg">Open the original illustration</a>.</span>
</figure>
<figure data-media-item>
<a href="/examples/media/detail.svg">
<img src="/examples/media/detail.svg" alt="Canvas tote illustration, stitching detail" width="480" height="640">
</a>
<figcaption>View 6: Canvas tote illustration, stitching detail</figcaption>
<span data-image-fallback>Preview unavailable. <a href="/examples/media/detail.svg">Open the original illustration</a>.</span>
</figure>
<figure data-media-item>
<a href="/examples/media/front.svg">
<img src="/examples/media/front.svg" alt="Canvas tote illustration, front" width="640" height="480">
</a>
<figcaption>View 7: Canvas tote illustration, front</figcaption>
<span data-image-fallback>Preview unavailable. <a href="/examples/media/front.svg">Open the original illustration</a>.</span>
</figure>
<figure data-media-item>
<a href="/examples/media/back.svg">
<img src="/examples/media/back.svg" alt="Canvas tote illustration, back" width="800" height="400">
</a>
<figcaption>View 8: Canvas tote illustration, back</figcaption>
<span data-image-fallback>Preview unavailable. <a href="/examples/media/back.svg">Open the original illustration</a>.</span>
</figure>
</div>
<div data-media-controls>
<button class="thin-button" type="button" data-media-previous>Previous image</button>
<button class="thin-button" type="button" data-media-next>Next image</button>
<output data-media-status>Image 1</output>
</div>
</section>
<script type="module">import { enhanceMedia } from "/behaviors.js"; enhanceMedia(document);</script>Show code
<section class="thin-product-gallery" aria-labelledby="product-long-title" style="overflow-wrap: anywhere">
<h2 id="product-long-title">Canvas tote illustrations</h2>
<p>Gallery navigation changes the media view only. Product price, availability and selected variant remain application-owned.</p>
<div class="thin-reel" tabindex="0" role="group" aria-label="Product illustrations">
<figure aria-current="true" data-media-item>
<a href="/examples/media/front.svg">
<img src="/examples/media/front.svg" alt="Canvas tote illustration, front" width="640" height="480">
</a>
<figcaption>View 1: Canvas tote illustration, front</figcaption>
<span data-image-fallback>Preview unavailable. <a href="/examples/media/front.svg">Open the original illustration</a>.</span>
</figure>
<figure data-media-item>
<a href="/examples/media/back.svg">
<img src="/examples/media/back.svg" alt="Canvas tote illustration, back" width="800" height="400">
</a>
<figcaption>View 2: Canvas tote illustration, back</figcaption>
<span data-image-fallback>Preview unavailable. <a href="/examples/media/back.svg">Open the original illustration</a>.</span>
</figure>
<figure data-media-item>
<a href="/examples/media/detail.svg">
<img src="/examples/media/detail.svg" alt="Canvas tote illustration, stitching detail" width="480" height="640">
</a>
<figcaption>View 3: Canvas tote illustration, stitching detail</figcaption>
<span data-image-fallback>Preview unavailable. <a href="/examples/media/detail.svg">Open the original illustration</a>.</span>
</figure>
<figure data-media-item>
<a href="/examples/media/front.svg">
<img src="/examples/media/front.svg" alt="Canvas tote illustration, front" width="640" height="480">
</a>
<figcaption>View 4: Canvas tote illustration, front</figcaption>
<span data-image-fallback>Preview unavailable. <a href="/examples/media/front.svg">Open the original illustration</a>.</span>
</figure>
<figure data-media-item>
<a href="/examples/media/back.svg">
<img src="/examples/media/back.svg" alt="Canvas tote illustration, back" width="800" height="400">
</a>
<figcaption>View 5: Canvas tote illustration, back</figcaption>
<span data-image-fallback>Preview unavailable. <a href="/examples/media/back.svg">Open the original illustration</a>.</span>
</figure>
<figure data-media-item>
<a href="/examples/media/detail.svg">
<img src="/examples/media/detail.svg" alt="Canvas tote illustration, stitching detail" width="480" height="640">
</a>
<figcaption>View 6: Canvas tote illustration, stitching detail</figcaption>
<span data-image-fallback>Preview unavailable. <a href="/examples/media/detail.svg">Open the original illustration</a>.</span>
</figure>
<figure data-media-item>
<a href="/examples/media/front.svg">
<img src="/examples/media/front.svg" alt="Canvas tote illustration, front" width="640" height="480">
</a>
<figcaption>View 7: Canvas tote illustration, front</figcaption>
<span data-image-fallback>Preview unavailable. <a href="/examples/media/front.svg">Open the original illustration</a>.</span>
</figure>
<figure data-media-item>
<a href="/examples/media/back.svg">
<img src="/examples/media/back.svg" alt="Canvas tote illustration, back" width="800" height="400">
</a>
<figcaption>View 8: Canvas tote illustration, back</figcaption>
<span data-image-fallback>Preview unavailable. <a href="/examples/media/back.svg">Open the original illustration</a>.</span>
</figure>
</div>
<div data-media-controls>
<button class="thin-button" type="button" data-media-previous>Previous image</button>
<button class="thin-button" type="button" data-media-next>Next image</button>
<output data-media-status>Image 1</output>
</div>
</section>
<script type="module">import { enhanceMedia } from "/behaviors.js"; enhanceMedia(document);</script>Anatomy
- Product heading
- Linked media reel
- Captions
- Status
Variants and sizes
- Mixed ratio — shown above
- Long gallery — shown above
States and difficult content
Rendered fixtures exercise authored content and constrained layouts with the same production assets.
Server-authored current image
The second figure starts with aria-current=true. Explicit enhancement reads that view and maintains one current image without selecting a product or changing server state.
<section class="thin-product-gallery" aria-labelledby="product-selected-title" style="overflow-wrap: anywhere">
<h2 id="product-selected-title">Canvas tote illustrations</h2>
<p>Gallery navigation changes the media view only. Product price, availability and selected variant remain application-owned.</p>
<div class="thin-reel" tabindex="0" role="group" aria-label="Product illustrations">
<figure data-media-item>
<a href="/examples/media/front.svg">
<img src="/examples/media/front.svg" alt="Canvas tote illustration, front" width="640" height="480">
</a>
<figcaption>View 1: Canvas tote illustration, front</figcaption>
<span data-image-fallback>Preview unavailable. <a href="/examples/media/front.svg">Open the original illustration</a>.</span>
</figure>
<figure aria-current="true" data-media-item>
<a href="/examples/media/back.svg">
<img src="/examples/media/back.svg" alt="Canvas tote illustration, back" width="800" height="400">
</a>
<figcaption>View 2: Canvas tote illustration, back</figcaption>
<span data-image-fallback>Preview unavailable. <a href="/examples/media/back.svg">Open the original illustration</a>.</span>
</figure>
<figure data-media-item>
<a href="/examples/media/detail.svg">
<img src="/examples/media/detail.svg" alt="Canvas tote illustration, stitching detail" width="480" height="640">
</a>
<figcaption>View 3: Canvas tote illustration, stitching detail</figcaption>
<span data-image-fallback>Preview unavailable. <a href="/examples/media/detail.svg">Open the original illustration</a>.</span>
</figure>
</div>
<div data-media-controls>
<button class="thin-button" type="button" data-media-previous>Previous image</button>
<button class="thin-button" type="button" data-media-next>Next image</button>
<output data-media-status>Image 1</output>
</div>
</section>
<script type="module">import { enhanceMedia } from "/behaviors.js"; enhanceMedia(document);</script>Show code
<section class="thin-product-gallery" aria-labelledby="product-selected-title" style="overflow-wrap: anywhere">
<h2 id="product-selected-title">Canvas tote illustrations</h2>
<p>Gallery navigation changes the media view only. Product price, availability and selected variant remain application-owned.</p>
<div class="thin-reel" tabindex="0" role="group" aria-label="Product illustrations">
<figure data-media-item>
<a href="/examples/media/front.svg">
<img src="/examples/media/front.svg" alt="Canvas tote illustration, front" width="640" height="480">
</a>
<figcaption>View 1: Canvas tote illustration, front</figcaption>
<span data-image-fallback>Preview unavailable. <a href="/examples/media/front.svg">Open the original illustration</a>.</span>
</figure>
<figure aria-current="true" data-media-item>
<a href="/examples/media/back.svg">
<img src="/examples/media/back.svg" alt="Canvas tote illustration, back" width="800" height="400">
</a>
<figcaption>View 2: Canvas tote illustration, back</figcaption>
<span data-image-fallback>Preview unavailable. <a href="/examples/media/back.svg">Open the original illustration</a>.</span>
</figure>
<figure data-media-item>
<a href="/examples/media/detail.svg">
<img src="/examples/media/detail.svg" alt="Canvas tote illustration, stitching detail" width="480" height="640">
</a>
<figcaption>View 3: Canvas tote illustration, stitching detail</figcaption>
<span data-image-fallback>Preview unavailable. <a href="/examples/media/detail.svg">Open the original illustration</a>.</span>
</figure>
</div>
<div data-media-controls>
<button class="thin-button" type="button" data-media-previous>Previous image</button>
<button class="thin-button" type="button" data-media-next>Next image</button>
<output data-media-status>Image 1</output>
</div>
</section>
<script type="module">import { enhanceMedia } from "/behaviors.js"; enhanceMedia(document);</script>Missing preview with original illustration
A server-authored failure snapshot keeps a visible explanation and working original-image link. A newly failing image is also verified through the real behavior.
<section class="thin-product-gallery" aria-labelledby="product-failed-title" style="overflow-wrap: anywhere">
<h2 id="product-failed-title">Canvas tote illustrations</h2>
<p>Gallery navigation changes the media view only. Product price, availability and selected variant remain application-owned.</p>
<div class="thin-reel" tabindex="0" role="group" aria-label="Product illustrations">
<figure aria-current="true" data-media-item>
<a href="/examples/media/front.svg">
<img src="/examples/media/front.svg" alt="Canvas tote illustration, front" width="640" height="480">
</a>
<figcaption>View 1: Canvas tote illustration, front</figcaption>
<span data-image-fallback>Preview unavailable. <a href="/examples/media/front.svg">Open the original illustration</a>.</span>
</figure>
<figure data-media-item data-image-error>
<a href="/examples/media/back.svg">
<img src="data:image/png;base64,broken" alt="Canvas tote illustration, back" width="800" height="400">
</a>
<figcaption>View 2: Canvas tote illustration, back</figcaption>
<span data-image-fallback>Preview unavailable. <a href="/examples/media/back.svg">Open the original illustration</a>.</span>
</figure>
<figure data-media-item>
<a href="/examples/media/detail.svg">
<img src="/examples/media/detail.svg" alt="Canvas tote illustration, stitching detail" width="480" height="640">
</a>
<figcaption>View 3: Canvas tote illustration, stitching detail</figcaption>
<span data-image-fallback>Preview unavailable. <a href="/examples/media/detail.svg">Open the original illustration</a>.</span>
</figure>
</div>
<div data-media-controls>
<button class="thin-button" type="button" data-media-previous>Previous image</button>
<button class="thin-button" type="button" data-media-next>Next image</button>
<output data-media-status>Image 1</output>
</div>
</section>
<script type="module">import { enhanceMedia } from "/behaviors.js"; enhanceMedia(document);</script>Show code
<section class="thin-product-gallery" aria-labelledby="product-failed-title" style="overflow-wrap: anywhere">
<h2 id="product-failed-title">Canvas tote illustrations</h2>
<p>Gallery navigation changes the media view only. Product price, availability and selected variant remain application-owned.</p>
<div class="thin-reel" tabindex="0" role="group" aria-label="Product illustrations">
<figure aria-current="true" data-media-item>
<a href="/examples/media/front.svg">
<img src="/examples/media/front.svg" alt="Canvas tote illustration, front" width="640" height="480">
</a>
<figcaption>View 1: Canvas tote illustration, front</figcaption>
<span data-image-fallback>Preview unavailable. <a href="/examples/media/front.svg">Open the original illustration</a>.</span>
</figure>
<figure data-media-item data-image-error>
<a href="/examples/media/back.svg">
<img src="data:image/png;base64,broken" alt="Canvas tote illustration, back" width="800" height="400">
</a>
<figcaption>View 2: Canvas tote illustration, back</figcaption>
<span data-image-fallback>Preview unavailable. <a href="/examples/media/back.svg">Open the original illustration</a>.</span>
</figure>
<figure data-media-item>
<a href="/examples/media/detail.svg">
<img src="/examples/media/detail.svg" alt="Canvas tote illustration, stitching detail" width="480" height="640">
</a>
<figcaption>View 3: Canvas tote illustration, stitching detail</figcaption>
<span data-image-fallback>Preview unavailable. <a href="/examples/media/detail.svg">Open the original illustration</a>.</span>
</figure>
</div>
<div data-media-controls>
<button class="thin-button" type="button" data-media-previous>Previous image</button>
<button class="thin-button" type="button" data-media-next>Next image</button>
<output data-media-status>Image 1</output>
</div>
</section>
<script type="module">import { enhanceMedia } from "/behaviors.js"; enhanceMedia(document);</script>Native gallery without enhancement
This fixture has no initialization script or optional controls. Every figure remains scrollable and every destination remains an ordinary link.
<section class="thin-product-gallery" aria-labelledby="product-native-title" style="overflow-wrap: anywhere">
<h2 id="product-native-title">Canvas tote illustrations</h2>
<p>Gallery navigation changes the media view only. Product price, availability and selected variant remain application-owned.</p>
<div class="thin-reel" tabindex="0" role="group" aria-label="Product illustrations">
<figure data-media-item>
<a href="/examples/media/front.svg">
<img src="/examples/media/front.svg" alt="Canvas tote illustration, front" width="640" height="480">
</a>
<figcaption>View 1: Canvas tote illustration, front</figcaption>
<span data-image-fallback>Preview unavailable. <a href="/examples/media/front.svg">Open the original illustration</a>.</span>
</figure>
<figure data-media-item>
<a href="/examples/media/back.svg">
<img src="/examples/media/back.svg" alt="Canvas tote illustration, back" width="800" height="400">
</a>
<figcaption>View 2: Canvas tote illustration, back</figcaption>
<span data-image-fallback>Preview unavailable. <a href="/examples/media/back.svg">Open the original illustration</a>.</span>
</figure>
<figure data-media-item>
<a href="/examples/media/detail.svg">
<img src="/examples/media/detail.svg" alt="Canvas tote illustration, stitching detail" width="480" height="640">
</a>
<figcaption>View 3: Canvas tote illustration, stitching detail</figcaption>
<span data-image-fallback>Preview unavailable. <a href="/examples/media/detail.svg">Open the original illustration</a>.</span>
</figure>
</div>
</section>Show code
<section class="thin-product-gallery" aria-labelledby="product-native-title" style="overflow-wrap: anywhere">
<h2 id="product-native-title">Canvas tote illustrations</h2>
<p>Gallery navigation changes the media view only. Product price, availability and selected variant remain application-owned.</p>
<div class="thin-reel" tabindex="0" role="group" aria-label="Product illustrations">
<figure data-media-item>
<a href="/examples/media/front.svg">
<img src="/examples/media/front.svg" alt="Canvas tote illustration, front" width="640" height="480">
</a>
<figcaption>View 1: Canvas tote illustration, front</figcaption>
<span data-image-fallback>Preview unavailable. <a href="/examples/media/front.svg">Open the original illustration</a>.</span>
</figure>
<figure data-media-item>
<a href="/examples/media/back.svg">
<img src="/examples/media/back.svg" alt="Canvas tote illustration, back" width="800" height="400">
</a>
<figcaption>View 2: Canvas tote illustration, back</figcaption>
<span data-image-fallback>Preview unavailable. <a href="/examples/media/back.svg">Open the original illustration</a>.</span>
</figure>
<figure data-media-item>
<a href="/examples/media/detail.svg">
<img src="/examples/media/detail.svg" alt="Canvas tote illustration, stitching detail" width="480" height="640">
</a>
<figcaption>View 3: Canvas tote illustration, stitching detail</figcaption>
<span data-image-fallback>Preview unavailable. <a href="/examples/media/detail.svg">Open the original illustration</a>.</span>
</figure>
</div>
</section>- Selected image — shown above
- Image failure — shown above
- No JavaScript — shown above
Interaction and keyboard behavior
Native scrolling and links remain canonical; enhancement tracks only the visible media index.
Accessibility
Describe each product view and do not infer product selection from gallery navigation.
- Contract
CAROUSEL· APG pattern- Basis
- APG Carousel for slide selection, with native image links as fallback
- Behavior guarantees
- Selects media and announces a bounded position; it never owns product variants, inventory, pricing, or image truth.
- Evidence
tests/unit/library/media.test.tstests/unit/library/advanced-component-contracts.test.ts
API
Use .thin-product-gallery, linked data-media-item figures, and optional lightbox attributes.
Behavior tier: dependency-free
Structured host, property, and example tables are not authored for this item yet. Use the summary above and the primary example.
Fallback
Every image remains an ordinary link or a natively scrollable figure without enhancement.
Assets and dependencies
/foundation.css/layouts.css/components.css/behaviors.js
Dependencies: None
Source: packages/ui/src/items/media/style.ts
Composition
Derived from the examples and style specs, not authored.