VideoFlowcodeGitHubTry itCoreRenderersReact Video EditorPlaygroundExamplesDocscodeGitHubTry it
Getting started
InstallationQuick startCore conceptsYour first video
Builder
Builder APITime formatsParallel & wait
Layers
TextImageVideoAudioCaptionsShapeGroups
Animation
Animate & keyframesEasing functionsTransitionsEffects
Renderers
Browser rendererServer rendererDOM preview
React Video Editor
QuickstartThemingUploadsCustom panelsHooks & commandsKeyboard shortcuts
API reference
Overview@videoflow/core@videoflow/renderer-browser@videoflow/renderer-server@videoflow/renderer-dom@videoflow/react-video-editor

Layer groups

$.group(properties, settings, fn) creates a container layer. The children authored inside fn are composited onto a private project-sized surface, then the group's own transform / opacity / transitions / effects pipeline runs on that surface — exactly as if the group were a single visual layer.

On this page
const $ = new VideoFlow({ width: 512, height: 512, fps: 30, backgroundColor: '#0a0d18' });

// One transition + one rotation animation applies to the whole sub-tree.
const card = $.group(
  { position: [0.5, 0.5] },
  {
    transitionIn:  { transition: 'overshootPop', duration: '600ms', params: { from: 0.6 } },
    transitionOut: { transition: 'blurResolve',  duration: '450ms' },
  },
  () => {
    $.addShape(
      {
        width: 50, height: 32,
        fill: '#0e1524',
        strokeColor: '#4ecdc4', strokeWidth: 0.25,
        cornerRadius: 2.5,
      },
      { shapeType: 'rectangle' },
    );
    $.addText({
      text: 'CARD', fontSize: 2, fontWeight: 700, color: '#4ecdc4',
      position: [0.5, 0.40], letterSpacing: 0.2,
    });
    $.addText({
      text: '128', fontSize: 11, fontWeight: 900, color: '#f1f5f9',
      position: [0.5, 0.58],
    });
    $.wait('2.6s'); // group's duration auto-derives from this
  },
);
card.animate(
  { rotation: -3 },
  { rotation:  3 },
  { duration: '2.6s', easing: 'easeInOut', wait: false },
);
return $;
Compiling00:00

Why groups?

  • Apply one transition to a whole card scene instead of per-layer.
  • Animate scale / rotation / opacity on the composite.
  • Run a GLSL effect over the entire sub-tree (one bloom pass over a card, not three).

Auto timing

A group's startTime is the current flow time when $.group(...) is called. Its sourceDuration auto-derives from the latest child's end. Children authored inside fn see the group-relative timeline starting at 0.

$.group(
  { position: [0.5, 0.5], scale: 1 },
  {
    transitionIn:  { transition: 'slideLeft', duration: '600ms' },
    transitionOut: { transition: 'zoom',           duration: '600ms' },
  },
  (card) => {
    // Children's timing is relative to the group — `0` here is the
    // group's start, not the project start.
    $.addImage({ fit: 'cover', opacity: 0.55 }, { source: 'https://videoflow.dev/samples/sample.jpg' });
    $.addText({ text: 'Card', fontSize: 4, color: '#fff' });
    $.wait('400ms');
    $.addText({ text: 'Composite as one', fontSize: 1.4, color: '#cdd5e1', position: [0.5, 0.6] });
  },
);

Animating the group

The callback receives the group layer itself, so you can animate it from inside the block. Use wait: false so the animation doesn't advance the group's local flow pointer.

$.group(
  { position: [0.5, 0.5] },
  {},
  (card) => {
    card.animate({ scale: 1, rotation: -3 }, { scale: 1.04, rotation: 3 }, { duration: '3s', wait: false });
    $.addShape({ width: 40, height: 24, fill: '#1c2233', cornerRadius: 1.5 }, { shapeType: 'rectangle' });
    $.addText({ text: '128k', fontSize: 5, fontWeight: 800, color: '#fff', position: [0.5, 0.5] });
    $.wait('3s'); // group's sourceDuration auto-derives from this
  },
);

Nested groups

Groups compose. A nested badge group has its own transition and its own timeline; it composites along with its parent and inherits the parent's transform.

$.group({}, {}, () => {
  $.addShape({ width: 40, height: 24, fill: '#1c2233' }, { shapeType: 'rectangle' });

  $.group(
    { position: [0.7, 0.32], scale: 0.9 },
    { transitionIn: { transition: 'zoom', duration: '350ms' } },
    () => {
      $.addShape({ width: 8, height: 3, fill: '#4ecdc4', cornerRadius: 1.5 }, { shapeType: 'rectangle' });
      $.addText({ text: 'NEW', fontSize: 0.9, fontWeight: 800, color: '#0a0d18' });
    },
  );
});
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.