Frontend Masters Boost RSS Feed https://frontendmasters.com/blog Helping Your Journey to Senior Developer Fri, 31 Oct 2025 00:00:06 +0000 en-US hourly 1 https://wordpress.org/?v=6.8.3 225069128 View Transitions Feature Explorer https://frontendmasters.com/blog/view-transitions-feature-explorer/ https://frontendmasters.com/blog/view-transitions-feature-explorer/#respond Thu, 30 Oct 2025 23:58:40 +0000 https://frontendmasters.com/blog/?p=7611 It’s a generally good thing to know that browser support for browser features isn’t always quite a simple as yes or no. There can be sub-features involved as things evolve that roll out in browsers at different times. View Transitions is an example of that going on right now. There are “Same-Document View Transitions” supported in all browsers right now, but “Cross-Document View Transitions” are still missing Firefox support. And there are quite a few more related features beyond that! Bramus has a nice way to explore that.

]]>
https://frontendmasters.com/blog/view-transitions-feature-explorer/feed/ 0 7611
View Transition List Reordering (with a Kick Flip) https://frontendmasters.com/blog/view-transition-list-reordering-with-a-kick-flip/ https://frontendmasters.com/blog/view-transition-list-reordering-with-a-kick-flip/#comments Tue, 08 Jul 2025 16:10:25 +0000 https://frontendmasters.com/blog/?p=6471 I remember when we first got animations and transitions in CSS on the web (ok grandpa), the talk around it was balanced between oooo! fun! shiny! and actually, movement is more than aesthetics; it can help people understand what is happening in user interfaces.

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 instantly happens, it can be hard to catch what even happened. But if you animate the movement, it can be extremely obvious what is happening.

Works, but it is not particularly easy to understand what is happening:

More fun and easier to understand what is happening:

The List

We’re talking a regular ol list. Perhaps ironic that we’re ordering and unordered lists, but I’ll leave that as a semantic thoughtworm for the reader.

Each list item has text, then a button which the intended action is that, when clicked, will move the list item to the top.

<ul class="list">
  <li>
    Apples
    <button hidden disabled aria-label="Move to Top">
      <svg ...></svg>
    </button>
  </li>
  <li>
    Oranges
    <button hidden disabled aria-label="Move to Top">
      <svg ...></svg>
    </button>
  </li>
  <li>
    <button hidden disabled aria-label="Move to Top">
      <svg ...></svg>
    </button>
    Mangos
  </li>
  <li>
    <button hidden disabled aria-label="Move to Top">
      <svg ...></svg>
    </button>
    Bananas
  </li>
</ul>

Note that each button has a text label (as we’re not using text inside the button), and a hidden attribute we’ll use to make sure the button isn’t there at all when JavaScript is disabled.

Scaffolding the Interactive JavaScript

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:

const button = document.querySelector("button");
const list = document.querySelector(".list");
let listItems = list.querySelectorAll("li");
const listItemButtons = list.querySelectorAll("li > button");

listItemButtons.forEach((button) => {
  button.hidden = false;
  button.addEventListener("click", async () => {
    // do stuff
  });
});

Moving the List Item to the Top

When the button is clicked, we’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 first list item is, and insertBefore it, making the clicked one the first one.

const button = document.querySelector("button");
const list = document.querySelector(".list");
let listItems = list.querySelectorAll("li");
const listItemButtons = list.querySelectorAll("li > button");

listItemButtons.forEach((button) => {
  button.hidden = false;
  button.addEventListener("click", async () => {
    const item = button.parentElement;
    const firstListItem = list.querySelector(".list :first-child");
    list.insertBefore(item, firstListItem);

    // This is probably the better API to use, but less supported...
    // list.moveBefore(item, firstListItem);
  });
});

I only recently learned about moveBefore which is probably a better API to use, but we can wait a bit for better support.

(Progressively Enhanced) Movement via View Transitions

One type of View Transitions are “same page” View Transitions, where we essentially call document.startViewTransition and change the DOM inside the callback.

const button = document.querySelector("button");
const list = document.querySelector(".list");
let listItems = list.querySelectorAll("li");
const listItemButtons = list.querySelectorAll("li > button");

function moveListItemFirst(item) {
  const firstListItem = list.querySelector(".list :first-child");
  list.insertBefore(item, firstListItem);
}

listItemButtons.forEach((button) => {
  button.hidden = false;
  button.addEventListener("click", async () => {
    const item = button.parentElement;
    if (document.startViewTransition) {
      const transition = document.startViewTransition(() => {
        moveListItemFirst(item);
      });
    } else {
      moveListItemFirst(item);
    }
  });
});

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.

This will immediately do a fade transition for the list items, which honestly isn’t much of an improvement in this case (it still can be nice for the other type of View Transitions: page transitions). Fortunately, we’ve got a pretty decent one-line fix in CSS:

ul {
  li {
    view-transition-name: match-element;
  }
}

If you’ve played with View Transitions before, it’s likely you’ve got in your head that every single element needs a unique view-transition-name. And that’s still true in Firefox for now, as only Chrome and Safari are supporting match-element as I write. But as we’re just playing here, this is such a nice improvement and reduces so much fiddliness, I think it’s worth it.

Special View Transitions Only For the “Main Moving Element”

