More Animations Snippets
Animated Task List — Add, Complete & Delete HTML CSS JS
Animated Task List · Animations · Plain HTML, CSS & JS · Live preview
What's included
Features
@keyframes slideIn with cubic-bezier(.34,1.56,.64,1) (spring overshoot) + staggered delay (i * 30ms) creates a cascading ripple on list population..removing class triggers @keyframes slideOut — slides right + collapses max-height to 0. State array updates in the animationend callback, keeping DOM and data in sync..done-state: green checkbox fill, strikethrough text, light green card background — all CSS transitions, zero JS style changes.render() is the single source of truth.opacity: 0 by default; opacity: 1 on .task-item:hover. Keyboard users can tab to the button directly since it's always in the focus order.deleteTask on each completed item — each one gets its own slide-out animation independently.<ul aria-live="polite"> announces additions and deletions to screen readers without interrupting ongoing speech.About this UI Snippet
Animated Task List — Slide-In/Out Keyframes, Filter Tabs & Optimistic State Update

Animated lists are a foundational interaction pattern in modern web apps — tasks that slide in when added, check off with a strikethrough, and slide out when deleted make state changes feel tangible rather than instantaneous. This snippet builds a complete animated task manager: a slide-in @keyframes for new items, a CSS transition for done state (strikethrough + green border), a slide-out animation on delete, filter tabs (All / Active / Done), a live "tasks left" counter, and a "Clear done" bulk action — all in plain HTML, CSS, and vanilla JavaScript.
Animation patterns for lists are tricky because adding, removing, and updating items need separate animation strategies that coexist without flickering.
Slide-in animation for new tasks
New items use animation: slideIn .28s cubic-bezier(.34,1.56,.64,1) — a spring-overshoot easing that gives the item a slight bounce on entry. The translateY(-12px) scale(.97) start state gives the impression of the item dropping in from slightly above. When multiple items are shown (e.g., on filter change), each gets a staggered animation-delay of i * 30ms — a ripple cascade that makes the full list feel alive.
Slide-out animation on delete
Rather than immediately removing the DOM element, the delete handler adds a .removing class and listens for animationend. The animation slides the item right and collapses its height: transform: translateX(20px) scale(.96), opacity: 0, max-height: 0, padding: 0. Transitioning max-height to 0 smoothly closes the gap in the list without a jump. Crucially, the actual state array (tasks.filter) runs after the animation completes — the DOM removal and state removal are in sync.
Filter tabs without full re-render
The filter buttons switch the filter variable and clear the list, then call render(). The render function computes which items should be visible and adds only the ones not already in the DOM — an incremental DOM approach. Items that were removed by filter change are handled by the "remove deleted items" loop that also runs on each render pass. This prevents flicker from a full list teardown on every filter switch.
Live counter with ARIA live region
The <ul> has aria-live="polite" — screen readers announce additions and removals. The count badge reads "X left" (active task count) and the footer shows "X of Y complete" — two levels of progress feedback. Pair with a progress bar if you want a visual completion percentage bar across the top.
Build with AI
Build, Understand, Optimize, and Extend It With AI
You don't have to trace the DOM-diffing logic by hand — paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly how render() decides which task-item elements to leave alone, which to create, and which to mark "removing" without ever tearing down and rebuilding the whole list, and why the actual tasks array is only spliced inside the animationend callback rather than immediately on delete. The same assistant is useful for optimizing it — asking whether querying list.querySelectorAll('.task-item') on every render call scales well once the list grows into the hundreds, or whether a keyed map lookup would be faster. It's just as good for extending the list: ask it to add drag-to-reorder using the Pointer Events API while preserving the existing slide animations, persist tasks to localStorage so they survive a refresh, or add due dates with an overdue-highlight state layered on top of the existing done-state styling. Treat the code less like a finished artifact and more like a starting point for a conversation.
Prompt to recreate it
Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:
Build an animated task list in plain HTML, CSS, and JavaScript — no libraries, with incremental DOM updates (not a full re-render) driving slide-in and slide-out keyframe animations.
Requirements:
- A single array of task objects (id, text, done) as the source of truth, an add form, filter buttons (All/Active/Done), a live "X left" counter badge, a footer completion summary, and a "Clear done" bulk action.
- Write one render function that: computes which tasks should currently be visible given the active filter, hides/removes any rendered list items whose task no longer belongs in that filtered view, creates DOM nodes only for tasks not already present in the list (checking by a data-id attribute, not recreating existing nodes), and updates the done/not-done visual state of existing nodes in place without recreating them.
- New task items must play a spring-overshoot slide-in keyframe animation (translateY plus scale, easing with an overshoot cubic-bezier) when first inserted, and when several appear at once (e.g. after switching filters) each one must be staggered with an increasing animation-delay so they cascade in rather than popping in simultaneously.
- Deleting a task (individually or via "Clear done") must not remove it from the DOM or the underlying array immediately. Instead, add a "removing" class that triggers a slide-out keyframe animating opacity, a horizontal translate, and the element's max-height/padding down to zero, and only splice the task out of the underlying array inside that animation's animationend event — so the visual removal and the data removal stay in sync.
- Toggling a task's done state must update a "done" class that drives CSS transitions only (background color, checkbox fill, strikethrough text) — no JavaScript-driven style changes for that part.
- Mark the task list container with aria-live="polite" so additions and removals are announced to screen readers without needing extra JavaScript.Step by step
How to Use
- 1Paste HTML, CSS, and JSA task list card renders with four pre-loaded tasks, filter tabs, an add input, and a footer with completion stats.
- 2Type a task and press EnterThe new task slides in from above with a spring bounce animation. The counter updates immediately.
- 3Click the checkbox to complete a taskThe checkbox fills green, the text gets a strikethrough and turns grey, and the card background turns light green — all via CSS class toggle.
- 4Hover and click the delete buttonThe × button appears on hover. Clicking it slides the item to the right and collapses its height before removing it from the DOM.
- 5Use the filter tabs"Active" shows only incomplete tasks. "Done" shows only completed tasks. "All" shows everything. The list re-renders with a staggered slide-in for visible items.
- 6Click "Clear done"All completed tasks animate out simultaneously. The counter and footer stats update after all animations complete.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
On every state change, call localStorage.setItem('tasks', JSON.stringify(tasks)). On load, initialise: let tasks = JSON.parse(localStorage.getItem('tasks') || 'null') || defaultTasks.
Add draggable="true" to each .task-item. Use dragstart, dragover, and drop events to track the dragged item and its target. On drop, splice the tasks array and call render(). For a polished version, see the drag sort list snippet.
Add a dueDate property to each task object. Render it as a <time> element inside the list item. Add a date picker input to the add form. Highlight overdue tasks with a red background by comparing task.dueDate < new Date().toISOString().
Use useState for the tasks array. The add handler calls setTasks(prev => [newTask, ...prev]). Toggle done: setTasks(prev => prev.map(t => t.id === id ? {...t, done: !t.done} : t)). Delete: setTasks(prev => prev.filter(t => t.id !== id)). CSS animations still trigger because the DOM element is newly mounted on each add.