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.

css
.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;
  }
}