The deal here is really that all the elements are moving. It’s either the element you clicked on moving to the first position, or the rest of the list items moving out of the way.

So the goal here is to apply a unique view-transition-name to the element that is the “main moving element”, then remove it once it’s done. To make matters a bit more difficult, we’ve got two animations we want to apply, one of the list item, and one just for the icon within the button. That’s slightly tricky!

const button = document.querySelector("button");
const list = document.querySelector(".list");
let listItems = list.querySelectorAll("li");
const listItemButtons = list.querySelectorAll("li > button");

function moveListItemFirst(item) {
  const firstListItem = list.querySelector(".list :first-child");
  list.insertBefore(item, firstListItem);
}

listItemButtons.forEach((button) => {
  button.hidden = false;
  button.addEventListener("click", async () => {
    const item = button.parentElement;

    item.style.viewTransitionName = "woosh";
    item.querySelector("svg").style.viewTransitionName = "tony-hawk";

    if (document.startViewTransition) {
      const transition = document.startViewTransition(() => {
        moveListItemFirst(item);
      });

      try {
        await transition.finished;
      } finally {
        item.style.viewTransitionName = "";
        item.querySelector("svg").style.viewTransitionName = "";
        makeFirstListItemsButtonDisabled();
      }
    } else {
      moveListItemFirst(item);
    }
  });
});

Now we’ve got “woosh” and “tony-hawk” view transition names we can use to apply animation control in CSS.

::view-transition-group(*) {
  animation-duration: 1s;
}

::view-transition-old(woosh),
::view-transition-new(woosh) {
  animation: woosh 1s ease-in-out;
}

@keyframes woosh {
  50% {
    translate: -100px 0;
    scale: 1.5;
    box-shadow: 0 30px 15px lch(0% 0 0 / 50%);
  }
}

::view-transition-old(tony-hawk),
::view-transition-new(tony-hawk) {
  animation: tony-hawk 1s ease-in-out;
}

@keyframes tony-hawk {
  /* sick kick flip */
  50% {
    rotate: 20deg;
    scale: 2.5;
  }
}

So for the “non-main” elements, they just move up and down over 1s. But for the “main” moving element, we’ve got these unique @keyframe animations we apply while the re-ordering is happening. Note that the keyframes are only applying the 50% 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.

Demo

Video

I’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’d enjoy that:

]]>
https://frontendmasters.com/blog/view-transition-list-reordering-with-a-kick-flip/feed/ 4 6471
Relatively New Things You Should Know about HTML Heading Into 2025 https://frontendmasters.com/blog/bone-up-html-2025/ https://frontendmasters.com/blog/bone-up-html-2025/#comments Mon, 06 Jan 2025 15:42:42 +0000 https://frontendmasters.com/blog/?p=4732 Not all of this is like absolutely brand spanking new just-dropped-in-2024 stuff. Some of it is, but generally it’s relatively new stuff that’s all pretty great. I’m pointing things out that I think are really worth knowing about. It’s possible you haven’t kept up too much with HTML developments as it tends to, rightfully, move a lot slower than CSS or JavaScript.

A group of details elements can behave like an accordion, among other improvements, but still have accessibility limitations.

We’ve had cross-browser <details> / <summary> support since 2016, but only recently have the abilities started to expand and clean up.

For one, you can make an “exclusive” accordion by grouping them together via the name attribute:

<details name="group"><summary>One</summary> ... </details>
<details name="group"><summary>At</summary> ... </details>
<details name="group"><summary>A</summary> ... </details>
<details name="group"><summary>Time</summary> ... </details>

Me, I mostly think the only-one-open-at-a-time thing is an anti-pattern (as do others), mostly because it’s weird to close something a user may have intentionally opened via side effect. But the web is a big place and certain specific designs I can see needing it to be effective so I don’t hate that it exists. At least I think using the term “accordion” is now appropriate in this context, but that there are still potential accessibility issues. Like imagine using this for a FAQ section where each question would normally be a header like <h3>, well, the semantics of that <h3> is wiped out by the <summary>, which is a “button”, so that’s not great.

Here’s an example of the accordion pattern being used with a group of horizontally laid out details elements. If more could be opened, it could cause horizontal scroll which I sure we can all imagine also sucks.

Note that those <details> elements are in a flexbox layout and are themselves display: flex; and only recently has that improved. (See Stephanie Stimac’s article on recent improvements.)

Ya know how the inside of a <details> is:

  1. <summary>
  2. … and whatever else

The “whatever else” can be one or more elements, and there isn’t any particularly clean way of selecting them. This is a CSS thing, but now we’ve got a ::details-content pseudo-element selector to get our hands on all that HTML in case it needs similar treatment (imagine fading it all in or out).

Here’s a great demo of using that, as well as other brand new CSS features, to make honest-to-god animating open/close details elements with arbitrary content in just HTML and CSS.

Styleable Selects are Coming

Browsers can’t just all the sudden make every aspect of a <select> and <option>s styleable, otherwise historical CSS that didn’t apply to them all the sudden does and it would wreak untold havoc on websites. The web doesn’t like to roll like that, and I applaud it for that backwards compatibility.

So… there needed to be an opt-in to make it work. A new element can work for that, which for a hot minute seemed like it would be <selectmenu>. But the progressive enhancement story for that basically sucked. So the new opt-in looks like it will be CSS triggered:

