Native rendering and state
Animate state with view transitions
Wrap a synchronous DOM update in startViewTransition() while preserving the same update and reduced-motion behavior as fallbacks.
Completing, filtering, or reordering tasks can be hard to follow when the board changes instantly. The View Transition API can animate between old and new rendered states without moving your state logic into an animation library.
Keep the DOM update as a normal function. If document.startViewTransition exists and the user has not requested reduced motion, pass that function to it. Otherwise call the same function directly. Enhancement changes presentation, never whether completion works.
const reduceMotion = matchMedia('(prefers-reduced-motion: reduce)')
function update() { setTaskComplete(card, complete) }
if (document.startViewTransition && !reduceMotion.matches) {
document.startViewTransition(update)
} else {
update()
}
With the API supported and motion allowed, completing a task cross-fades instead of jumping. With reduced motion enabled, the same setTaskComplete call runs with no transition.
The update callback should perform the state change that produces the new view. Do not call the update first and then start a transition. The browser would capture two identical states and have nothing meaningful to animate.
Give stable items unique view-transition-name values only when you need element-level continuity, and keep names unique in the rendered view. For a first version, the root cross-fade may already make the change easier to follow.
Reduced motion is not merely shorter motion. Prefer the direct update when the user requests reduction. Also test interruptions. A user may trigger another change before an animation finishes, so the data operation must stay correct regardless of transition timing.
Try this on your board: filter tasks with and without the API branch, then enable reduced motion in your OS settings. The same tasks must appear in every case. Slow animations in DevTools and activate another filter midway. The final task set should follow the latest selected filter, not the animation that started first. If the update happens twice, check that you call update() only inside the supported branch or the fallback, never both.
Lesson completed