Skip to content
NativeCSS
Docs contents

Media

Image, video, audio and hero sections - helpers/media.css, components/video.css, components/audio.css, components/hero.css, components/cover.css, components/overlay.css.

Images

ClassPurpose
.ncss-ratio-video/-square/-portraitaspect-ratio via custom property, no padding hack needed
.ncss-img-cover / .ncss-img-containobject-fit, -cover including --ncss-focus-point
.ncss-img-rounded / .ncss-img-circleCorner radius / full circle shape
.ncss-img-dim-in-darkDims in dark mode (icons/graphics with a hard light background)
.ncss-figureFigure+figcaption layout (typography already sits in base.css)
.ncss-figure--width-25/-50/-75/-fullWidth of a standalone image inside flowing text (e.g. from a rich-text editor)
.ncss-figure--align-left/-right/-centerText wrap (float) or centering for the same standalone image

Art direction: <picture><source media="..."> reliably loads a different image file per breakpoint - the native, correct tool for this.

Standalone image in flowing text: .ncss-figure--width-*/-align-* are deliberately separate from the .ncss-width-1-2/-1-3/etc. classes in Layout - those are meant for sibling tiles inside a .ncss-flex-grid, not a single image with text wrap. Markup:

<figure class="ncss-figure ncss-figure--width-50 ncss-figure--align-left">
  <img src="..." alt="...">
  <figcaption>Caption</figcaption>
</figure>
<p>Text flows around the image ...</p>

Cover (cropping embeds to fill)

For <img>/<video>, .ncss-img-cover above is enough (native object-fit). components/cover.css covers the one case object-fit does NOT reliably solve: <iframe> embeds (YouTube/Vimeo/Google Maps) - object-fit doesn't affect the embedded document in most browsers.

<div class="ncss-cover-container" style="height: 20rem;">
  <iframe class="ncss-cover" src="https://www.youtube.com/embed/..." title="..." allowfullscreen></iframe>
</div>

Technique: the classic min-width/min-height:100% + width/height:auto crop pattern (no object-fit) - needs an EXPLICIT aspect-ratio on the embedded element itself for this (default: --ncss-aspect-video, overridable via style="aspect-ratio: ..."). Relying on an <iframe>'s width/height HTML attributes instead gives NO aspect-ratio-accurate result on an absolutely positioned element (confirmed via a real test). .ncss-cover-container needs its own height supplied from outside (fixed, %, or e.g. inside a .ncss-hero) - unlike .ncss-ratio-video, which derives height from width.

Video & Audio

.ncss-videoAspect-ratio wrapper for <video>
.ncss-video--responsive-cropDifferent crop of the same file via object-fit + breakpoint - there's no reliable native "different file per breakpoint" for video, unlike images (<source media> there is now just Safari legacy)
.ncss-audioConsistent width for <audio controls>
.ncss-audio-cardAudio player with a title

Overlay (panel over image/video)

components/overlay.css - a panel over an image (caption, CTA, gradient scrim). For an entire page section with a background image: Hero Section below - Overlay is the smaller, media-local variant of the same underlying principle.

<div class="ncss-overlay-container">
  <img src="photo.jpg" alt="...">
  <div class="ncss-overlay ncss-overlay--bottom ncss-overlay--scrim">
    <p class="ncss-text-light">Caption</p>
  </div>
</div>
ClassPurpose
.ncss-overlayDefault: covers the whole area (inset: 0)
.ncss-overlay--top / --bottomOnly the top/bottom edge instead of the whole area
.ncss-overlay--centerCentered, at its own size (no padding) - e.g. for a centered play icon
.ncss-overlay--scrimGradient scrim (dark → transparent) - readable text over varying photo content, mirrors automatically with --top
.ncss-overlay--tintEven, semi-transparent fill instead of a gradient (--ncss-color-overlay)

Combines with existing ncss building blocks instead of duplicating them: .ncss-reveal-on-hover (Scroll & Visibility) for "only visible on hover" - works unchanged, because .ncss-overlay is a direct child of .ncss-overlay-container. .ncss-text-light/-dark (Typography) for text that should stay readable regardless of the image content.

Hero Section

Image background with content on top, components/hero.css.

<section class="ncss-hero">
  <div class="ncss-hero-media" style="background-image: url(image.jpg)"></div>
  <div class="ncss-hero-content ncss-hero-content--filled">
    <h2>Heading</h2>
    <p>Text...</p>
  </div>
</section>
.ncss-heroWrapper, min-height: 22rem, overflow: clip
.ncss-hero-mediaIts own layer for the background image (including a readability scrim) - always set background-image inline, never via a custom property, see Known Limitations
.ncss-hero-content--transparentText directly on the image (default)
.ncss-hero-content--filledText on an opaque card on top
.ncss-hero--parallaxImage moves slower than the content while scrolling, via animation-timeline: view() - no JS, no background-attachment: fixed (deliberately never supported on iOS Safari)

Background video instead of an image

The same .ncss-hero-media, now with a real <video> child instead of background-image - object-fit: cover works reliably for <video> (unlike <iframe>, see Cover above), no special cropping needed:

<div class="ncss-hero-media">
  <video src="clip.mp4" autoplay muted loop playsinline poster="frame.jpg"></video>
</div>

autoplay NEEDS muted (otherwise virtually every browser blocks autostart), playsinline prevents fullscreen takeover on iOS Safari, poster shows a still frame before loading. For prefers-reduced-motion: pure CSS can't control <video> playback (unlike the animation-timeline used by --parallax above) - js/hero-video-motion.js (opt-in, only include it on pages with an actual background video) removes autoplay and pauses the video when that preference is active; the video stays visible (poster/last frame), just without automatic motion:

<script src="js/hero-video-motion.js" defer></script>