select,
::picker(select) {
  appearance: base-select;
}
Demo

Once you’ve opted in, you can apply styling to elements inside the <select> pretty freely, opening up huge doors to designing that experience.

There is some other funky things to know so I’d suggest reading this whole article. Even some new (tricky) HTML!

<select class="country-select">
  <button>
    <selectedoption></selectedoption>
  </button>
  <option value="" hidden>
    <figure></figure>
    <span>Select a country</span>
  </option>
  <option value="andorra">
    <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/1/19/Flag_of_Andorra.svg/120px-Flag_of_Andorra.svg.png" alt="" />
    <span>Andorra</span>
  </option>
  ...
</select>

My favorite episode of Off the Main Thread this year was about styleable selects and all the interesting details behind them that will have knock-on effects.

Oh — also you can slap a line into a select menu

I kinda love how the old school name attribute was used with <details> for the accordion behavior.

And speaking of old school elements, you can put an <hr> within a <select> to just draw a line there (a “horizontal rule” as it were). You’ve still got <optgroup label="label"> for more emphatic grouping, but sometimes a line is all you need.

<select>
   <option>apple</option>
   <option>orange</option>
   <option>banana</option>
   <hr>
   <option>pepper</option>
   <option>squash</option>
   <option>broccoli</option>
</select>

You Can Open/Close a Popover with a Button Alone

No JavaScript is required to make the opening and closing of a popover work.

<button popovertarget="the-popover">Open Popover</button>

<div popover id="the-popover">
  I'm a popover.
  
  <button popovertarget="the-popover">Close Popover</button>
</div>

If you’d prefer that the popover be closed by just a click anywhere outside of it (that’s called a “light dismiss”) then update the popover attribute to popover="auto" and that’ll do it.

The “targetting” you can see happening with those buttons is an example of an “Invoker”, which is poised bring great power to HTML in coming years.

You can’t close a popover with a form submission like you can a <dialog>, but a popover probably isn’t a great place for a form anyway so that’s fine. Another interesting sidenote is you can make a <dialog popover> if you like, getting you this button behavior for free.

There are quite a few differences between dialogs and popovers, and both are awfully useful. Perhaps the most important two features being the focus trap potential and the fact they are promoted to the “top layer” of the rendered site, meaning no futzing with z-index.

The situation we’re in with popovers is that you pretty much need to be OK with either centered or edge-based positioning for them for now, like dialogs. They are just begging for anchor positioning, but the current guess is 2026 for interop on that.

Checkboxes can be Toggle Switches

It’s as easy as:

<input type="checkbox" switch>

Although only Safari supports it for now and it’s not actually specced yet so it could be said they jumped the gun a bit. Main discussion here. And I guess we should call it a “switch” to be proper.

I’m a fan here because the absolutely correct implementation of a toggle/switch was easy to get wrong from an accessibility standpoint, and this seems… hard to get wrong.

Daniel Yuschick has an article digging into the details. I like the idea that pseudo elements specific to this UI will be exposed, like ::thumb and ::track, but I can’t tell you what the status of that is right now. Even the official demos in Safari Nightly Preview with the flag turned on aren’t rendering properly for me.

Wrap your Search

This will be easy to remember. Got an area of your site that is meant for searching? Wrap it.

<search>

</search>

It’s the same as doing <div role="search">, but my bet is that you’ll actually remember to do it.

You probably don’t need noopener noreferrer on links anymore

I’ve used linters on projects that help ensure that a link like:

<a 
  href="https://google.com" 
  target="_blank"
>

Has attributes like this as well:

<a 
  href="https://google.com" 
  target="_blank"
  rel="noopener noreferrer"
>

The problem was, that actually gave the opened pages rights to their referrer: it opened a security hole that could potentially have leaked user information or opened the door to phishing.

Ben Werd

This is not new “2025” information, but I’m only just learning that this isn’t really needed anymore. Chrome was the last to automatically apply this behavior to _blank links and that was 2021. I’ve been doing it as I have a linter that always warns me about it, so in case your browser support targets agree, you might want to check those linter settings.

Declarative Shadow DOM Paves the Way for Better Web Component Frameworks

It used to be that if you wanted a Web Component to use Shadow DOM, the only way to do it was for it to be rendered by JavaScript. This meant that Web Components that wanted or needed to use Shadow DOM had no Server Side Rendering (SSR) story at all. That was a big gap, as all the major UI frameworks have coalesced on the idea that SSR is a good idea for many reasons (performance (perceived and actual), resilience, SEO, hosting options, etc).

Now we’ve got Declarative Shadow DOM and the gap has closed.

I think it’s cool to see the Shadow DOM at work with no JavaScript at all:

Demo

What I hope we’ll see in 2025 and beyond is frameworks actually help use this. It feels like foundational technology that mostly isn’t expected to be written by hand by authors, but instead used by libraries/frameworks to build great authoring experiences around.

React 19 was the last framework to fully correctly support Web Components, so perhaps we’ll see frameworks do more than support them now but embrace them. I would expect to see a “Next.js of Web Components” at some point.

Import Maps

I used to be fond of point out that a line like this isn’t standard JavaScript.

import React from "react";

