Media
The Media module (M70): a page section that presents photos or video — a single visual at three widths, a pair, two grids, and two carousels — with an optional caption. Built from the Visual atom and the Carousel footer.
Built withVisualCarousel footer
Examples
Single, full width
A 21:9 visual bleeding to the viewport edges; the optional caption stays inside the content column. This is the type the module opens a page with.
<figure class="kompan-media kompan-media_full">
<div class="kompan-visual kompan-visual_21x9"><img src="https://cdn.sanity.io/images/aqjgxzv4/production/49d040d99874d92e901541340b7cf4f7ca7a6533-1600x1067.jpg" alt="A green climbing net seen from below"></div>
</figure>Single, contained
A 16:9 visual inside the 1280px container, with a caption.
<figure class="kompan-media">
<div class="kompan-media__container">
<div class="kompan-visual kompan-visual_16x9"><img src="https://cdn.sanity.io/images/aqjgxzv4/production/bb4a81fd245831de26b47bc1a62a43afd75ef3b9-1600x1066.jpg" alt="Children climbing the owl-shaped play tower"></div>
<figcaption class="kompan-media__caption">Caption</figcaption>
</div>
</figure>Single, small
The same, in a 768px column — for a photo that accompanies text rather than leading it.
<figure class="kompan-media kompan-media_small">
<div class="kompan-media__container">
<div class="kompan-visual kompan-visual_16x9"><img src="https://cdn.sanity.io/images/aqjgxzv4/production/25daa91b33fe981b44036954c2670e6e82edc727-1600x1066.jpg" alt="Children queuing on a wide slide"></div>
<figcaption class="kompan-media__caption">Federal Hill Commons Park i Noblesville er et sted med kunstnerisk flair, da parkens højdepunkt er det smukke amfiteater, der er omgivet af æstetisk landskabsarkitektur.</figcaption>
</div>
</figure>Double image
Two 4:5 visuals side by side; on mobile they stack as 5:4.
<figure class="kompan-media">
<div class="kompan-media__container">
<div class="kompan-media__pair">
<div class="kompan-visual kompan-visual_4x5"><img src="https://cdn.sanity.io/images/aqjgxzv4/production/bb4a81fd245831de26b47bc1a62a43afd75ef3b9-1600x1066.jpg" alt="Children climbing the owl-shaped play tower"></div>
<div class="kompan-visual kompan-visual_4x5"><img src="https://cdn.sanity.io/images/aqjgxzv4/production/25daa91b33fe981b44036954c2670e6e82edc727-1600x1066.jpg" alt="Children queuing on a wide slide"></div>
</div>
<figcaption class="kompan-media__caption">Caption</figcaption>
</div>
</figure>Image grid
Two columns of 16:9 visuals, as many rows as needed (Figma toggles rows 2 and 3). On mobile the tiles become squares.
<section class="kompan-media">
<div class="kompan-media__container">
<div class="kompan-media__grid">
<div class="kompan-visual kompan-visual_16x9"><img src="https://cdn.sanity.io/images/aqjgxzv4/production/06f6f5def9acaa1a3e02c1d6504ef3ea1c776ef2-1600x748.jpg" alt="A girl at the top of a slide"></div>
<div class="kompan-visual kompan-visual_16x9"><img src="https://cdn.sanity.io/images/aqjgxzv4/production/9a969aae62a5185f4db5ff973cf7eedc69619cc9-1000x667.jpg" alt="Two girls peeking through a wooden climbing wall"></div>
<div class="kompan-visual kompan-visual_16x9"><img src="https://cdn.sanity.io/images/aqjgxzv4/production/b97e801e03d070499e757d923d874c4a89b5512d-1600x748.jpg" alt="A boy peeking through turquoise bars"></div>
<div class="kompan-visual kompan-visual_16x9"><img src="https://cdn.sanity.io/images/aqjgxzv4/production/49d040d99874d92e901541340b7cf4f7ca7a6533-1600x1067.jpg" alt="A green climbing net seen from below"></div>
<div class="kompan-visual kompan-visual_16x9"><img src="https://cdn.sanity.io/images/aqjgxzv4/production/ab9654ba02d9d75a5e993f6bda3fa882bd783bb0-1000x667.jpg" alt="A girl looking up through a climbing tube"></div>
<div class="kompan-visual kompan-visual_16x9"><img src="https://cdn.sanity.io/images/aqjgxzv4/production/bb4a81fd245831de26b47bc1a62a43afd75ef3b9-1600x1066.jpg" alt="Children climbing the owl-shaped play tower"></div>
</div>
</div>
</section>Image grid, offset
Three stacks of visuals with mixed ratios, so the rows fall out of line. Ratios follow the Figma variant column by column.
<section class="kompan-media">
<div class="kompan-media__container">
<div class="kompan-media__columns">
<div class="kompan-media__column">
<div class="kompan-visual kompan-visual_5x4"><img src="https://cdn.sanity.io/images/aqjgxzv4/production/9a969aae62a5185f4db5ff973cf7eedc69619cc9-1000x667.jpg" alt="Two girls peeking through a wooden climbing wall"></div>
<div class="kompan-visual kompan-visual_1x1"><img src="https://cdn.sanity.io/images/aqjgxzv4/production/bb4a81fd245831de26b47bc1a62a43afd75ef3b9-1600x1066.jpg" alt="Children climbing the owl-shaped play tower"></div>
<div class="kompan-visual kompan-visual_4x5"><img src="https://cdn.sanity.io/images/aqjgxzv4/production/ab9654ba02d9d75a5e993f6bda3fa882bd783bb0-1000x667.jpg" alt="A girl looking up through a climbing tube"></div>
</div>
<div class="kompan-media__column">
<div class="kompan-visual kompan-visual_4x5"><img src="https://cdn.sanity.io/images/aqjgxzv4/production/b97e801e03d070499e757d923d874c4a89b5512d-1600x748.jpg" alt="A boy peeking through turquoise bars"></div>
<div class="kompan-visual kompan-visual_16x9"><img src="https://cdn.sanity.io/images/aqjgxzv4/production/49d040d99874d92e901541340b7cf4f7ca7a6533-1600x1067.jpg" alt="A green climbing net seen from below"></div>
<div class="kompan-visual kompan-visual_1x1"><img src="https://cdn.sanity.io/images/aqjgxzv4/production/25daa91b33fe981b44036954c2670e6e82edc727-1600x1066.jpg" alt="Children queuing on a wide slide"></div>
</div>
<div class="kompan-media__column">
<div class="kompan-visual kompan-visual_3x2"><img src="https://cdn.sanity.io/images/aqjgxzv4/production/06f6f5def9acaa1a3e02c1d6504ef3ea1c776ef2-1600x748.jpg" alt="A girl at the top of a slide"></div>
<div class="kompan-visual kompan-visual_2x3"><img src="https://cdn.sanity.io/images/aqjgxzv4/production/ab9654ba02d9d75a5e993f6bda3fa882bd783bb0-1000x667.jpg" alt="A girl looking up through a climbing tube"></div>
<div class="kompan-visual kompan-visual_1x1"><img src="https://cdn.sanity.io/images/aqjgxzv4/production/9a969aae62a5185f4db5ff973cf7eedc69619cc9-1000x667.jpg" alt="Two girls peeking through a wooden climbing wall"></div>
</div>
</div>
</div>
</section>Carousel, small
A 304px-high track of mixed ratios that scrolls freely (trackpad, drag, keyboard), snapping loosely to a card. The Carousel footer underneath shows the count and arrows; wiring the arrows to scroll the track is the consumer’s script.
<section class="kompan-media kompan-media_carousel" aria-label="Photo carousel">
<div class="kompan-media__container">
<div class="kompan-media__track" tabindex="0">
<div class="kompan-visual kompan-visual_4x5"><img src="https://cdn.sanity.io/images/aqjgxzv4/production/ab9654ba02d9d75a5e993f6bda3fa882bd783bb0-1000x667.jpg" alt="A girl looking up through a climbing tube"></div>
<div class="kompan-visual kompan-visual_3x2"><img src="https://cdn.sanity.io/images/aqjgxzv4/production/49d040d99874d92e901541340b7cf4f7ca7a6533-1600x1067.jpg" alt="A green climbing net seen from below"></div>
<div class="kompan-visual kompan-visual_1x1"><img src="https://cdn.sanity.io/images/aqjgxzv4/production/bb4a81fd245831de26b47bc1a62a43afd75ef3b9-1600x1066.jpg" alt="Children climbing the owl-shaped play tower"></div>
<div class="kompan-visual kompan-visual_4x5"><img src="https://cdn.sanity.io/images/aqjgxzv4/production/b97e801e03d070499e757d923d874c4a89b5512d-1600x748.jpg" alt="A boy peeking through turquoise bars"></div>
<div class="kompan-visual kompan-visual_5x4"><img src="https://cdn.sanity.io/images/aqjgxzv4/production/25daa91b33fe981b44036954c2670e6e82edc727-1600x1066.jpg" alt="Children queuing on a wide slide"></div>
</div>
<footer class="kompan-carousel-footer">
<p class="kompan-carousel-footer__info">5 photos</p>
<div class="kompan-carousel-footer__controls">
<button type="button" class="kompan-button kompan-button_secondary-dark kompan-button_md kompan-button_icon-only kompan-carousel-footer__arrow" aria-label="Previous"><svg class="kompan-button__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m15 6-6 6 6 6"/></svg></button>
<button type="button" class="kompan-button kompan-button_secondary-dark kompan-button_md kompan-button_icon-only kompan-carousel-footer__arrow" aria-label="Next"><svg class="kompan-button__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m9 6 6 6-6 6"/></svg></button>
</div>
</footer>
</div>
</section>Carousel, large
The 736px version with a wider gap; the footer sits closer to the track.
<section class="kompan-media kompan-media_carousel kompan-media_carousel-lg" aria-label="Photo carousel">
<div class="kompan-media__container">
<div class="kompan-media__track" tabindex="0">
<div class="kompan-visual kompan-visual_4x5"><img src="https://cdn.sanity.io/images/aqjgxzv4/production/9a969aae62a5185f4db5ff973cf7eedc69619cc9-1000x667.jpg" alt="Two girls peeking through a wooden climbing wall"></div>
<div class="kompan-visual kompan-visual_3x2"><img src="https://cdn.sanity.io/images/aqjgxzv4/production/25daa91b33fe981b44036954c2670e6e82edc727-1600x1066.jpg" alt="Children queuing on a wide slide"></div>
<div class="kompan-visual kompan-visual_1x1"><img src="https://cdn.sanity.io/images/aqjgxzv4/production/bb4a81fd245831de26b47bc1a62a43afd75ef3b9-1600x1066.jpg" alt="Children climbing the owl-shaped play tower"></div>
<div class="kompan-visual kompan-visual_4x5"><img src="https://cdn.sanity.io/images/aqjgxzv4/production/06f6f5def9acaa1a3e02c1d6504ef3ea1c776ef2-1600x748.jpg" alt="A girl at the top of a slide"></div>
</div>
<footer class="kompan-carousel-footer">
<p class="kompan-carousel-footer__info">4 photos</p>
<div class="kompan-carousel-footer__controls">
<button type="button" class="kompan-button kompan-button_secondary-dark kompan-button_md kompan-button_icon-only kompan-carousel-footer__arrow" aria-label="Previous"><svg class="kompan-button__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m15 6-6 6 6 6"/></svg></button>
<button type="button" class="kompan-button kompan-button_secondary-dark kompan-button_md kompan-button_icon-only kompan-carousel-footer__arrow" aria-label="Next"><svg class="kompan-button__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m9 6 6 6-6 6"/></svg></button>
</div>
</footer>
</div>
</section>In context
How the module reads on a page: a full-width opener, a text block, then a pair and a small photo — the sequence from the "Example in context" page in Figma. The text block is a stand-in for the Rich text module.
<figure class="kompan-media kompan-media_full">
<div class="kompan-visual kompan-visual_21x9"><img src="https://cdn.sanity.io/images/aqjgxzv4/production/49d040d99874d92e901541340b7cf4f7ca7a6533-1600x1067.jpg" alt="A green climbing net seen from below"></div>
</figure>
<div class="demo-text">
<small>Pre header</small>
<h2>Header</h2>
<p>A playground swing is a type of equipment commonly found in playgrounds. Swings are a popular choice for any playground setting.</p>
<p>The playground swing is ever-present in our childhood memories.</p>
</div>
<figure class="kompan-media">
<div class="kompan-media__container">
<div class="kompan-media__pair">
<div class="kompan-visual kompan-visual_4x5"><img src="https://cdn.sanity.io/images/aqjgxzv4/production/bb4a81fd245831de26b47bc1a62a43afd75ef3b9-1600x1066.jpg" alt="Children climbing the owl-shaped play tower"></div>
<div class="kompan-visual kompan-visual_4x5"><img src="https://cdn.sanity.io/images/aqjgxzv4/production/25daa91b33fe981b44036954c2670e6e82edc727-1600x1066.jpg" alt="Children queuing on a wide slide"></div>
</div>
<figcaption class="kompan-media__caption">Caption</figcaption>
</div>
</figure>
<figure class="kompan-media kompan-media_small">
<div class="kompan-media__container">
<div class="kompan-visual kompan-visual_16x9"><img src="https://cdn.sanity.io/images/aqjgxzv4/production/49d040d99874d92e901541340b7cf4f7ca7a6533-1600x1067.jpg" alt="A green climbing net seen from below"></div>
<figcaption class="kompan-media__caption">Federal Hill Commons Park i Noblesville er et sted med kunstnerisk flair, da parkens højdepunkt er det smukke amfiteater, der er omgivet af æstetisk landskabsarkitektur.</figcaption>
</div>
</figure>Accessibility
- Pictures carry meaning — describe them
Every img inside a Visual needs alt text that says what the photo shows; alt="" only for a purely decorative image. A caption complements alt text, it does not replace it.
- Use figure and figcaption when there is a caption
The single and double types are a <figure> whose caption is the <figcaption class="kompan-media__caption">, so the caption is associated with the picture(s) for screen readers. Grids and carousels without a caption are plain <section>s.
- Carousels must scroll without the arrows
The track is a scrollable region with tabindex="0" and a visible focus ring, so keyboard users can scroll it with the arrow keys; touch and trackpad scroll it natively. Give the section an aria-label ("Photo carousel", or the gallery’s subject).
- Arrows are progressive enhancement
The Carousel footer arrows do nothing without a script. When you wire them, scroll the track by one card (scrollBy with behavior: "smooth"), update the status line (aria-live="polite"), and disable the arrow at each end.
- Video and galleries
A Visual that opens a lightbox or plays video must expose a real button (see the Visual atom). Do not make the whole picture a silent click target.
- Motion
Snap scrolling is the browser’s own and honours the user’s reduced-motion preference; do not add an auto-advancing carousel.
Stylesheet
Shipped as @wah-kompan/kompan-ui/components/modules/media.css. Every value is a token reference, so it themes with the rest of the system rather than being restyled. The shipped file also imports Visual, Carousel footer — what this component is built with — so one import is enough.
.kompan-media {
--kompan-media-gap: var(--container-gap-content-cards-md);
--kompan-media-track-h: 19rem; /* Carousel Small: 304px */
--kompan-media-track-gap: var(--kompan-media-gap);
--kompan-media-footer-gap: var(--spacing-4xl);
display: grid;
gap: var(--spacing-md);
box-sizing: border-box;
margin: 0;
padding-block: var(--spacing-5xl);
background: var(--color-bg-primary);
font-family: var(--type-font-family-body);
}
/* The 1280px content column, padded by the responsive container token */
.kompan-media__container {
display: grid;
gap: var(--spacing-md);
box-sizing: border-box;
width: 100%;
max-width: var(--container-default-max-width);
margin-inline: auto;
padding-inline: var(--container-padding-lg);
}
.kompan-media_small .kompan-media__container {
max-width: var(--width-xl);
}
.kompan-media__caption {
max-width: var(--width-paragraph-max-width);
margin: 0;
color: var(--color-text-secondary);
font-size: var(--type-font-size-xs);
font-weight: var(--type-font-weight-regular);
line-height: var(--type-line-height-xs);
}
/* Double image */
.kompan-media__pair {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--kompan-media-gap);
}
/* Image grid (straight): two columns, any number of rows */
.kompan-media__grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--kompan-media-gap);
}
/* Image grid (offset): three stacks of visuals with different ratios */
.kompan-media__columns {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--kompan-media-gap);
align-items: start;
}
.kompan-media__column {
display: grid;
gap: var(--kompan-media-gap);
}
/* Carousels: a horizontally scrolling track, free-flowing with proximity snap */
.kompan-media_carousel .kompan-media__container {
gap: var(--kompan-media-footer-gap);
}
.kompan-media_carousel-lg {
--kompan-media-track-h: 46rem; /* Carousel Large: 736px */
--kompan-media-track-gap: var(--spacing-4xl);
--kompan-media-footer-gap: var(--kompan-media-gap);
}
.kompan-media__track {
display: flex;
gap: var(--kompan-media-track-gap);
margin: 0;
padding: 0;
overflow-x: auto;
overscroll-behavior-x: contain;
scroll-snap-type: x proximity;
scrollbar-width: none;
}
.kompan-media__track::-webkit-scrollbar {
display: none;
}
.kompan-media__track:focus-visible {
outline: 2px solid var(--color-button-focus-ring);
outline-offset: 2px;
}
.kompan-media__track > * {
flex: none;
scroll-snap-align: start;
}
/* A visual in the track keeps its ratio and takes its width from the track height */
.kompan-media__track > .kompan-visual {
width: auto;
height: var(--kompan-media-track-h);
}
@media (max-width: 480px) {
.kompan-media {
--kompan-media-track-h: 12.5rem; /* 200px */
}
.kompan-media_carousel-lg {
--kompan-media-track-h: 20rem; /* 320px */
}
.kompan-media_full > .kompan-visual {
--kompan-visual-ratio: 16 / 9; /* Figma: 21:9 on desktop, 16:9 on mobile */
}
.kompan-media__pair {
grid-template-columns: 1fr;
}
.kompan-media__pair > .kompan-visual {
--kompan-visual-ratio: 5 / 4;
}
.kompan-media__grid > .kompan-visual,
.kompan-media__column > .kompan-visual {
--kompan-visual-ratio: 1 / 1;
}
.kompan-media__columns {
grid-template-columns: 1fr 1fr;
}
.kompan-media__column {
display: contents;
}
}