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.
| Option | Type | Default |
|---|---|---|
preset | preset name | — |
direction | 'enter' | 'exit' | 'enter' |
stagger | ms between consecutive starts | 60 |
order | 'start' | 'end' | 'center' | 'random' | 'start' |
maxDuration | ms | no cap |
root | ParentNode for selector targets | document |
roles | string[] | — |
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.