That looks like ES Modules code, but the fact that the value in quotes doesn’t start with an absolute URL or a . (a relative path) means it’s… not. It’s just a convention that we all got used to writing because JavaScript bundlers understand it to mean “that’s a thing from npm so I should go look for it in the node_modules folder.

That’s changed now, since you can, via HTML Import Maps, map the value “react” to something else via Import Maps.

So if you executed that JavaScript above from an HTML file that included an import map like this:

<script type="importmap">
  {
    "imports": {
      "react": "https://esm.sh/react@18",
      "reactdom": "https://esm.sh/react-dom@18"
    }
  }
</script>

(Basic demo.)

It would work and import the JavaScript from those locations instead. This opens up the door for not needing to use a bundler and still having the benefit of an abstraction for importing dependencies. Having the possibility to avoid tooling can be huge for long term maintenance of projects.

Don’t Sleep on the inert Attribute

You can make an element, and the entire chunk of DOM under it, ignored completely from an interactivity perspective, just by by using the inert attribute. It’s quite powerful. No clicks, no focus, the element(s) are gone from the accessibility tree, nothing can be selected, heck, the on-page “find” command won’t even find text within there.

If you’ve got any reason at all to put stuff in the DOM but have it essentially behave as if it isn’t there, make it inert until you are ready for it not to be. Imagine a multi-step form that is all in the DOM right away, but only one step at a time is not inert, so future or previous form controls aren’t accidentally used until ready.

I’d tell you this is ideal for implementing modals, but you get this behavior for free, and easier because it can be placed anywhere, just by using <dialog>.

Keep your find-on-page working properly

Another interesting attribute here. We’ve long had hidden as an attribute (even though it’s kinda weak). The change here is it taking a value, like hidden="until-found". That will hide the element as hidden does, but the content within it will still be findable with on-page text search. When it is found, it’s on you to react to the DOM event beforematch to un-hide (by removing the attribute) the content so it can be seen the hidden attribute is removed automatically, plus you’ve got the beforematch event to hook into if you need to do additional work (thx Nathan).

Here’s the demo from chrome for developers, which you might need to try in Debug View for it to trigger properly.

You’d think this would be useful for <details> elements in how they hide content, but this behavior is baked into them. This is more solid evidence for using native features — because they get the details right (generally) rather than you needing to replicate them.

Multi-Page View Transitions

You have to opt-in to this via CSS like:

@view-transition {
  navigation: auto;
}

Then once you have, regular ol’ clicked links that move to a new page (on the same domain) can have view transitions. That is, you’ll see the page use a fade effect by default. But this unlocks an amazing amount of animation control over the transition between those two page loads.

I’m listing this as an HTML feature, because I find most of the useful-ness of multi-page view transitions are unlocked in the HTML. For instance…

<!-- homepage -->
<div class="card">
  <h3 
    style="view-transition-name: post-title-087afd;"
  >Blog Post Title</h3>
  <p>...</p>
  <a href="/blog-post">Read the article Blog Post Title</a>
</div>

<!-- blog post -->
<article>
  <h1 
    style="view-transition-name: post-title-087afd;"
  >Blog Post Title</h1>
  <p>...</p>
</article>

Above, I’m imagining the “card” as a component that is generated from data and is likely one of many on the page. So it requires having a unique view-transition-name, and really the only good opportunity to apply that is in HTML.

Responsive Video is catching up to Responsive Images

With the <picture> element in HTML we get the ability to use the <source> element within and control exactly when we swap out to different source images. It’s a powerful concept that can offer big performance wins.

That idea actually originally came from a concept with the <video> tag, but then that was bizarrely removed from (most) browsers. But now it’s back thanks to some serious work by Scott Jehl and others he convinced to help the revival along.

You can do media attribute control the sources, which will probably mostly be used for width query stuff, but it can do anything media can do. Scott wrote up some examples here, like:

<video autoplay controls playsinline muted loop>
  <source media="(orientation: landscape)" src="sunset-landscape-1080.mp4">
  <source src="sunset-portrait-1080.mp4">
</video>

HTML Imports are Coming Back

Just kidding they totally aren’t.

Did we miss anything?

Any newfangled HTML out there you’ve been eyeing up or using?

]]>
https://frontendmasters.com/blog/bone-up-html-2025/feed/ 17 4732
View Transitions Staggering https://frontendmasters.com/blog/view-transitions-staggering/ https://frontendmasters.com/blog/view-transitions-staggering/#comments Tue, 22 Oct 2024 16:42:53 +0000 https://frontendmasters.com/blog/?p=4232 I love view transitions. When you’re using view transitions to move multiple items, I think staggering them is cool effect and a reasonable ask for doing so succinctly. While I was playing with this recently I learned a lot and a number of different related tech and syntax came up, so I thought I’d document it. Blogging y’all, it’s cool. You should.

Example

So let’s say we have a menu kinda thing that can open & close. It’s just an example, feel free to use your imagination to consider two states of any UI with multiple elements. Here’s ours:

Closed
Open

View Transitions is a great way to handle animating this menu open. I won’t beat around the bush with a working example. Here’s that:

That works in all browsers (see support). It animates (with staggering) in Chrome and Safari, and at this time of this writing, just instantly opens and closes in Firefox (which is fine, just less fancy).

Unique View Transition Names

In order to make the view transition work at all, every single item needs a unique view-transition-name. Otherwise the items will not animate on their own. If you ever seen a view transition that has a simple fade-out-fade-in, when you were trying to see movement, it’s probably a problem with unique view-transition-names.

