Articles / Animation & Motion /
Chaining transitions with delay
Create cascading animation sequences without JavaScript. Learn how to stagger multiple property transitions using delay for polished, professional effects.
The challenge of sequential animations
When an element changes state, you may want different properties to change at different times. A dropdown menu might fade in immediately, but its items should appear one by one. A card might slide into position while its content fades in slightly later. These sequential effects feel more natural than everything happening at once.
In JavaScript, you'd use setTimeout or requestAnimationFrame to stagger updates. In CSS transitions, you achieve the same effect using the delay value. Each property can wait a different amount of time before starting its transition. The result is a choreographed sequence where properties change in order.
This technique is called chaining or staggering transitions. It's useful for list items, card components, dropdown menus, toast notifications, and any multi-part interface element. Let's explore how to master it.
The delay parameter
The transition shorthand accepts a delay value as its fourth parameter. Written out: transition: property duration timing-function delay. The delay is a time value that tells the browser to wait before starting the transition. Zero or negative delay means immediate start. Positive delay means wait that long first.
.button {
transition: background-color 0.3s ease 0s;
}
.button:focus {
background-color: oklch(0.55 0.18 260);
}
This button transitions immediately on focus because the delay is zero. Change the delay to 0.2s and the button waits 200 milliseconds before starting. The transition itself still takes 0.3 seconds; you're just delaying when it begins.
For chaining, each property gets its own delay. Property A starts at 0s, property B starts at 0.1s, property C starts at 0.2s. They chain together in sequence.
Basic chaining pattern
Here's a dropdown menu that chains three properties: display, opacity, and transform. The display transition starts immediately, then opacity and translate follow with staggered delays:
.dropdown-menu {
display: block;
opacity: 1;
translate: 0 0;
transition:
display 0.2s allow-discrete,
opacity 0.2s ease 0.05s,
translate 0.2s ease 0.1s;
@starting-style {
opacity: 0;
translate: 0 0.5rem;
}
}
.dropdown-menu.hidden {
opacity: 0;
translate: 0 -0.5rem;
display: none;
}
On entrance, the menu shows immediately. After 0.05s it starts fading in. After 0.1s it starts sliding up. On exit, the order reverses: translate animates out first, then opacity, then display removes the element. This creates a smooth, cascading effect where the menu feels like it's building and collapsing organically.
The delays are relative. You might use 0s, 0.1s, 0.2s for a tight sequence or 0s, 0.2s, 0.4s for more breathing room. The ratio matters more than the absolute values.
Animating list items with staggering
The most common use case for chained transitions is animating lists of items. Each list item should enter slightly later than the previous one, creating a cascading wave:
.list-item {
opacity: 1;
translate: 0 0;
transition:
opacity 0.3s ease,
translate 0.3s cubic-bezier(0.2, 0, 0, 1);
@starting-style {
opacity: 0;
translate: 0 1rem;
}
}
.list-item:nth-child(1) { transition-delay: 0s; }
.list-item:nth-child(2) { transition-delay: 0.05s; }
.list-item:nth-child(3) { transition-delay: 0.1s; }
.list-item:nth-child(4) { transition-delay: 0.15s; }
.list-item:nth-child(5) { transition-delay: 0.2s; }
Each list item waits for its turn. Item 1 appears immediately, item 2 waits 50ms, item 3 waits 100ms, and so on. The total animation time is the duration plus the last delay: 0.3s + 0.2s = 0.5s for all five items to complete. The effect is smooth and intentional.
Use the individual transition-delay property when you need different delays per element. You can't specify different delays in a single shorthand for all items. The :nth-child() selector targets each item individually.
Using custom properties for dynamic delays
For longer lists or dynamic content, --i custom properties let you calculate delays programmatically. Modern CSS lets you multiply values, so you can compute the delay directly:
.list-item {
--i: 0;
opacity: 1;
translate: 0 0;
transition:
opacity 0.25s ease,
translate 0.25s ease 0.08s;
transition-delay: calc(0.05s * var(--i));
@starting-style {
opacity: 0;
translate: 0 0.75rem;
}
}
Set --i: 1 on the second item, --i: 2 on the third, and so on. The delay scales automatically. You can generate these values in CSS using :has() or in JavaScript when building the list. This approach is more maintainable than writing out every :nth-child() selector.
For very long lists, cap the delay with min() to prevent excessive waiting: transition-delay: min(0.3s, calc(0.05s * var(--i))). This caps the total animation at a reasonable duration regardless of list length.
Multiple properties, multiple delays
You can also chain different properties on a single element. A card might animate its transform, opacity, and box-shadow with individual delays for each:
.card {
opacity: 1;
translate: 0 0;
box-shadow: 0 0 oklch(0 0 0 / 0);
transition:
translate 0.3s cubic-bezier(0.2, 0, 0, 1),
opacity 0.3s ease,
box-shadow 0.3s ease 0.15s;
@starting-style {
opacity: 0;
translate: 0 1rem;
box-shadow: 0 0 oklch(0 0 0 / 0);
}
}
.card.is-visible {
box-shadow: 0 12px 40px oklch(0 0 0 / 0.12);
}
The card slides in and fades in together. After 0.15s, the shadow grows in, giving depth to the finished state. This layered approach feels deliberate: first the card appears, then it gains presence. Each property contributes at the right moment.
Experiment with delays to find the right balance. Too short and the effect is imperceptible. Too long and the animation feels sluggish. In the 50ms to 150ms range is typically effective for individual property staggering.
Common patterns and best practices
For entrance animations, use small delays between 0.03s and 0.1s. Each item should appear to flow naturally from the previous one. For larger, more dramatic effects like modals or page transitions, you can extend to 0.15s or 0.2s. Exceeding that threshold risks making the animation feel lazy.
Always test with keyboard navigation. Staggered animations shouldn't interfere with focus management. If a user tabs to a menu item, it should appear immediately without waiting for the animation sequence to catch up. Use :focus-visible or reduce timing for keyboard users.
prefers-reduced-motion. If users have requested reduced motion, use transition-duration: 0s without delays. The content should appear instantly.
Don't overuse chaining. Simple hover states don't need staggering. Reserve delays for moments where the sequence adds value: modal entrances, list reveals, complex state changes. Too many staggered animations make the interface feel busy and draw attention away from content.
Best practices for timing selection
Selecting the right durations and delays is crucial for a polished effect. A good rule of thumb: entrance animations should complete in 200-500ms total. Anything faster feels twitchy; anything slower feels sluggish. The total time includes both the duration of each animation and any delays between them.
For staggered lists, use 0.03s to 0.1s between items, depending on list length. A five-item menu works well with 0.05s delays. A twenty-item list might need 0.03s to avoid a 600ms wait for the last item. Cap extreme delays with min() or calculate them dynamically based on item count.
Consider the context. A fast-app dashboard needs quicker animations than a marketing landing page. Match the timing to your application's personality. A financial trading app might use 150ms transitions for snappy feedback. An editorial blog might use 400ms for a calmer, more deliberate feel. Test with actual users if uncertain—they'll tell you what feels right.
Accessibility considerations
Chained transitions must respect user preferences for reduced motion. Some users have vestibular disorders that make animation painful or even dangerous. The prefers-reduced-motion media query detects these preferences and lets you disable or simplify animations:
@media (prefers-reduced-motion: reduce) {
.list-item {
transition-duration: 0s;
transition-delay: 0s;
}
}
.list-item {
transition: opacity 0.3s ease;
transition-delay: calc(0.05s * var(--i));
}
This overrides both duration and delay to zero, making content appear instantly. Users with motion sensitivity don't want alternatives; they want animations removed. Don't be creative here—just make it instant.
Also consider keyboard navigation timing. If a user tabs through a list of items, they shouldn't feel forced to wait for staggered animations to complete before interacting. The delay should apply only on initial entrance, not on every state change. For interactive elements, use immediate transitions for focus and hover states.
Real-world examples
Consider a notification list in a chat application. Each new message should slide in smoothly without disturbing the user. Using chaining, you animate the container opacity and then reveal each message item one by one:
.notification {
opacity: 1;
translate: 0 0;
transition: opacity 0.25s ease, translate 0.3s cubic-bezier(0.2, 0, 0, 1) 0.1s;
@starting-style {
opacity: 0;
translate: 0 0.5rem;
}
}
.notification-item {
opacity: 1;
transition: opacity 0.2s ease;
transition-delay: calc(0.05s * var(--i));
@starting-style {
opacity: 0;
}
}
The container slides in first (with a 0.1s delay for the slide), then each item fades in 50ms after the previous one. The user sees a smooth cascade where notifications appear naturally. This effect is common in real-time chat apps, comment threads, and activity feeds.
Another example: dropdown menus. Modern frameworks like Headless UI and Radix use similar patterns for their menu animations. The menu container appears first, then focus rings and selection highlights animate separately. This gives the interface a responsive yet polished feel.
Browser support and debugging
Transition delays are supported in all modern browsers with no meaningful differences. Chrome, Firefox, Safari, and Edge all execute delays the same way. There are no polyfills needed and no fallback strategies. If the browser supports transitions, it supports delays. This universal support makes chaining a safe technique for production use.
Performance is identical to non-delayed transitions. The delay is just a wait time before starting; it doesn't affect GPU acceleration or compositing. Staggered transitions are just as efficient as simultaneous ones. The browser batches all property changes after their respective delays and animates them smoothly on the compositor thread.
For complex sequences with many elements, performance can degrade if you animate expensive properties. Stick to opacity, transform, and translate for best results. Avoid chaining layout properties like width, height, or margin. These trigger reflows and can cause jank, especially with many elements. Use the Performance panel in DevTools to identify janky animations.
Debugging timed transitions can be tricky because the effect depends on timing values that may seem arbitrary. Use the "Animate" feature in DevTools to preview your transitions before testing in the browser. This lets you experiment with durations and delays without constantly reloading the page. It's much faster than the edit-reload-debug cycle.
Practical strategy
When designing chained transitions, follow this workflow: first, determine which properties need to change. Second, decide the order. The most important property (usually opacity or transform) goes first. Third, assign base delays of 0.05s between items. Fourth, test and adjust. If the effect feels too slow, reduce delays. If it feels rushed, increase them.
Document your patterns. Create a style guide entry showing common use cases: "list items stagger at 0.05s intervals, modals use 0.1s between properties." This ensures consistency across your project and helps new team members understand your animation language.
Finally, consider the context. A 0.5s list animation might feel appropriate on a slow-paced settings page but jarring on a fast dashboard. Match the timing to your application's personality. Fast tools need snappy animations. Editorial content can afford slower, more deliberate pacing.