VideoFlowcodeGitHubTry itCoreRenderersReact Video EditorPlaygroundExamplesDocscodeGitHubTry it
Example

Layer groups

Notification and metric cards built from nested layer groups.

#groups
// Three card groups stagger in (zoom + 500ms delay), drift on a subtle 3D
// tilt, count their headline number up from 0, then exit together.
// Demonstrates `$.group()`, nested layers, `numberCountUp`, and `$.parallel`.
const $ = new VideoFlow({
  name: 'Groups',
  width: 1920,
  height: 1080,
  fps: 30,
  backgroundColor: '#0a0d18',
});

// Per-card config. rotFrom/rotTo are [rx, ry, rz] in degrees — small values
// keep the tilt subtle. Each card uses a different axis combo for variety.
const CARDS = [
  { x: 0.20, label: 'BOOKS READ',  value: '24',  color: '#ff5a1f', rotFrom: [5, -12, 0], rotTo: [-5,  12, 0] },
  { x: 0.50, label: 'MOVIES SEEN', value: '87',  color: '#4ecdc4', rotFrom: [-10, 5, 0], rotTo: [10,  10, 0] },
  { x: 0.80, label: 'SONGS LIKED', value: '342', color: '#a78bfa', rotFrom: [-8, 10, 0], rotTo: [8, -10, 0] },
];

const groups = [];

for (const card of CARDS) {
  // $.group(props, settings, fn, options) — `fn` builds the children inside
  // the group's local time origin (their startTime is relative to the group).
  const g = $.group(
    // perspective enables 3D depth for child rotation.
    { position: [card.x, 0.5], perspective: 20 },
    {
      transitionIn:  { transition: 'zoom', duration: '600ms', params: { from: 0.6 } },
      transitionOut: { transition: 'zoom', duration: '500ms', params: { from: 0.6 } },
    },
    () => {
      // Rounded square frame.
      $.addShape(
        {
          width: 30, height: 30,
          fill: '#0e1524',
          strokeColor: card.color, strokeWidth: 0.2,
          cornerRadius: 3,
          position: [0.5, 0.5],
        },
        { shapeType: 'rectangle' },
      );
      // Eyebrow label.
      $.addText({
        text: card.label,
        fontSize: 1.8, fontWeight: 700,
        color: card.color, position: [0.5, 0.42],
        letterSpacing: 0.2,
      });
      // Big number — `numberCountUp` ramps the digits from 0 → final value
      // over the transitionIn duration.
      $.addText(
        {
          text: card.value,
          fontSize: 8, fontWeight: 900,
          color: '#f1f5f9', position: [0.5, 0.55],
        },
        { transitionIn: { transition: 'numberCountUp', duration: '1s' } },
      );
    },
    // `waitFor: 0` so the loop's $.wait('500ms') alone controls the stagger.
    { waitFor: 0 },
  );

  // Slow 3D drift on the *group* — applies to every child layer at once.
  g.animate(
    { rotation: card.rotFrom },
    { rotation: card.rotTo   },
    { duration: '5s', easing: 'easeInOut', wait: false },
  );

  groups.push(g);
  $.wait('500ms');
}

$.wait('4s');

// $.parallel runs each branch from the current flow time — all three exits
// fire on the same frame, then each plays its own declared transitionOut.
$.parallel(groups.map((g) => () => g.remove()));
$.wait('500ms');

return $;
Compiling00:00
play_arrowOpen in playgroundcodeSource on GitHub← All examples
VideoFlow

Open-source toolkit for composing videos from code.

Product

CoreRenderersReact Video EditorPlayground

Learn

DocsAPI referenceExamplesvs. Remotionvs. FFmpeg

Project

GitHubLicenseContact

Legal

TermsPrivacy
© 2026 VideoFlow. Apache-2.0 core.