This brings me to my first point. Generating unique view-transition-names is a bit cumbersome. In a “real world” application, it’s probably not that big of a deal as you’ll likely be using some kind of templating that could add it. Some variation of this:

<div class="card"
     style="view-transition-name: card-<%= card.id %>">

<!-- turns into -->

<div class="card" 
     style="view-transition-name: card-987adf87aodfasd;">

But… you don’t always have access to something like that, and even when you do, isn’t it a bit weird that the only real practical way to apply these is from the HTML and not the CSS? Don’t love it. In my simple example, I use Pug to create a loop to do it.

#grid
  - const items = 10;
  - for (let i = 0; i < items; i++)
    div(style=`view-transition-name: item-${i};`)

That Pug code turns into:

<div id="grid">
  <div style="view-transition-name: item-0;"></div>
  <div style="view-transition-name: item-1;"></div>
  <div style="view-transition-name: item-2;"></div>
  <div style="view-transition-name: item-3;"></div>
  <div style="view-transition-name: item-4;"></div>
  <div style="view-transition-name: item-5;"></div>
  <div style="view-transition-name: item-6;"></div>
  <div style="view-transition-name: item-7;"></div>
  <div style="view-transition-name: item-8;"></div>
  <div style="view-transition-name: item-9;"></div>
</div>

Jen Simmons made the point about how odd this is.

This is being improved, I hear. The CSSWG has resolved to

Add three keywords, one for ID attribute, one for element identity, and one that does fallback between the two.

Which sounds likely we’ll be able to do something like:

#grid {
  > div {
    view-transition-name: auto; 
  }
}

This makes me think that it could break in cross-document view transitions, but… I don’t think it actually will if you use the id attribute on elements and the view-transition-name ends up being based on that. Should be sweet.

Customizing the Animation

We’ve got another issue here. It wasn’t just a Pug loop need to pull of the view transition staggering, it’s a Sass loop as well. That’s because in order to control the animation (applying an animation-delay which will achieve the staggering), we need to give a pseudo class selector the view-transition-name, which are all unique. So…

::view-transition-group(item-0) {
  animation-delay: 0s;
}
::view-transition-group(item-1) {
  animation-delay: 0.01s;
}
::view-transition-group(item-0) {
  animation-delay: 0.02s;
}
/* etc. */

That’s just as cumbersome as the HTML part, except maybe even more-so, as it’s less and less common we even have a CSS processor like Sass to help. If we do, we can do it like this:

@for $i from 0 through 9 {
  ::view-transition-group(item-#{$i}) {
    animation-delay: $i * 0.01s;
  }
}

Making Our Own Sibling Indexes with Custom Properties

How much do we need to delay each animation in order to stagger it? Well it should be a different timing, probably increasing slightly for each element.

1st element = 0s delay
2nd element = 0.01s delay
3rd element - 0.02s delay
etc

How do we know which element is the 1st, 2nd, 3rd, etc? Well we could use :nth-child(1), :nth-child(2) etc, but that saves us nothing. We still have super repetitive CSS that all but requires a CSS processor to manage.

Since we’re already applying unique view-transition-names at the HTML level, we could apply the element’s “index” at that level too, like:

#grid
  - const items = 10;
  - for (let i = 0; i < items; i++)
    div(style=`view-transition-name: item-${i}; --sibling-index: ${i};`) #{icons[i]}

Which gets us that index as a custom property:

<div id="grid">
  <div style="view-transition-name: item-0; --sibling-index: 0;"> </div>
  <div style="view-transition-name: item-1; --sibling-index: 1;"> </div>
  <div style="view-transition-name: item-2; --sibling-index: 2;"> </div>
  <div style="view-transition-name: item-3; --sibling-index: 3;"> </div>
  <div style="view-transition-name: item-4; --sibling-index: 4;"> </div>
  <div style="view-transition-name: item-5; --sibling-index: 5;"> </div>
  <div style="view-transition-name: item-6; --sibling-index: 6;"> </div>
  <div style="view-transition-name: item-7; --sibling-index: 7;"> </div>
  <div style="view-transition-name: item-8; --sibling-index: 8;"> </div>
  <div style="view-transition-name: item-9; --sibling-index: 9;"> </div>
</div>

… but does that actually help us?

Not really?

It seems like we should be able to use that value rather than the CSS processor value, like…

@for $i from 0 through 9 {
  ::view-transition-group(item-#{$i}) {
    animation-delay: calc(var(--sibling-index) * 0.01s);
  }
}

But there are two problems with this:

  1. We need the Sass loop anyway for the view transition names
  2. It doesn’t work

Lolz. There is something about the CSS custom property that doesn’t get applied do the ::view-transition-group like you would expect it to. Or at least *I* would expect it to. 🤷

Enter view-transition-class

There is a way to target and control the CSS animation of a selected bunch of elements at once, without having to apply a ::view-transition-group to individual elements. That’s like this:

#grid {
  > div {
    view-transition-class: item;
  }
}

Notice that’s class not name in the property name. Now we can use that to select all the elements rather than using a loop.

/* Matches a single element with `view-transition-name: item-5` */
::view-transition-group(item-5) {
  animation-delay: 0.05s;
}

