Brik Design System
Components

AmbientField

Premium-tier ambient motion field behind content — Lottie by default or a 2D-canvas particle field, freezing to a static poster frame under reduced motion.

AmbientField is the living background layer: a slow, ambient motion field that sits behind content and never competes with it. It is the decorative layer only — absolutely positioned, aria-hidden, non-interactive. It does not create the stacking context it lives in, because MediaBand already owns that recipe.

AmbientField

Use when: A Premium-tier surface that has spent the motion budget on a living, moving background.

Don't: A non-Premium surface, or a layer that must paint from SSR — that's BackgroundPattern.

<AmbientField src={fieldLoop} />

BackgroundPattern

Use when: Static dot- or line-grid texture — costs nothing and needs no reduced-motion handling.

Don't: The brief asks for ambient motion on a Premium surface — that's AmbientField.

<BackgroundPattern variant="dot-grid" />

MediaBand

Use when: The positioned band both layers slot into — it owns the z-order and clip recipe.

Don't: You only need the decorative layer itself, not a band wrapper.

<MediaBand graphic={<AmbientField />} />

Use it for

  • A Premium-tier surface that has committed the motion budget for a living background
  • A hero or section band that should read as alive without animating anything the reader is trying to use
  • Per-client atmosphere, where the loop itself is swapped from a theme rather than from JSX

Not for content-bearing media — the layer is aria-hidden and non-interactive, so nothing inside it reaches the accessibility tree. Not for non-Premium surfaces either: prefer the static BackgroundPattern, which costs nothing and needs no reduced-motion handling.

Import

import { AmbientField, MediaBand } from '@brikdesigns/bds';

Composition

AmbientField expects a positioned parent. Compose it into MediaBand's graphic slot rather than hand-rolling one:

<MediaBand as="section" graphic={<AmbientField src={fieldLoop} />}>
  <SectionHeader title="How we work" />
</MediaBand>
LayerPosition
.bds-media-bandrelative, z-index: 1, overflow: clip
.bds-ambient-fieldabsolute, inset: 0, z-index: 1, pointer-events: none
.bds-media-band__contentrelative, z-index: 2

Two draw modes

mode="lottie" is the default and the one to reach for: cheap, already in the toolkit, and art-directable by whoever made the file. mode="canvas" draws a drifting 2D particle field and is only worth its cost when the brief asks for a physical feel a designed loop cannot fake.

<AmbientField src={fieldLoop} />                      // Lottie — default
<AmbientField mode="canvas" particleCount={64} />     // physics feel

Source: prop or token

Pass src, or leave it off and set --bds-bg-field-src. The token is the per-client swap path — one theme declaration changes the loop across every field on the page.

.theme-acme .bds-ambient-field {
  --bds-bg-field-src: url('/acme/field.json');
}

The token is read once on mount with getComputedStyle. A CSS custom property is not available to the server, so a token-sourced field paints from the first client render rather than from SSR output. Pass src when that matters.

Reduced motion

This is the ship gate, not a nicety. Under prefers-reduced-motion: reduce the field freezes to a static poster frame. It is never hidden — a reader who asked for less motion still gets the composition the designer intended, held still.

ModeAt rest
lottieLoads and holds frame 0 via stop(), with autoplay and loop both off
canvasPaints exactly one frame and never schedules requestAnimationFrame
EitherCSS animation and transition are cut on the layer and its descendants

The CSS rule lands before any script runs, so the still state applies even if JS never arrives. The component holds the frame explicitly rather than relying on lottie-react declining to autoplay by itself — that is vendor behaviour a future bump could change silently.

The canvas field uses a fixed seed, so the still frame is the same composition every time rather than a different random scatter per paint.

Accessibility

  • Renders aria-hidden="true" with pointer-events: none — out of the accessibility tree and out of the tab order.
  • The reduced-motion poster frame is the ship gate above; nothing about the layer moves once it is honoured.
  • The layer carries no contrast guarantee of its own. Use --bds-bg-field-tint and --bds-bg-field-opacity to hold contrast for the content stacked above it.

API

PropTypeDefault
srcstring | object—
mode'lottie' | 'canvas''lottie'
particleCountnumber48

Plus all standard HTML attributes for the rendered div.

CSS Override API

VariableDefaultControls
--bds-bg-field-srcnoneLottie source when no src prop is passed
--bds-bg-field-tinttransparentFlat wash over the field
--bds-bg-field-opacity1Opacity of the whole layer
--bds-bg-field-particlevar(--bds-text-muted)Particle colour for mode="canvas"

--bds-bg-field-particle reaches the canvas through the element's inherited color, because a 2D context cannot read a custom property. The indirection keeps the value in the token layer instead of hardcoded in the paint loop.

On this page

💬