Carousel
Preview Available in release assets
Present a finite set of media in a natively scrollable reel.
Carousel primary example
<section class="thin-carousel" aria-labelledby="carousel-title">
<h2 id="carousel-title">Featured stories</h2>
<div class="thin-reel" id="featured-reel">
<figure data-media-item>
<a href="one-large.jpg">
<img src="one.jpg" alt="Coast at sunrise">
</a>
<figcaption>Coast at sunrise</figcaption>
</figure>
<figure data-media-item>
<a href="two-large.jpg">
<img src="two.jpg" alt="Pine forest">
</a>
<figcaption>Pine forest</figcaption>
</figure>
</div>
<div data-media-controls>
<button class="thin-button" data-variant="secondary" type="button" data-media-previous>
<svg class="thin-icon" data-icon="chevron-left" viewBox="0 0 24 24" aria-hidden="true">
<path d="m15 18-6-6 6-6" />
</svg>
<span>Previous</span>
</button>
<button class="thin-button" data-variant="secondary" type="button" data-media-next>
<span>Next</span>
<svg class="thin-icon" data-icon="chevron-right" viewBox="0 0 24 24" aria-hidden="true">
<path d="m9 18 6-6-6-6" />
</svg>
</button>
<output data-media-status>Image 1 of 2</output>
</div>
</section>Show code
<section class="thin-carousel" aria-labelledby="carousel-title">
<h2 id="carousel-title">Featured stories</h2>
<div class="thin-reel" id="featured-reel">
<figure data-media-item>
<a href="one-large.jpg">
<img src="one.jpg" alt="Coast at sunrise">
</a>
<figcaption>Coast at sunrise</figcaption>
</figure>
<figure data-media-item>
<a href="two-large.jpg">
<img src="two.jpg" alt="Pine forest">
</a>
<figcaption>Pine forest</figcaption>
</figure>
</div>
<div data-media-controls>
<button class="thin-button" data-variant="secondary" type="button" data-media-previous>
<svg class="thin-icon" data-icon="chevron-left" viewBox="0 0 24 24" aria-hidden="true">
<path d="m15 18-6-6 6-6" />
</svg>
<span>Previous</span>
</button>
<button class="thin-button" data-variant="secondary" type="button" data-media-next>
<span>Next</span>
<svg class="thin-icon" data-icon="chevron-right" viewBox="0 0 24 24" aria-hidden="true">
<path d="m9 18 6-6-6-6" />
</svg>
</button>
<output data-media-status>Image 1 of 2</output>
</div>
</section>Variant gallery
Each authored example uses the production assets and includes copyable markup.
Editorial reel
Every illustration is an ordinary link. Explicit enhanceMedia initialization adds bounded Previous/Next mechanics and a current-image status; native scrolling remains available.
<section class="thin-carousel" aria-labelledby="carousel-editorial-title" style="overflow-wrap: anywhere; ">
<h2 id="carousel-editorial-title">Illustration gallery</h2>
<div class="thin-reel" tabindex="0" role="group" aria-label="Scrollable 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>
<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-carousel" aria-labelledby="carousel-editorial-title" style="overflow-wrap: anywhere; ">
<h2 id="carousel-editorial-title">Illustration gallery</h2>
<div class="thin-reel" tabindex="0" role="group" aria-label="Scrollable 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>
<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>Contained reel
The same production markup sits inside an authored content measure. Long captions wrap and overflow stays inside the reel.
<section class="thin-carousel" aria-labelledby="carousel-contained-title" style="overflow-wrap: anywhere; max-inline-size: 24rem">
<h2 id="carousel-contained-title">Illustration gallery</h2>
<div class="thin-reel" tabindex="0" role="group" aria-label="Scrollable 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>
<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-carousel" aria-labelledby="carousel-contained-title" style="overflow-wrap: anywhere; max-inline-size: 24rem">
<h2 id="carousel-contained-title">Illustration gallery</h2>
<div class="thin-reel" tabindex="0" role="group" aria-label="Scrollable 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>
<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
- Named region
- Reel
- Linked figures
- Optional controls and status
Variants and sizes
- Editorial — shown above
- Contained — shown above
States and difficult content
Rendered fixtures exercise authored content and constrained layouts with the same production assets.
First, middle and last images
Use Next twice to reach the middle and last image, then Previous to return. Navigation stops at the authored bounds and changes no product/application state.
<section class="thin-carousel" aria-labelledby="carousel-states-title" style="overflow-wrap: anywhere; ">
<h2 id="carousel-states-title">Illustration gallery</h2>
<div class="thin-reel" tabindex="0" role="group" aria-label="Scrollable 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>
<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-carousel" aria-labelledby="carousel-states-title" style="overflow-wrap: anywhere; ">
<h2 id="carousel-states-title">Illustration gallery</h2>
<div class="thin-reel" tabindex="0" role="group" aria-label="Scrollable 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>
<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>Unavailable preview with original link
A server-authored error snapshot shows the readable fallback even before enhancement. Browser checks also exercise a newly failing image after behavior initialization.
Show code
<section class="thin-carousel" aria-labelledby="carousel-failed-title" style="overflow-wrap: anywhere; ">
<h2 id="carousel-failed-title">Illustration gallery</h2>
<div class="thin-reel" tabindex="0" role="group" aria-label="Scrollable 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 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>Long gallery
Eight illustrations remain in authored order, including when no script runs. Repeated source illustrations keep this finite example self-contained.
<section class="thin-carousel" aria-labelledby="carousel-long-title" style="overflow-wrap: anywhere; ">
<h2 id="carousel-long-title">Illustration gallery</h2>
<div class="thin-reel" tabindex="0" role="group" aria-label="Scrollable 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>
<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-carousel" aria-labelledby="carousel-long-title" style="overflow-wrap: anywhere; ">
<h2 id="carousel-long-title">Illustration gallery</h2>
<div class="thin-reel" tabindex="0" role="group" aria-label="Scrollable 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>
<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>- First — shown above
- Middle — shown above
- Last — shown above
- Image failure — shown above
- Long gallery — shown above
Interaction and keyboard behavior
Touch and trackpad scrolling work natively; optional buttons and arrow keys update only the mechanical media index.
Accessibility
Name the region, provide useful alternatives, and keep every full image reachable by link.
- Contract
CAROUSEL· APG pattern- Basis
- APG Carousel with native links and labelled controls
- 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-carousel with a .thin-reel descendant and data-media-item figures.
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.