/* Matches all elements with `view-transition-class: item` */
::view-transition-group(*.item) {
  animation-delay: 0.05s;
}

That *. syntax is what makes it use the class instead of the name. That’s how I understand it at least!

So with this, we’re getting closer to having staggering working without needing a CSS processor:

::view-transition-group(*.item) {
  animation-delay: calc(var(--sibling-index) * 0.01s);
}

Except: that doesn’t work. It doesn’t work because --sibling-index doesn’t seem available to the pseudo class selector we’re using there. I have no idea if that is a bug or not, but it feels like it is to me.

Real Sibling Index in CSS

We’re kinda “faking” sibling index with custom properties here, but we wouldn’t have to do that forever. The CSSWG has resolved:

sibling-count() and sibling-index() to css-values-5 ED

I’m told Chrome is going to throw engineering at it in Q4 2024, so we should see an implementation soon.

So then mayyyyybe we’d see this working:

::view-transition-group(*.item) {
  animation-delay: calc(sibling-index() * 0.01s);
}

Now that’s enabling view transitions staggering beautifully easily, so I’m going to cross my fingers there.

Random Stagger

And speaking of newfangled CSS, random() should be coming to native CSS at some point somewhat soon as well as I belive that’s been given the thumbs up. So rather than perfectly even staggering, we could do like…

::view-transition-group(*.item) {
  animation-delay: calc(random() * 0.01s);
}

Faking that with Sass if fun!

Sibling Count is Useful Too

Sometimes you need to know how many items there are also, so you can control timing and delays such that, for example, the last animation can end when the first one starts again. Here’s an example from Stephen Shaw with fakes values as Custom Properties showing how that would be used.

One line above could be written removing the need for custom properties:

/* before */
animation-delay: calc(2s * (var(--sibling-index) / var(--sibling-count)));

/* after */
animation-delay: calc(2s * (sibling-index() / sibling-count()));

Overflow is a small bummer

I just noticed while working on this particular demo that during a view transition, the elements that are animating are moved to something like a “top layer” in the document, meaning they do not respect the overflow of parent elements and whatnot. See example:

Don’t love that, but I’m sure there are huge tradeoffs that I’m just not aware of. I’ve been told this is actually a desirable trait of view transitions 🤷.

p.s. DevTools Can Inspect This Stuff

In Chrome-based browsers, open the Animations tab and slow down the animations way down.

The mid-animation, you can use that Pause icon to literally stop them. It’s just easier to see everything when it’s stopped. Then you’ll see a :view-transition element at the top of the DOM and you can drill into it an inspect what’s going on.

]]>
https://frontendmasters.com/blog/view-transitions-staggering/feed/ 3 4232
Fanout with Grid and View Transitions https://frontendmasters.com/blog/fanout-with-grid-and-view-transitions/ https://frontendmasters.com/blog/fanout-with-grid-and-view-transitions/#respond Mon, 14 Oct 2024 14:26:51 +0000 https://frontendmasters.com/blog/?p=4184 I got a little nerdsniped by Preethi’s post CSS Fan Out with Grid and @property the other day. I like the idea of a opening a menu of items where the layout is powered by CSS grid. Then it collapses back into just one cell of the grid. You can even animate the grid columns/rows themselves to pull this off, as Preethi demonstrated. If you know how many columns/rows you want, you can animate that number up and down.

I found the animations just a bit less smooth than I’d like to see, generally. The smoothness depends on lots of factors, like how many columns/rows there are, how long the duration is, and how big those columns/rows are. But imagine 3 rows collapsing to 1 over a full second. Since what is being animated is an integer. The best that can do is have two keyframes (3 to 2, 2 to 1) at 500ms each. It will not feel smooth. Preethi smoothed it over by animating the heights of items too, but the column/rows changes can’t be smoothed over (there can never be 1.5 rows, for example).

My mind went right to View Transitions. Particularly the “same page” style of View Transitions you can call with the JavaScript API document.startViewTransition. With it, we actually don’t even need CSS transitions/animations at all. Weird right?! We just alter the DOM (by changing a class and letting CSS do it’s thing) inside a startViewTransition function, and the browser will automatically tween any elements with unique view-transition-name values.

Here’s me re-creating a similar layout to the fan out navigation in Preethi’s article:

Above, the #grid uses CSS grid to make an 1-column 7-row grid. By default all items are placed in the 4th grid row, making a “closed” state. When the open class is applied to the grid, the grid-row is replaced with auto letting them fall to where they normally would in the grid (the “fan out”). The item in the middle position is just styled differently to look and act as a toggle.

Here’s a video if you’re on a device that doesn’t support View Transitions

In that above example, the space the grid occupies is the same in both states, but that wouldn’t need to be the case. If you want to alter the grid columns/rows, thus changing the dimensions and nature of the grid, then view transition between those states, you can absolutely do that too.

There really is no limit to what you want to do with the grid in the open and closed state. You don’t even have to think of the “states” in that way, although I do find it satisfying myself. Here’s many more items laid out on a grid with both columns and rows:

When I was poking around with that demo, it felt it was just begging for “staggered transitions”, that is, animations that occur with a slight time delay between each element. I’m eyeing up future CSS that looks like it’s going to help with this, but we can actually do it now even using the view transitions we already have.

