SolutionsPackagesComponentsCLI — Coming soonResourcesDocumentationStorybookChangelogBlogAboutSign up freeGet early access

Documentation

Sequence

Stagger one preset across many elements, by order, cap, or data-motion-role, or letter by letter with splitText(), using sequence() from @ds-gn/motion.

sequence(targets, options) animates many elements with one preset, offset by a stagger.

import { sequence } from '@ds-gn/motion';

sequence(cards, {
  preset: 'slideUp',
  stagger: 60,
  order: 'start',
  maxDuration: 480,
});

targets is an element, a list or NodeList, or a selector resolved against root (a document, element, or shadow root). A <slot> expands to its assigned elements.

OptionTypeDefault
presetpreset name—
direction'enter' | 'exit''enter'
staggerms between consecutive starts60
order'start' | 'end' | 'center' | 'random''start'
maxDurationmsno cap
rootParentNode for selector targetsdocument
rolesstring[]—

order: 'center' fans out from the middle; the two middle items of an even list start together.

maxDuration caps the time from the first start to the last start. When stagger × (count − 1) would exceed it, the stagger shrinks to fit, so twelve cards at 60ms start within 480ms instead of 660ms. The last element still runs its full duration.

Role-based sequencing

With roles, the targets are a scope, and its data-motion-role descendants are sequenced by their role’s position in the list. Elements that share a role start together; elements with other roles are left alone. CMS-authored sections get consistent choreography without page-specific code.

<section id="feature">
  <h2 data-motion-role="heading">…</h2>
  <img data-motion-role="asset" … />
  <a data-motion-role="cta">…</a>
</section>
sequence(document.querySelector('#feature'), {
  preset: 'slideUp',
  stagger: 100,
  roles: ['heading', 'asset', 'cta'],
});

Letter by letter

splitText(el) wraps each character of an element’s text in its own inline-block element, ready to stagger with sequence.

import { sequence, splitText } from '@ds-gn/motion';

const split = splitText(eyebrow);
const playback = sequence(split.chars, {
  preset: 'fade',
  y: 4, // a small rise per letter
  stagger: 30,
  maxDuration: 600,
});
playback.finished.then(split.revert);
  • Words stay whole. Each word is one unbreakable box, and the whitespace between words stays plain text, so lines still wrap between words.
  • Assistive technology reads the text once. The element holds a visually hidden copy of its text, and the split characters are aria-hidden.
  • Nested markup is kept, so a <strong> inside the text still wraps its characters.
  • Characters are graphemes, so accented letters and emoji stay whole.
  • Revert when the motion ends. revert() puts the original nodes back, which also restores the font’s kerning between characters.

Use maxDuration to cap long text, and keep letter-by-letter motion to short labels: it is decoration, and splitting large text costs more than it adds.

sequence returns a Playback handle, and a whole sequence can be one timeline step.

© 2026 LightLeap Labs LLC. All rights reserved. · Terms of Service · Privacy Policy · Cookies · Contact · Support