{"id":9551,"date":"2026-05-06T20:38:33","date_gmt":"2026-05-07T01:38:33","guid":{"rendered":"https:\/\/frontendmasters.com\/blog\/?p=9551"},"modified":"2026-05-06T20:38:34","modified_gmt":"2026-05-07T01:38:34","slug":"animating-focus-with-view-transitions","status":"publish","type":"post","link":"https:\/\/frontendmasters.com\/blog\/animating-focus-with-view-transitions\/","title":{"rendered":"Animating Focus with View Transitions"},"content":{"rendered":"\n<p>The big idea here is animating the focus ring around literally-focused elements on web pages. Like the <code>:focus<\/code> (or <code>:focus-visible<\/code>) styles, either the default or your own.<\/p>\n\n\n\n<p><strong>I&#8217;m just going to go ahead and say this idea I&#8217;m about to play with probably isn&#8217;t a good idea. <\/strong>It&#8217;s a bunch of probably-unnecessary motion. Nobody is asking for it.<\/p>\n\n\n\n<p>Although I <em>say<\/em> that, and <a href=\"https:\/\/webaim.org\/blog\/new-webaim-website\/\">the WebAIM website<\/a>, a site literally all about web accessibility, does it. <\/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='285' src='https:\/\/videopress.com\/embed\/2RXL6BF0?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=1770107250'><\/script><\/div>\n\t\t\t<figcaption>If you can&#8217;t see the video, imagine blue focus outlines around things like navigation links and breadcrumb nav links that have a &#8220;flying&#8221; animation between them as you tab through the page. <\/figcaption>\n\t\t\t\n\t\t<\/figure>\n\t\t\n\n\n<p>Jared says:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>To help ensure high accessibility for sighted keyboard users, we\u2019ve added some nifty keyboard focus indicators for links and form controls. A distinctive color change and slight transition draw visual attention to focused links. Additionally, <strong>scripting provides a focus \u2018trace\u2019 or \u2018flying focus\u2019 to help the user follow the visual focus. Tab through the links on this page to see it in action.<\/strong><\/p>\n<\/blockquote>\n\n\n\n<p>Emphasis mine. They certainly know more about web accessibility than I do! I also note their implementation respects <code>prefers-reduced-motion<\/code>, which seems highly relevant. <\/p>\n\n\n\n<p>Me, I just find all this a fun and interesting challenge, especially since there is a long line of people doing it over the years, and now there is new tech to add to the party. <\/p>\n\n\n\n<p>I thought of this <span style=\"box-sizing: border-box; margin: 0px; padding: 0px;\">after recently reading Ben Nadel&#8217;s&nbsp;<\/span><a href=\"https:\/\/www.bennadel.com\/blog\/4864-animating-dom-rectangles-over-focused-elements-in-javascript.htm\"><span style=\"box-sizing: border-box; margin: 0px; padding: 0px;\">&#8220;Animating DOM Rectangles Over Focused Elements In JavaScr<\/span>ipt.&#8221;<\/a> Ben&#8217;s implementation, along with several others I looked at, involves having a reference in JavaScript to the focused element, measuring its location and dimensions with things like <code>getBoundingClientRect<\/code> and\/or <code>getClientRects<\/code> (in case the lines of text break) then animating\/transitioning between the new numbers you get. <\/p>\n\n\n\n<p>That&#8217;s fine, I suppose. But in my experience, measuring things in JavaScript isn&#8217;t particularly performant, nor is animating values like <code>top<\/code> and <code>left<\/code>. Maybe we could use <a href=\"https:\/\/aerotwist.com\/blog\/flip-your-animations\/\">FLIP<\/a> somehow? Maybe we could make the focused elements <em>anchors<\/em> then use <a href=\"https:\/\/nerdy.dev\/anchor-interpolated-morphing\">AIM<\/a>? That all sounds kind of fun, but what came to mind first for me was View Transitions. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Let&#8217;s Do View Transitions<\/h2>\n\n\n\n<p>View Transitions can do <em>tweening<\/em>, which should work nicely for us here. Short story, if an element has a unique <code>view-transition-name<\/code> on it, and you call <code>startViewTransition()<\/code> and mess around with the DOM (in our case, moving focus from one element to another), then that same element (or another) has that same <code>view-transition-name<\/code>, it will literally animate from one state to the next. Even &#8220;fly&#8221; to the next position, which is exactly what we&#8217;re after. <\/p>\n\n\n\n<p>So we&#8217;re doing JavaScript here. There is a thing called multi-page View Transitions, and they are great, but we&#8217;re not dealing with multiple pages; we&#8217;re just dealing with moving focus around a single page.<\/p>\n\n\n\n<p> Our job is to <em>do something that changes the DOM<\/em> inside the View Transition, and it will just magically animate. I swear, it&#8217;s weird. So what we&#8217;ll do is: we will manually move the focus ourselves, like with a <code>.focus()<\/code> call. That&#8217;s the DOM change, and it&#8217;s enough to animate from the old element with focus and focus styles to the new element with focus and focus styles.<\/p>\n\n\n\n\t\t<figure class=\"wp-block-jetpack-videopress jetpack-videopress-player aligncenter wp-block-jetpack-videopress--has-max-width\" style=\"max-width: 643.992px;\" >\n\t\t\t<div class=\"jetpack-videopress-player__wrapper\"> <iframe title=\"VideoPress Video Player\" aria-label='VideoPress Video Player' width='500' height='330' src='https:\/\/videopress.com\/embed\/4hCmcaXO?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=1770107250'><\/script><\/div>\n\t\t\t<figcaption>It&#8217;s surprising to me that this actually works.<\/figcaption>\n\t\t\t\n\t\t<\/figure>\n\t\t\n\n\n<p>What kinda sucks is that we&#8217;re hijacking the Tab key presses to do this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-built_in\">document<\/span>.addEventListener(<span class=\"hljs-string\">'keydown'<\/span>, (e) =&gt; {\n  <span class=\"hljs-keyword\">if<\/span> (e.key !== <span class=\"hljs-string\">'Tab'<\/span>) <span class=\"hljs-keyword\">return<\/span>;\n  <span class=\"hljs-keyword\">if<\/span> (!<span class=\"hljs-built_in\">document<\/span>.startViewTransition) <span class=\"hljs-keyword\">return<\/span>; <span class=\"hljs-comment\">\/\/ graceful fallback<\/span>\n\n  <span class=\"hljs-keyword\">const<\/span> focusables = &#91;...document.querySelectorAll(\n    <span class=\"hljs-string\">'a&#91;href], button:not(&#91;disabled]), input:not(&#91;disabled]), select:not(&#91;disabled]), textarea:not(&#91;disabled]), &#91;tabindex]:not(&#91;tabindex=\"-1\"])'<\/span>\n  )].filter(<span class=\"hljs-function\"><span class=\"hljs-params\">el<\/span> =&gt;<\/span> el.offsetParent !== <span class=\"hljs-literal\">null<\/span>); <span class=\"hljs-comment\">\/\/ skip hidden<\/span>\n\n  <span class=\"hljs-keyword\">const<\/span> i = focusables.indexOf(<span class=\"hljs-built_in\">document<\/span>.activeElement);\n  <span class=\"hljs-keyword\">if<\/span> (i === <span class=\"hljs-number\">-1<\/span>) <span class=\"hljs-keyword\">return<\/span>; <span class=\"hljs-comment\">\/\/ focus is somewhere weird, let browser handle it<\/span>\n\n  e.preventDefault();\n  <span class=\"hljs-keyword\">const<\/span> dir = e.shiftKey ? <span class=\"hljs-number\">-1<\/span> : <span class=\"hljs-number\">1<\/span>;\n  <span class=\"hljs-keyword\">const<\/span> next = focusables&#91;(i + dir + focusables.length) % focusables.length];\n\n  <span class=\"hljs-built_in\">document<\/span>.startViewTransition(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> next.focus());\n});<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><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>Sucky, meaning the Tab key certainly isn&#8217;t the only way elements receive focus, but that&#8217;s all we&#8217;re dealing with here. In Ben&#8217;s demo, he&#8217;s doing the work on a <code>focusin<\/code> event, which will work no matter how an element gets focus. But if I did that here, the DOM change has already happened, and it&#8217;s too late for a View Transition. I&#8217;m sure there are ways to make this approach more comprehensive, it&#8217;s just too silly a project for me to dig that deep.<\/p>\n\n\n\n<p>Here are things so far:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_019df561-075c-7315-b61a-8d6e58493e7e\" src=\"\/\/codepen.io\/editor\/anon\/embed\/019df561-075c-7315-b61a-8d6e58493e7e?height=450&amp;theme-id=1&amp;slug-hash=019df561-075c-7315-b61a-8d6e58493e7e&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed 019df561-075c-7315-b61a-8d6e58493e7e\" title=\"CodePen Embed 019df561-075c-7315-b61a-8d6e58493e7e\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>It&#8217;s also kinda sucky we&#8217;re in charge of deciding what elements can be focusable. My list is excluding <code>&lt;summary><\/code>, for example, which is a foul. I&#8217;m leaving it off on purpose to emphsize the suck.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Cross-Fade Weirdness<\/h2>\n\n\n\n<p>If we slow down our demo above, we&#8217;ll see some extra funky behavior. We can do that like:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-pseudo\">::view-transition-group(focus-ring)<\/span> {\n  <span class=\"hljs-attribute\">animation-duration<\/span>: <span class=\"hljs-number\">5s<\/span>;\n  <span class=\"hljs-attribute\">animation-timing-function<\/span>: <span class=\"hljs-built_in\">cubic-bezier<\/span>(.<span class=\"hljs-number\">4<\/span>, <span class=\"hljs-number\">0<\/span>, .<span class=\"hljs-number\">2<\/span>, <span class=\"hljs-number\">1<\/span>);\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><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\t\t<figure class=\"wp-block-jetpack-videopress jetpack-videopress-player aligncenter wp-block-jetpack-videopress--has-max-width\" style=\"max-width: 499px;\" >\n\t\t\t<div class=\"jetpack-videopress-player__wrapper\"> <iframe title=\"VideoPress Video Player\" aria-label='VideoPress Video Player' width='500' height='285' src='https:\/\/videopress.com\/embed\/GXI0o9ym?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=1770107250'><\/script><\/div>\n\t\t\t\n\t\t\t\n\t\t<\/figure>\n\t\t\n\n\n<p>I don&#8217;t exactly know how to fix that. There are all sorts of fancy View Transitions pseudo-elements to control parts of the animation, but I don&#8217;t think any of them do quite what I want here. What I&#8217;d like to see is only that pink rectangle moving around, nothing inside it. That pink rectangle is on the element itself. I don&#8217;t think there is any CSS thing for &#8220;make myself transparent but my outline still visible&#8221;. We could fake it, and we&#8217;ll get to that next, but otherwise, I don&#8217;t think there is a way.<\/p>\n\n\n\n<p>Could we kinda &#8220;black out&#8221; the middle, though?<\/p>\n\n\n\n<p>If we add a class to the document during the View Transition, we could &#8220;cover&#8221; the element with a background color while it&#8217;s transitioning. <\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.focus-transitioning<\/span> <span class=\"hljs-selector-pseudo\">:focus-visible<\/span><span class=\"hljs-selector-pseudo\">::after<\/span> {\n  <span class=\"hljs-attribute\">content<\/span>: <span class=\"hljs-string\">\"\"<\/span>;\n  <span class=\"hljs-attribute\">position<\/span>: absolute;\n  <span class=\"hljs-attribute\">inset<\/span>: <span class=\"hljs-number\">0<\/span>;\n  <span class=\"hljs-attribute\">background<\/span>: Canvas;\n  <span class=\"hljs-attribute\">border-radius<\/span>: inherit;\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><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\t\t<figure class=\"wp-block-jetpack-videopress jetpack-videopress-player aligncenter wp-block-jetpack-videopress--has-max-width\" style=\"max-width: 504px;\" >\n\t\t\t<div class=\"jetpack-videopress-player__wrapper\"> <iframe title=\"VideoPress Video Player\" aria-label='VideoPress Video Player' width='500' height='374' src='https:\/\/videopress.com\/embed\/oLLp3TcZ?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=1770107250'><\/script><\/div>\n\t\t\t\n\t\t\t\n\t\t<\/figure>\n\t\t\n\n\n<p>Nahhh, still be pretty gross. But here it is anyway:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_019dfafc-3f0f-75d8-8584-ccb63e221132\" src=\"\/\/codepen.io\/editor\/anon\/embed\/019dfafc-3f0f-75d8-8584-ccb63e221132?height=450&amp;theme-id=1&amp;slug-hash=019dfafc-3f0f-75d8-8584-ccb63e221132&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed 019dfafc-3f0f-75d8-8584-ccb63e221132\" title=\"CodePen Embed 019dfafc-3f0f-75d8-8584-ccb63e221132\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Using a Child Element<\/h2>\n\n\n\n<p>I think the trick is going to be using a <code>&lt;span><\/code> (some meaningless child element) to behave like the <code>:focus-visible<\/code> styling would. So essentially we don&#8217;t have any <code>:focus-visible<\/code> styling directly, we  use the <code>&lt;span><\/code> to replicate it. This is because the <code>&lt;span><\/code> doesn&#8217;t have any children that will move around. It can be an empty ring that flies around.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" 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\">button<\/span>&gt;<\/span>\n  Actual Button Text\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"focus-ring\"<\/span> <span class=\"hljs-attr\">aria-hidden<\/span>=<span class=\"hljs-string\">\"true\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><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>But there is a funky trick&#8230;<\/p>\n\n\n\n<p>The actual <code>&lt;span><\/code> doesn&#8217;t move. Every focusable element has it&#8217;s own <code>&lt;span><\/code> that behaves as a focus ring. It&#8217;s just that different <code>&lt;span><\/code>s becomes visible when the focus changes. <\/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\">a<\/span>, <span class=\"hljs-selector-tag\">button<\/span> {\n  <span class=\"hljs-attribute\">position<\/span>: relative;\n}\n\n<span class=\"hljs-selector-tag\">a<\/span><span class=\"hljs-selector-pseudo\">:focus-visible<\/span>,\n<span class=\"hljs-selector-tag\">button<\/span><span class=\"hljs-selector-pseudo\">:focus-visible<\/span> {\n  <span class=\"hljs-attribute\">outline<\/span>: none; <span class=\"hljs-comment\">\/* the span is the ring now *\/<\/span>\n}\n\n<span class=\"hljs-selector-tag\">span<\/span><span class=\"hljs-selector-class\">.focus-ring<\/span> {\n  <span class=\"hljs-attribute\">position<\/span>: absolute;\n  <span class=\"hljs-attribute\">inset<\/span>: -<span class=\"hljs-number\">6px<\/span>;\n  <span class=\"hljs-attribute\">border<\/span>: <span class=\"hljs-number\">2px<\/span> solid deeppink;\n  <span class=\"hljs-attribute\">border-radius<\/span>: <span class=\"hljs-number\">6px<\/span>;\n  <span class=\"hljs-attribute\">pointer-events<\/span>: none;\n  <span class=\"hljs-attribute\">opacity<\/span>: <span class=\"hljs-number\">0<\/span>;\n}\n\n<span class=\"hljs-comment\">\/* Visible + named only when the parent is focused *\/<\/span>\n<span class=\"hljs-selector-pseudo\">:is(a<\/span>, <span class=\"hljs-selector-tag\">button<\/span>)<span class=\"hljs-selector-pseudo\">:focus-visible<\/span> &gt; <span class=\"hljs-selector-tag\">span<\/span><span class=\"hljs-selector-class\">.focus-ring<\/span> {\n  <span class=\"hljs-attribute\">opacity<\/span>: <span class=\"hljs-number\">1<\/span>;\n  <span class=\"hljs-attribute\">view-transition-name<\/span>: focus-ring;\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>So when one ring disappears and another appears, that&#8217;s the DOM change that is relevant and that the View Transition will take care of tweening.<\/p>\n\n\n\n\t\t<figure class=\"wp-block-jetpack-videopress jetpack-videopress-player aligncenter wp-block-jetpack-videopress--has-max-width\" style=\"max-width: 616px;\" >\n\t\t\t<div class=\"jetpack-videopress-player__wrapper\"> <iframe title=\"VideoPress Video Player\" aria-label='VideoPress Video Player' width='500' height='348' src='https:\/\/videopress.com\/embed\/M9kFW5bd?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=1770107250'><\/script><\/div>\n\t\t\t<figcaption>How about them apples?<\/figcaption>\n\t\t\t\n\t\t<\/figure>\n\t\t\n\n\n<p>That&#8217;s just the kind of look we were shooting for all along. Still don&#8217;t love binding to the Tab key and all, but overall I think this is an interesting addition to the grand history of flying focus.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_019dff8d-6d05-725b-beba-10710e805dab\" src=\"\/\/codepen.io\/editor\/anon\/embed\/019dff8d-6d05-725b-beba-10710e805dab?height=450&amp;theme-id=1&amp;slug-hash=019dff8d-6d05-725b-beba-10710e805dab&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed 019dff8d-6d05-725b-beba-10710e805dab\" title=\"CodePen Embed 019dff8d-6d05-725b-beba-10710e805dab\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Two Little Bonus Things<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">1) Aspect Ratio During Transitons Is A Thing<\/h3>\n\n\n\n<p>If you&#8217;re ever transitioning elements that might change in aspect ratio, like we are definitely doing here as it&#8217;s totally random focused elements, then read Jake&#8217;s <a href=\"https:\/\/jakearchibald.com\/2024\/view-transitions-handling-aspect-ratio-changes\/\">View transitions: Handling aspect ratio changes<\/a>. Essentially you fiddle with things like <code>object-fit<\/code> to ensure the changes in size between the two elements don&#8217;t render too weird while tweening.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">2) Honor Reduced Motion<\/h3>\n\n\n\n<p>Easy enough. Default cross-fades <em>might<\/em> be OK and not impart too much motion, but automatic movement might still happen, so in my opinion, best to just nuke the animation entirely.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-keyword\">@media<\/span> (<span class=\"hljs-attribute\">prefers-reduced-motion:<\/span> reduce) {\n  <span class=\"hljs-selector-pseudo\">::view-transition-group(<\/span>*),\n  <span class=\"hljs-selector-pseudo\">::view-transition-old(<\/span>*),\n  <span class=\"hljs-selector-pseudo\">::view-transition-new(<\/span>*) {\n    <span class=\"hljs-attribute\">animation<\/span>: none;\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><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<h2 class=\"wp-block-heading\">Other Prior Art<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/n12v.com\/focus-transition\/\">https:\/\/n12v.com\/focus-transition\/<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/focusoverlay.js.org\/\">https:\/\/focusoverlay.js.org\/<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/felixzapata.github.io\/flying-focus-element\/components\/flying-focus-element\/\">https:\/\/felixzapata.github.io\/flying-focus-element\/components\/flying-focus-element\/<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/codepen.io\/hchiam\/pen\/MWKKxdW\">https:\/\/codepen.io\/hchiam\/pen\/MWKKxdW<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Let&#8217;s try a fresh take on animating focus rings around a page. Flying focus, as it were. Only instead of measuring where elements are ourselves, we&#8217;ll let View Transitions figure it out.<\/p>\n","protected":false},"author":1,"featured_media":9575,"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":[100,7,469,3],"class_list":["post-9551","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-animation","tag-css","tag-focus","tag-javascript"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/05\/focus-fly.jpg?fit=2000%2C1200&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/9551","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=9551"}],"version-history":[{"count":11,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/9551\/revisions"}],"predecessor-version":[{"id":9578,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/9551\/revisions\/9578"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media\/9575"}],"wp:attachment":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media?parent=9551"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/categories?post=9551"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/tags?post=9551"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}