List Item Slide
Each new row enters from the left with a slight overshoot (0.4 s); on removal, it slides 60 px to the right while fading as its height, margins and border fold to zero (0.4 s): the following rows move up smoothly instead of jumping. CSS and 2.9 KB of JavaScript, 150 ms fades when the user reduces motion.
Updated
This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Transitions has 52 effects, including 9 free. Explore the category →
3 usage examples



How it works
The .list-slide-root block declares its background (#0a0a0f) and stacks the .list-slide-container list (260 px wide at most, 6 px between rows) above two buttons, "Add" and "Reset". Each .list-slide-item row carries its label and a .list-slide-remove cross; 15% indigo background, 20% indigo border, 8 px corners, 40 px tall. On load, three rows arrive one after another, 150 ms apart; beyond five, adding sends the oldest out.
The entrance is the listSlideIn animation: 0.4 s from translateX(-40px) and opacity 0 to its place, on cubic-bezier(.34, 1.56, .64, 1), which slightly overshoots to the right before settling. Measured 90 ms after the click: 9 px short of its place and opacity 0.78 in Chromium 151, 11 px and 0.72 in Firefox 153. The animation only fills backwards (backwards): the row's resting state is its final state, opacity 1 and no transform, which leaves the exit free to animate them. An animation finished in forwards would keep hold of opacity and transform, and the row could only squash in place.
The exit sets .list-slide-removing, which declares the end state with transition: all 0.4s ease: max-height from 50 px to 0, vertical padding and border to zero, opacity 0, translateX(60px). Measured 150 ms after clicking the cross: 37 px to the right, opacity 0.39 and 19 px tall out of 40 in Chromium, 35 px, 0.42 and 21 px in Firefox, 38 px, 0.37 and 21 px in WebKit 26. The row is removed from the DOM at 400 ms; its height is then zero and the following rows have almost finished moving up: only the 6 px gap between two rows vanishes at once on removal, a last jump of 3 to 8 px depending on the browser. A single listener on the list handles every cross, including those of rows added later; the script walks every .list-slide-root on the page, and the demo labels follow the block's language (data-lang, otherwise the page's lang).
Accessibility
- Reduced motion: under
prefers-reduced-motion, the entrance becomes a 150 ms fade (listSlideFade) and the exit a plain 150 ms fade-out, with no slide or fold; the row is removed at 150 ms and the following ones move up at once — measured in Chromium: no transform, height intact during the fade. - Named cross: each cross is a
buttonwith anaria-label"Remove" followed by the row's label ("Retirer …" in French), since the × sign alone says nothing to a screen reader. It is 17 × 18 px: below the 24 × 24 px recommended for fingers, raisepaddingto6px 8pxon mobile. - Focus after removal: the button that had focus disappears with its row, and focus falls back to the page. In a real list, move it to the next row's cross (or to the list heading) just before the removal, and announce "Paracetamol removed" in an
aria-livearea. - Contrast: white labels at 17.3:1 on the row background; the cross at rest (40% white) gives 3.8:1, enough for an icon, and turns red
#ef4444on hover.
Browser compatibility
CSS animations and transitions (max-height, padding, border-width, transform, opacity) and about sixty lines of JavaScript, no dependency. Measured in Chromium 151, Firefox 153 and WebKit 26: same overshooting entrance, same exit to the right with fold, following rows moving up continuously, then with a jump of a few pixels on removal, 150 ms fades under reduced motion. The script uses Element.closest and NodeList.forEach, available since Chrome 51, Firefox 50 and Safari 10.
Without JavaScript, the list stays empty and the buttons do nothing: render the rows on the server and keep a classic delete link or form. With reduced motion, rows appear and disappear with a 150 ms fade.
The code
HTML structure to paste into your page (the CSS + JS come with Premium):
<div class="list-slide-root">
<div class="list-slide-container">
<!-- rows created by the script: -->
<div class="list-slide-item">
<span>Dashboard</span>
<button class="list-slide-remove" type="button" aria-label="Remove Dashboard">×</button>
</div>
</div>
<div style="display:flex;gap:8px;">
<button class="list-slide-btn list-slide-add" type="button">Add Item</button>
<button class="list-slide-btn secondary list-slide-reset" type="button">Reset</button>
</div>
</div>
The full HTML + CSS + JS of List Item Slide, ready to paste — plus all 840 effects. Or $299 once, lifetime access.
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
Entrance (CSS — listSlideIn, translateX(-40px)) |
0.4 s from 40 px to the left, overshoot | translateY(-12px) drops the row from above; ease-out instead of the curve removes the overshoot. Keep backwards, or the exit stops sliding. |
Exit (CSS — .list-slide-removing) |
0.4 s, 60 px to the right | translateX(-60px) exits to the left, scale(.9) gives a shrinking row. If you change the duration, match the 400 in removeListItem's setTimeout. |
Row max height (CSS — max-height: 50px) |
50 px (a row is 40 px) | This is where the fold starts. For rows with two lines of text, go to 80px; too high (300 px), the fold looks delayed, since it first runs through heights that clip nothing. |
Row count (JS — items.length >= 5) |
5 at most | Beyond it, adding sends the oldest out. Remove this block for a list without a limit. |
Labels (JS — NAMES object) |
8 labels in French and English | They are only for the demo: in your page, create the rows from your data, keeping the <span> + button.list-slide-remove structure. |
Cascade on load (JS — idx * 150) |
3 rows, 150 ms apart, starting at 400 ms | idx * 80 gives a tighter cascade; remove the last setTimeout for an empty list at start. |
FAQ
max-height rather than height, because height: auto does not animate.removeListItem: fetch('/api/items/' + id, { method: 'DELETE' }).then(function (r) { if (r.ok) removeListItem(item); });, with the identifier set as data-id on the row. The row only leaves once the deletion is confirmed; if it fails, it stays in place and you can show a message.