{"id":6471,"date":"2025-07-08T11:10:25","date_gmt":"2025-07-08T16:10:25","guid":{"rendered":"https:\/\/frontendmasters.com\/blog\/?p=6471"},"modified":"2025-07-08T11:10:27","modified_gmt":"2025-07-08T16:10:27","slug":"view-transition-list-reordering-with-a-kick-flip","status":"publish","type":"post","link":"https:\/\/frontendmasters.com\/blog\/view-transition-list-reordering-with-a-kick-flip\/","title":{"rendered":"View Transition List Reordering (with a Kick Flip)"},"content":{"rendered":"\n<p>I remember when we first got animations and transitions in CSS on the web (ok grandpa), the talk around it was balanced between <em>oooo! fun! shiny! <\/em>and <em>actually, movement is more than aesthetics; it can help people understand what is happening in user interfaces.<\/em><\/p>\n\n\n\n<p>The example that got stuck in my head was reordering lists. Imagine a single list item being plucked off and moved to the top. If that <em>instantly<\/em> happens, it can be hard to catch what even happened. But if you animate the movement, it can be extremely obvious what is happening.<\/p>\n\n\n\n<div class=\"wp-block-columns is-layout-flex wp-container-core-columns-is-layout-9d6595d7 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<p><strong>Works, but it is not particularly easy to understand what is happening<\/strong>:<\/p>\n\n\n\n\t\t<figure class=\"wp-block-jetpack-videopress jetpack-videopress-player\" style=\"\" >\n\t\t\t<div class=\"jetpack-videopress-player__wrapper\"> <iframe title=\"VideoPress Video Player\" aria-label='VideoPress Video Player' width='500' height='427' src='https:\/\/videopress.com\/embed\/xuAQHNXP?cover=1&amp;autoPlay=0&amp;controls=1&amp;loop=0&amp;muted=0&amp;persistVolume=1&amp;playsinline=0&amp;preloadContent=metadata&amp;useAverageColor=1&amp;hd=0' frameborder='0' allowfullscreen data-resize-to-parent=\"true\" allow='clipboard-write'><\/iframe><script src='https:\/\/v0.wordpress.com\/js\/next\/videopress-iframe.js?m=1739540970'><\/script><\/div>\n\t\t\t\n\t\t\t\n\t\t<\/figure>\n\t\t<\/div>\n\n\n\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<p><strong>More fun <em>and<\/em> easier to understand what is happening<\/strong>:<\/p>\n\n\n\n\t\t<figure class=\"wp-block-jetpack-videopress jetpack-videopress-player\" style=\"\" >\n\t\t\t<div class=\"jetpack-videopress-player__wrapper\"> <iframe title=\"VideoPress Video Player\" aria-label='VideoPress Video Player' width='500' height='396' src='https:\/\/videopress.com\/embed\/tDSbUdpY?cover=1&amp;autoPlay=0&amp;controls=1&amp;loop=0&amp;muted=0&amp;persistVolume=1&amp;playsinline=0&amp;preloadContent=metadata&amp;useAverageColor=1&amp;hd=0' frameborder='0' allowfullscreen data-resize-to-parent=\"true\" allow='clipboard-write'><\/iframe><script src='https:\/\/v0.wordpress.com\/js\/next\/videopress-iframe.js?m=1739540970'><\/script><\/div>\n\t\t\t\n\t\t\t\n\t\t<\/figure>\n\t\t<\/div>\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\">The List<\/h2>\n\n\n\n<p>We&#8217;re talking a regular ol list. <span style=\"box-sizing: border-box; margin: 0px; padding: 0px;\">Perhaps ironic that we&#8217;re&nbsp;<em>ordering<\/em>&nbsp;and&nbsp;<em>unordered<\/em>&nbsp;lists, but I&#8217;ll leave that as a semantic thoughtworm for the reader.<\/span><\/p>\n\n\n\n<p>Each list item has text, then a button which the intended action is that, when clicked, will move the list item to the top.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ul<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"list\"<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">li<\/span>&gt;<\/span>\n    Apples\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span> <span class=\"hljs-attr\">hidden<\/span> <span class=\"hljs-attr\">disabled<\/span> <span class=\"hljs-attr\">aria-label<\/span>=<span class=\"hljs-string\">\"Move to Top\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">svg<\/span> <span class=\"hljs-attr\">...<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">svg<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">li<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">li<\/span>&gt;<\/span>\n    Oranges\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span> <span class=\"hljs-attr\">hidden<\/span> <span class=\"hljs-attr\">disabled<\/span> <span class=\"hljs-attr\">aria-label<\/span>=<span class=\"hljs-string\">\"Move to Top\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">svg<\/span> <span class=\"hljs-attr\">...<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">svg<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">li<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">li<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span> <span class=\"hljs-attr\">hidden<\/span> <span class=\"hljs-attr\">disabled<\/span> <span class=\"hljs-attr\">aria-label<\/span>=<span class=\"hljs-string\">\"Move to Top\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">svg<\/span> <span class=\"hljs-attr\">...<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">svg<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n    Mangos\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">li<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">li<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span> <span class=\"hljs-attr\">hidden<\/span> <span class=\"hljs-attr\">disabled<\/span> <span class=\"hljs-attr\">aria-label<\/span>=<span class=\"hljs-string\">\"Move to Top\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">svg<\/span> <span class=\"hljs-attr\">...<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">svg<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n    Bananas\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">li<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">ul<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Note that each button has a text label (as we&#8217;re not using text inside the button), and a <code>hidden<\/code> attribute we&#8217;ll use to make sure the button isn&#8217;t there at all when JavaScript is disabled. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Scaffolding the Interactive JavaScript<\/h2>\n\n\n\n<p>This will get us references to the elements we need, as well as do a loop and un-hide the buttons as well as attach an event listener to them:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">const<\/span> button = <span class=\"hljs-built_in\">document<\/span>.querySelector(<span class=\"hljs-string\">\"button\"<\/span>);\n<span class=\"hljs-keyword\">const<\/span> list = <span class=\"hljs-built_in\">document<\/span>.querySelector(<span class=\"hljs-string\">\".list\"<\/span>);\n<span class=\"hljs-keyword\">let<\/span> listItems = list.querySelectorAll(<span class=\"hljs-string\">\"li\"<\/span>);\n<span class=\"hljs-keyword\">const<\/span> listItemButtons = list.querySelectorAll(<span class=\"hljs-string\">\"li &gt; button\"<\/span>);\n\nlistItemButtons.forEach(<span class=\"hljs-function\">(<span class=\"hljs-params\">button<\/span>) =&gt;<\/span> {\n  button.hidden = <span class=\"hljs-literal\">false<\/span>;\n  button.addEventListener(<span class=\"hljs-string\">\"click\"<\/span>, <span class=\"hljs-keyword\">async<\/span> () =&gt; {\n    <span class=\"hljs-comment\">\/\/ do stuff<\/span>\n  });\n});<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Moving the List Item to the Top<\/h2>\n\n\n\n<p>When the button is clicked, we&#8217;ll need the list item, not the button itself, so we reach up a level to the parent. Then we freshly figure out what the <em>first<\/em> list item is, and <code>insertBefore<\/code> it, making the clicked one the first one. <\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">const<\/span> button = <span class=\"hljs-built_in\">document<\/span>.querySelector(<span class=\"hljs-string\">\"button\"<\/span>);\n<span class=\"hljs-keyword\">const<\/span> list = <span class=\"hljs-built_in\">document<\/span>.querySelector(<span class=\"hljs-string\">\".list\"<\/span>);\n<span class=\"hljs-keyword\">let<\/span> listItems = list.querySelectorAll(<span class=\"hljs-string\">\"li\"<\/span>);\n<span class=\"hljs-keyword\">const<\/span> listItemButtons = list.querySelectorAll(<span class=\"hljs-string\">\"li &gt; button\"<\/span>);\n\nlistItemButtons.forEach(<span class=\"hljs-function\">(<span class=\"hljs-params\">button<\/span>) =&gt;<\/span> {\n  button.hidden = <span class=\"hljs-literal\">false<\/span>;\n  button.addEventListener(<span class=\"hljs-string\">\"click\"<\/span>, <span class=\"hljs-keyword\">async<\/span> () =&gt; {\n    <span class=\"hljs-keyword\">const<\/span> item = button.parentElement;\n    <span class=\"hljs-keyword\">const<\/span> firstListItem = list.querySelector(<span class=\"hljs-string\">\".list :first-child\"<\/span>);\n    list.insertBefore(item, firstListItem);\n\n    <span class=\"hljs-comment\">\/\/ This is probably the better API to use, but less supported...<\/span>\n    <span class=\"hljs-comment\">\/\/ list.moveBefore(item, firstListItem);<\/span>\n  });\n});<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>I only recently <a href=\"https:\/\/developer.chrome.com\/blog\/movebefore-api\">learned about <code>moveBefore<\/code><\/a> which is probably a better API to use, but we can wait a bit for better support. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">(Progressively Enhanced) Movement via View Transitions<\/h2>\n\n\n\n<p>One type of View Transitions are &#8220;same page&#8221; View Transitions, where we essentially call <code>document.startViewTransition<\/code> and change the DOM inside the callback. <\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">const<\/span> button = <span class=\"hljs-built_in\">document<\/span>.querySelector(<span class=\"hljs-string\">\"button\"<\/span>);\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">const<\/span> list = <span class=\"hljs-built_in\">document<\/span>.querySelector(<span class=\"hljs-string\">\".list\"<\/span>);\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">let<\/span> listItems = list.querySelectorAll(<span class=\"hljs-string\">\"li\"<\/span>);\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">const<\/span> listItemButtons = list.querySelectorAll(<span class=\"hljs-string\">\"li &gt; button\"<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><mark class='shcb-loc'><span><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">moveListItemFirst<\/span>(<span class=\"hljs-params\">item<\/span>) <\/span>{\n<\/span><\/mark><mark class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> firstListItem = list.querySelector(<span class=\"hljs-string\">\".list :first-child\"<\/span>);\n<\/span><\/mark><mark class='shcb-loc'><span>  list.insertBefore(item, firstListItem);\n<\/span><\/mark><mark class='shcb-loc'><span>}\n<\/span><\/mark><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>listItemButtons.forEach(<span class=\"hljs-function\">(<span class=\"hljs-params\">button<\/span>) =&gt;<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>  button.hidden = <span class=\"hljs-literal\">false<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>  button.addEventListener(<span class=\"hljs-string\">\"click\"<\/span>, <span class=\"hljs-keyword\">async<\/span> () =&gt; {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> item = button.parentElement;\n<\/span><\/span><mark class='shcb-loc'><span>    <span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-built_in\">document<\/span>.startViewTransition) {\n<\/span><\/mark><mark class='shcb-loc'><span>      <span class=\"hljs-keyword\">const<\/span> transition = <span class=\"hljs-built_in\">document<\/span>.startViewTransition(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n<\/span><\/mark><mark class='shcb-loc'><span>        moveListItemFirst(item);\n<\/span><\/mark><mark class='shcb-loc'><span>      });\n<\/span><\/mark><mark class='shcb-loc'><span>    } <span class=\"hljs-keyword\">else<\/span> {\n<\/span><\/mark><mark class='shcb-loc'><span>      moveListItemFirst(item);\n<\/span><\/mark><mark class='shcb-loc'><span>    }\n<\/span><\/mark><span class='shcb-loc'><span>  });\n<\/span><\/span><span class='shcb-loc'><span>});\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Because we need to move the list item whether the browser supports View Transitions or not, we abstract that to a function, and call it on either branch of logic testing that support. <\/p>\n\n\n\n<p>This will immediately do a fade transition for the list items, which honestly isn&#8217;t much of an improvement in this case (it still can be nice for the other type of View Transitions: page transitions). Fortunately, we&#8217;ve got a pretty decent one-line fix in CSS:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">ul<\/span> {\n  li {\n    <span class=\"hljs-attribute\">view-transition-name<\/span>: match-element;\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>If you&#8217;ve played with View Transitions before, it&#8217;s likely you&#8217;ve got in your head that every single element needs a <em>unique<\/em> <code>view-transition-name<\/code>. And that&#8217;s still true in Firefox for now, as only Chrome and Safari are supporting <code>match-element<\/code> as I write. But as we&#8217;re just playing here, this is such a nice improvement and reduces so much fiddliness, I think it&#8217;s worth it. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Special View Transitions Only For the &#8220;Main Moving Element&#8221;<\/h2>\n\n\n\n<p>The deal here is really that <em>all<\/em> the elements are moving. It&#8217;s either the element you clicked on moving to the first position, or the rest of the list items moving out of the way. <\/p>\n\n\n\n<p>So the goal here is to apply a unique <code>view-transition-name<\/code> to the element that is the &#8220;main moving element&#8221;, then remove it once it&#8217;s done. To make matters a bit more difficult, we&#8217;ve got <em>two<\/em> animations we want to apply, one of the list item, and one <em>just<\/em> for the icon within the button. That&#8217;s slightly tricky!<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">const<\/span> button = <span class=\"hljs-built_in\">document<\/span>.querySelector(<span class=\"hljs-string\">\"button\"<\/span>);\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">const<\/span> list = <span class=\"hljs-built_in\">document<\/span>.querySelector(<span class=\"hljs-string\">\".list\"<\/span>);\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">let<\/span> listItems = list.querySelectorAll(<span class=\"hljs-string\">\"li\"<\/span>);\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">const<\/span> listItemButtons = list.querySelectorAll(<span class=\"hljs-string\">\"li &gt; button\"<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">moveListItemFirst<\/span>(<span class=\"hljs-params\">item<\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> firstListItem = list.querySelector(<span class=\"hljs-string\">\".list :first-child\"<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>  list.insertBefore(item, firstListItem);\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>listItemButtons.forEach(<span class=\"hljs-function\">(<span class=\"hljs-params\">button<\/span>) =&gt;<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>  button.hidden = <span class=\"hljs-literal\">false<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>  button.addEventListener(<span class=\"hljs-string\">\"click\"<\/span>, <span class=\"hljs-keyword\">async<\/span> () =&gt; {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> item = button.parentElement;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><mark class='shcb-loc'><span>    item.style.viewTransitionName = <span class=\"hljs-string\">\"woosh\"<\/span>;\n<\/span><\/mark><mark class='shcb-loc'><span>    item.querySelector(<span class=\"hljs-string\">\"svg\"<\/span>).style.viewTransitionName = <span class=\"hljs-string\">\"tony-hawk\"<\/span>;\n<\/span><\/mark><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-built_in\">document<\/span>.startViewTransition) {\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">const<\/span> transition = <span class=\"hljs-built_in\">document<\/span>.startViewTransition(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>        moveListItemFirst(item);\n<\/span><\/span><span class='shcb-loc'><span>      });\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><mark class='shcb-loc'><span>      <span class=\"hljs-keyword\">try<\/span> {\n<\/span><\/mark><mark class='shcb-loc'><span>        <span class=\"hljs-keyword\">await<\/span> transition.finished;\n<\/span><\/mark><mark class='shcb-loc'><span>      } <span class=\"hljs-keyword\">finally<\/span> {\n<\/span><\/mark><mark class='shcb-loc'><span>        item.style.viewTransitionName = <span class=\"hljs-string\">\"\"<\/span>;\n<\/span><\/mark><mark class='shcb-loc'><span>        item.querySelector(<span class=\"hljs-string\">\"svg\"<\/span>).style.viewTransitionName = <span class=\"hljs-string\">\"\"<\/span>;\n<\/span><\/mark><mark class='shcb-loc'><span>        makeFirstListItemsButtonDisabled();\n<\/span><\/mark><mark class='shcb-loc'><span>      }\n<\/span><\/mark><span class='shcb-loc'><span>    } <span class=\"hljs-keyword\">else<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>      moveListItemFirst(item);\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span>  });\n<\/span><\/span><span class='shcb-loc'><span>});\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Now we&#8217;ve got &#8220;woosh&#8221; and &#8220;tony-hawk&#8221; view transition names we can use to apply animation control in CSS.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-pseudo\">::view-transition-group(<\/span>*) {\n  <span class=\"hljs-attribute\">animation-duration<\/span>: <span class=\"hljs-number\">1s<\/span>;\n}\n\n<span class=\"hljs-selector-pseudo\">::view-transition-old(woosh)<\/span>,\n<span class=\"hljs-selector-pseudo\">::view-transition-new(woosh)<\/span> {\n  <span class=\"hljs-attribute\">animation<\/span>: woosh <span class=\"hljs-number\">1s<\/span> ease-in-out;\n}\n\n<span class=\"hljs-keyword\">@keyframes<\/span> woosh {\n  50% {\n    <span class=\"hljs-attribute\">translate<\/span>: -<span class=\"hljs-number\">100px<\/span> <span class=\"hljs-number\">0<\/span>;\n    <span class=\"hljs-attribute\">scale<\/span>: <span class=\"hljs-number\">1.5<\/span>;\n    <span class=\"hljs-attribute\">box-shadow<\/span>: <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">30px<\/span> <span class=\"hljs-number\">15px<\/span> <span class=\"hljs-built_in\">lch<\/span>(<span class=\"hljs-number\">0%<\/span> <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">0<\/span> \/ <span class=\"hljs-number\">50%<\/span>);\n  }\n}\n\n<span class=\"hljs-selector-pseudo\">::view-transition-old(tony-hawk)<\/span>,\n<span class=\"hljs-selector-pseudo\">::view-transition-new(tony-hawk)<\/span> {\n  <span class=\"hljs-attribute\">animation<\/span>: tony-hawk <span class=\"hljs-number\">1s<\/span> ease-in-out;\n}\n\n<span class=\"hljs-keyword\">@keyframes<\/span> tony-hawk {\n  <span class=\"hljs-comment\">\/* sick kick flip *\/<\/span>\n  50% {\n    <span class=\"hljs-attribute\">rotate<\/span>: <span class=\"hljs-number\">20deg<\/span>;\n    <span class=\"hljs-attribute\">scale<\/span>: <span class=\"hljs-number\">2.5<\/span>;\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>So for the &#8220;non-main&#8221; elements, they just move up and down over 1s. But for the &#8220;main&#8221; moving element, we&#8217;ve got these unique <code>@keyframe<\/code> animations we apply while the re-ordering is happening. Note that the keyframes are only applying the <code>50%<\/code> keyframe, so they animate from wherever they were to wherever they are going still, just in the middle they do something special, like the sick kick flip. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Demo<\/h2>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_vEOaJXy\" src=\"\/\/codepen.io\/anon\/embed\/vEOaJXy?height=450&amp;theme-id=1&amp;slug-hash=vEOaJXy&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed vEOaJXy\" title=\"CodePen Embed vEOaJXy\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Video<\/h2>\n\n\n\n<p>I&#8217;m playing with streaming and this idea started as a loose idea for a stream, then I lightly edited it for a regular YouTube video, so maybe you&#8217;d enjoy that:<\/p>\n\n\n\n<figure class=\"wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio\"><div class=\"wp-block-embed__wrapper\">\n<iframe loading=\"lazy\" title=\"Animating the Re-Ordering of a List with View Transitions\" width=\"500\" height=\"281\" src=\"https:\/\/www.youtube.com\/embed\/8vFRqReS-TE?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe>\n<\/div><\/figure>\n","protected":false},"excerpt":{"rendered":"<p>It&#8217;s pretty straightforward to animate list items into new positions, but there is a few tricks when the specific one you&#8217;ve chosen to move needs a *different* transition.<\/p>\n","protected":false},"author":1,"featured_media":6492,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[7,101],"class_list":["post-6471","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-css","tag-view-transitions"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/07\/View-Transition-List-Reordering-with-a-Kick-Flip.jpg?fit=1140%2C676&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/6471","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/comments?post=6471"}],"version-history":[{"count":11,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/6471\/revisions"}],"predecessor-version":[{"id":6494,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/6471\/revisions\/6494"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media\/6492"}],"wp:attachment":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media?parent=6471"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/categories?post=6471"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/tags?post=6471"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}