I used Pug to create the HTML because it’s so repetitive and a processor can help abstract that and make it easier to update, but ultimately the HTML is like this:

<div id="grid">
  <div class="item" style="view-transition-name: item-0"><a>0</a>
  </div>
  <div class="item" style="view-transition-name: item-1"><a>1</a>
  </div>
  <div class="item" style="view-transition-name: item-2"><a>2</a>
  </div>

  ...

We can target each one of those items with unique view-transition-specific CSS and apply the animation-delay there. I used a Sass loop for the same reason as above, but ultimately the CSS looks like:

::view-transition-group(item-0) {
  animation-delay: 0s;
}
::view-transition-group(item-1) {
  animation-delay: 0.01s;
}
::view-transition-group(item-2) {
  animation-delay: 0.02s;
}
...

That ends up like this:

I find that terribly pleasing.

Again I’m leaving the entire grid in place here rather than changing the amount or size of any of the columns/rows. But you could, and it wouldn’t be terribly different. It might actually be smart so the “closed” state isn’t taking up as much space in the flow.

Again if a browser doesn’t support this kind of view transition (Firefox, at the time of writing), it doesn’t matter, it will still toggle open and closed just fine, just without animation.

]]>
https://frontendmasters.com/blog/fanout-with-grid-and-view-transitions/feed/ 0 4184
View transitions + speculative rules https://frontendmasters.com/blog/view-transitions-speculative-rules/ https://frontendmasters.com/blog/view-transitions-speculative-rules/#respond Thu, 11 Jul 2024 22:21:58 +0000 https://frontendmasters.com/blog/?p=3000 Ryan Seddon makes clear the potential performance problem with cross-page View Transitions:

… on a slow or spotty network, the transition may appear as if the screen is freezing, as the browser waits for the page to load before it can transition smoothly between the two screens—this is not ideal.

But also that our new friend the Speculation Rules API is a potential remedy:

Combining these two helps mitigate the original tradeoff of the “pause” between navigations while the browser loads the next document. With speculative prerender, it can render the page before the user clicks, making the transition near-instant.

Both these APIs are Chrome’n’friends only, so I guess it’s a you-break-it you-fix-it deal. They are also both progressive enhancements so no grave harm in using them now, unless you consider potentially unused pre-renders too wasteful.

]]>
https://frontendmasters.com/blog/view-transitions-speculative-rules/feed/ 0 3000
View Transitions Break Incremental Rendering https://frontendmasters.com/blog/view-transitions-break-incremental-rendering/ https://frontendmasters.com/blog/view-transitions-break-incremental-rendering/#respond Thu, 20 Jun 2024 10:54:30 +0000 https://frontendmasters.com/blog/?p=2771 I’m a big fan of View Transitions. Part of what they can do, animating elements across fresh page loads, is a uniquely powerful ability. The performance story, though, is a bit complicated. You might think because you don’t have to reach for a JavaScript framework with Single Page App abilities to do this, it could be a big performance win. That’s a fair take. But it’s also important to know that View Transitions Break Incremental Rendering, as Eric Portis notes.

Consider: how does the browser know which elements are supposed to render across those page loads? Well, it’ll need to fully render the 2nd page to know that, meaning it can’t incrementally render it for us, which means there is technically an increased delay which is not a good performance characteristic.

Good news though. What if the browser has already rendered that 2nd page? That could be the case with the Speculation Rules API we’ve started covering. I also might argue that a site with nice animations between pages might help with the perceived performance.

]]>
https://frontendmasters.com/blog/view-transitions-break-incremental-rendering/feed/ 0 2771
Deciding on Using a Browser Feature via Baseline https://frontendmasters.com/blog/deciding-on-using-a-browser-feature-via-baseline/ https://frontendmasters.com/blog/deciding-on-using-a-browser-feature-via-baseline/#respond Tue, 04 Jun 2024 15:51:00 +0000 https://frontendmasters.com/blog/?p=2526 Google has this little widget called Baseline. Here’s a screenshot example:

The idea is for it to accompany a web platform feature so you can have a sense of what browsers support it. Web developers are rightfully skeptical of new web tech, wanting to know when they can actually use a feature, so this is helpful.

But there is crucial information missing here. Developers don’t decide to use a feature only based on browser support, but also:

  1. Based on if that feature could be considered a progressive enhancement
  2. Based on if that feature has a polyfill

Jeremy Keith brought this up and has a great point.

Not all browser features work the same way. For features that work as progressive enhancements you don’t need to wait for them to be widely available.

For example, if you’re looking at the View Transitions API, you’ll see:

And perhaps go awww bummer I can’t use it! But that would be wrong. You can use it. The API is designed such that you can write code that will use it if it’s available or not if it’s not. It’s really not a big deal.

That’s a beautiful part of progressive enhancement, as Jeremy says:

… there’s a real irony in a common misunderstanding around progressive enhancement: some people seem to think it’s about not being able to use advanced browser features. In reality it’s the opposite. Progressive enhancement allows you to use advanced browser features even before they’re widely supported.

]]>
https://frontendmasters.com/blog/deciding-on-using-a-browser-feature-via-baseline/feed/ 0 2526
New Code Requirement for Multi Page View Transitions https://frontendmasters.com/blog/new-code-requirement-for-multi-page-view-transitions/ https://frontendmasters.com/blog/new-code-requirement-for-multi-page-view-transitions/#respond Fri, 31 May 2024 01:50:00 +0000 https://frontendmasters.com/blog/?p=2462 If you want to use multi-page view transitions, this used to be a prerequisite:

<meta name="view-transition" content="same-origin">

That’s dead. Now you do this:

@view-transition {
  navigation: auto;
}

Thanks to Bramus for the PSA and update article.

I imagine we’ll see PRs a lot like this!

Real quick here, I think the new Speculation Rules API is worth a mention. See, there is an immediate performance cost to using multi-page view transitions. In order for the browser to understand what and how to transition, it needs to essentially render the page you are navigating to behind-the-scenes, then navigate to it, causing a bit of a delay. Hopefully that’s mitigated by the transition itself a bit, but any performance hit sucks. If the page was already pre-rendered (like it might be with this new API available), it should be smooth as butter.

]]>
https://frontendmasters.com/blog/new-code-requirement-for-multi-page-view-transitions/feed/ 0 2462
The View Transitions API https://frontendmasters.com/blog/the-view-transition-api/ https://frontendmasters.com/blog/the-view-transition-api/#comments Wed, 03 Apr 2024 17:50:59 +0000 https://frontendmasters.com/blog/?p=1541 Like anyone, I love a good “native-feeling” experience on the web. I’ve really enjoyed creating that vibe with the new View Transitions API. Luckily it’s is pretty clean to work with, both with the Astro framework, and out of the box!

View Transition Options

Transitioning UI between states has been something that developers have been doing for ages, and yes, you can use things like the Web Animation API as well as CSS transitions and animations for that. But, handling transitions when elements are removed from the page, or you navigate away from a given page, is much harder, particularly when you’re thinking about assistive tech, scroll position, and focus… it’s gnarly.

Untiiiiil now! The View Transitions API has been evolving in the browser both as a proper W3C Candidate Recommendation, and as of this week, the draft for cross-document navigation is in a public working draft!

What does that mean? It means you can have that “native” animation experience, right in the browser! But more specifically, currently you can use the View Transitions API:

  1. For transitions on normal page-to-page loading
  2. For transitions on a single page application (SPA)
  3. For transitions of the DOM changes without a page change

View Transitions + Astro

My favorite way to actually see this implemented so far is using the Astro framework. They have a really great API that allows you to use View Transitions on a single page, or turn on “SPA mode” and navigate across your entire application and have transitions between pages and elements.

Out of the box, Astro supports:

  • Out of the box animations like fade and slide
  • Navigation forwards and backwards
  • Support for prefers-reduced-motion
  • And moooore

Using View Transitions in Astro

Adding the View Transitions API to an Astro page is just two lines of code: importing it in your frontmatter, and adding it to the <head>.

---
import { ViewTransitions } from 'astro:transitions';
---

<html lang="en">
  <head>
    <!-- ... -->
    <ViewTransitions />
  </head>
  <body>
    <!-- ... -->
  </body>
</html>

If you put these lines in a layout component that’s shared across pages, that automatically enables View Transition-powered client-side navigation, fading between pages.

Astro automatically corresponds elements between two pages by their type and location within the DOM, so if you want something simple, this is all you need!

Customize more with directives

You can have more fine-grained control of what your transitions do by using the transition:* directives.

  • transition:name overrides the default matching that Astro does between elements. Make sure when you use this one, you use unique names, kind of like an id.
  • transition:animate overrides the default fade animation, and you can customize what animations you want.
  • transition:persist overrides how Astro replaces elements across pages and lets you persist components and elements across pages.

Example

I built a little demo for you to see the View Transitions API in action!

There’s two main pages to make this little “city directory” site, index.astro, and [city].astro, which pull from a locations.json file.

In index.astro, we loop through the locations:

<ul>
    {locations.map((city) => {
        return (
            <li>
                <span transition:name={`${city.emoji} emoji`}>{city.emoji}</span>
                <a href={`/city/${city.city.toLowerCase()}`} transition:name={`${city.city} link`}>
                    {city.city}, {city.country}
                </a>
            </li>
        )
    })}
</ul>    

In this, note the two transition names on the emoji and on the link, unique for each city.

In [city].astro (which is a dynamic route generated per city), we map those transition names to different elements:

<h2 transition:name={`${city} link`}>{city}, {country}</h2>

<p>
    <span transition:name={`${city} emoji`}>{emoji}</span> {description}
</p>

So now, when we navigate between the home page and a city page (and back), the heading transitions from the link, and the emoji moves from the link to the paragraph!

Using View Transitions without Astro

If your framework of choice is not Astro, but you still want to try out the View Transitions API, it does work in Chrome, Edge, and Opera right now out of the box (Safari and Firefox require polyfilling).

You can check out the documentation here. It’s a bit more manual when you want to build with it from scratch, but you do get a lot of control!

Here’s a very basic example of transitioning between some text values with the API:

Note that the CSS lets you control the transition, similar to how traditional CSS animations work! You assign an animation with view-transition-name, and then you give the view transition pseudo-elements get assigned animations in and out (via ::view-transition-old and ::view-transition-new)!

Read more

This is just scratching the surface of what the View Transitions API can do! You can find some more resources here:

]]>
https://frontendmasters.com/blog/the-view-transition-api/feed/ 1 1541