<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Stefan Judis</title>
    <description>The latest articles on DEV Community by Stefan Judis (@stefanjudis).</description>
    <link>https://dev.to/stefanjudis</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F71681%2Fe3475e89-9f65-4fec-92ec-9d86013fe56e.png</url>
      <title>DEV Community: Stefan Judis</title>
      <link>https://dev.to/stefanjudis</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/stefanjudis"/>
    <language>en</language>
    <item>
      <title>Web Weekly 171 — ::details-content goes baseline, the `closed-by` attribute, and `field-sizing: content`.</title>
      <dc:creator>Stefan Judis</dc:creator>
      <pubDate>Tue, 21 Oct 2025 04:10:00 +0000</pubDate>
      <link>https://dev.to/webweekly/web-weekly-171-details-content-goes-baseline-the-closed-by-attribute-and-field-sizing-4f5c</link>
      <guid>https://dev.to/webweekly/web-weekly-171-details-content-goes-baseline-the-closed-by-attribute-and-field-sizing-4f5c</guid>
      <description>&lt;h2&gt;
  
  
  Guten Tag! Guten Tag! 👋
&lt;/h2&gt;

&lt;p&gt;Do you know what core web vitals metrics work across browsers? Have you used the &lt;code&gt;closed-by&lt;/code&gt; attribute? And is AI really saving time?&lt;/p&gt;

&lt;p&gt;Turn on the Web Weekly tune and find some answers below. Enjoy!&lt;/p&gt;




&lt;p&gt;Kate listens to &lt;a href="https://www.youtube.com/watch?v=VgakpSoHEiE" rel="noopener noreferrer"&gt;"Dick Around" by Sparks&lt;/a&gt; and says:&lt;/p&gt;

&lt;p&gt;"I'm not saying this is my work anthem, but every day, every day, every night, every night..."&lt;/p&gt;

&lt;p&gt;Do you want to share your favorite song with the Web Weekly community? &lt;a href="//mailto:stefan@webweekly.email"&gt;Hit reply&lt;/a&gt;; there are &lt;strong&gt;five more songs&lt;/strong&gt; left in the queue.&lt;/p&gt;




&lt;p&gt;Last week, &lt;a href="https://www.stefanjudis.com/blog/web-weekly-170/" rel="noopener noreferrer"&gt;I opened Web Weekly with CSS functions (&lt;code&gt;@function&lt;/code&gt;)&lt;/a&gt; and how they can be used to create a custom &lt;code&gt;--light-dark()&lt;/code&gt; function. I learned some things from the reactions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/light-dark" rel="noopener noreferrer"&gt;The existing &lt;code&gt;light-dark()&lt;/code&gt; function&lt;/a&gt; only works with color values. Why's that?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.bram.us/2023/10/09/the-future-of-css-easy-light-dark-mode-color-switching-with-light-dark/#schemed-value" rel="noopener noreferrer"&gt;Bramus blogged about it back in 2023&lt;/a&gt; and the answer is to ship &lt;em&gt;something&lt;/em&gt;. The main idea that led to &lt;code&gt;light-dark()&lt;/code&gt; was to have a flexible &lt;code&gt;schemed-value()&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpc4nzmvq1kzbx6c1l2n2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpc4nzmvq1kzbx6c1l2n2.png" alt="Screenshot 2025-10-20 at 13.59.23:root {   color-scheme: dark light custom; }  body {   color: schemed-value(     light hotpink,                dark lime,                    --custom rebeccapurple      ); }" width="800" height="497"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Specifications are complicated and folks preferred to ship &lt;code&gt;light-dark()&lt;/code&gt; over figuring out how to have a highly dynamic function which might have taken "a while". If you want to follow further conversations, here's a spec issue discussing &lt;a href="https://github.com/w3c/csswg-drafts/issues/12513" rel="noopener noreferrer"&gt;how &lt;code&gt;light-dark()&lt;/code&gt; could work for images&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But are there other ways to change non-color styles based on the current color scheme? &lt;a href="https://bsky.app/profile/anatudor.bsky.social/post/3m3at3skvnk2w" rel="noopener noreferrer"&gt;Ana advocated for a boolean toggle trick&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8kzk72gllvwcq9wb1smr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8kzk72gllvwcq9wb1smr.png" alt=".my-elem {   border: solid;   border-width: calc(var(--dark)*var(--b1) + var(--light)*var(--b0));   background: url(light.svg), url(dark.svg);   background-size: calc(var(--light)*100%), calc(var(--dark)*100%) }" width="800" height="258"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To be honest, I'm not the biggest fan of these because they're unreadable in my opinion, but hey... use what works for you.&lt;/p&gt;




&lt;p&gt;If you enjoy Web Weekly, share it with your friends and family.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🦋 &lt;a href="https://bsky.app/profile/stefanjudis.com/post/3m3ofwmibtk2z" rel="noopener noreferrer"&gt;Bluesky&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🐘 &lt;a href="https://front-end.social/@stefan/115410072903641500" rel="noopener noreferrer"&gt;Mastodon&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💼 &lt;a href="https://www.linkedin.com/posts/stefan-judis_with-a-bit-of-delay-because-the-internet-activity-7386245409985277954-wUEs" rel="noopener noreferrer"&gt;Linkedin&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A quick "repost" really helps this indie newsletter out. Thank you! ❤️&lt;/p&gt;




&lt;h2&gt;
  
  
  Something that made me smile this week
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F13vek9p9204iwri2javk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F13vek9p9204iwri2javk.png" alt="Previews of multiple 3d web portfolios" width="800" height="412"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I admire people that take the time and effort to create truly unique online portfolios. This post highlights 6 personal websites of which I really don't want to know how long it took to create them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mattcool.tech/posts/amazing-web-portfolios" rel="noopener noreferrer"&gt;Stand out&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  No code
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://tetralogical.com/blog/2025/10/14/common-misconceptions-about-screen-readers/" rel="noopener noreferrer"&gt;Common misconceptions about screen readers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.w3.org/blog/2025/w3c-logo-refresh-more-than-a-cosmetic-change-a-small-step-towards-durable-and-sustainable-success/" rel="noopener noreferrer"&gt;The W3C has a new logo!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://herman.bearblog.dev/being-present/" rel="noopener noreferrer"&gt;Smartphones and being present&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Are editor themes too "loud"?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzhudc8w5ffjyk12e6smi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzhudc8w5ffjyk12e6smi.png" alt="Limit the number of different color to what you can remember." width="800" height="278"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I've been happily using the seti theme for years because it's colorful and bright. Niki argues that most themes out there highlight too much. And what should I say, I think he's right and I'll give another theme a try!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://tonsky.me/blog/syntax-highlighting/" rel="noopener noreferrer"&gt;Calm your editor&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;closed-by=any&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/f20lfrunubsq/1aM0fGLHBo6GwKRC1oUTfn/dd610ec93f01bfd92d4ba39fed0a856d/Screenshot_2025-10-20_at_14.50.55.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/f20lfrunubsq/1aM0fGLHBo6GwKRC1oUTfn/dd610ec93f01bfd92d4ba39fed0a856d/Screenshot_2025-10-20_at_14.50.55.png" alt="&amp;lt;dialog closedBy=&amp;quot;any&amp;quot;&amp;gt;   &amp;lt;p&amp;gt;Hi, I'm a dialog.&amp;lt;/p&amp;gt; &amp;lt;/dialog&amp;gt;"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Adam argues that there aren't enough people talking about &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/closedBy" rel="noopener noreferrer"&gt;the &lt;code&gt;closed-by&lt;/code&gt; attribute&lt;/a&gt;. And he's right!&lt;/p&gt;

&lt;p&gt;&lt;code&gt;closed-by&lt;/code&gt; is the declarative way to tell open dialogs when they should close. The property doesn't work in Safari yet, but you can bring in a short JS snippet.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://nerdy.dev/closedby-any" rel="noopener noreferrer"&gt;Declare&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  When the virtual keyboard messes with your viewport
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fggqohbg72bv1me2dyitq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fggqohbg72bv1me2dyitq.png" alt='&amp;lt;meta name="viewport"    content="width=device-width,             initial-scale=1.0,             interactive-widget=resizes-content"&amp;gt;' width="800" height="227"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Maybe you know the problem, you're on your phone, want to edit some data, the virtual keyboard opens and whoops, important fixed-positioned elements are gone. Where did they go?&lt;/p&gt;

&lt;p&gt;Bramus explained how the different viewports (yes, multiple!) are affected by the keyboard and how the fairly new &lt;code&gt;interactive-widget&lt;/code&gt; meta element value can help.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://htmhell.dev/adventcalendar/2024/4/" rel="noopener noreferrer"&gt;Embrace the view(port)&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;header&lt;/code&gt; and &lt;code&gt;footer&lt;/code&gt; elements change their roles
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw3ag5k3gq074oycn273x.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw3ag5k3gq074oycn273x.jpg" alt="Footer elements on the root level get  raw `contentinfo` endraw  assigned whereas  raw `footer` endraw  elements in a  raw `main` endraw  element get  raw `sectionfooter` endraw , " width="800" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You're probably using &lt;code&gt;header&lt;/code&gt; and &lt;code&gt;footer&lt;/code&gt; elements to improve HTML semantics and accessibility, but do you know that the elements don't always get their initial ARIA roles assigned?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.stefanjudis.com/today-i-learned/header-and-footer-elements-lose-their-roles-in-sectioning-content/" rel="noopener noreferrer"&gt;Give some structure&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Wowza! Would you enjoy getting &lt;a href="https://www.webweekly.email" rel="noopener noreferrer"&gt;Web Weekly&lt;/a&gt; straight to your inbox?&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The wonderful weird web – spurious correlations
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftcsdlndcce81zzh8p58x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftcsdlndcce81zzh8p58x.png" alt="Correlation between " width="800" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I've got nothing but love for the person maintaining this site highlighting correlations that don't look "quite right".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.tylervigen.com/spurious-correlations" rel="noopener noreferrer"&gt;Check the numbers&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;field-sizing&lt;/code&gt; works for &lt;code&gt;input&lt;/code&gt; and &lt;code&gt;select&lt;/code&gt; elements, too!
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhveyhux56eqaz795o8l2.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhveyhux56eqaz795o8l2.gif" alt="Input, select and textarea elements expanding depending on the content." width="626" height="304"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's a quick one: have you heard about the &lt;code&gt;field-sizing&lt;/code&gt; property? The new prop allows form controls to grow depending on their content. I thought this will only be about &lt;code&gt;textarea&lt;/code&gt;s, but it works for &lt;code&gt;input&lt;/code&gt; and &lt;code&gt;select&lt;/code&gt; elements, too!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.stefanjudis.com/today-i-learned/field-sizing-is-about-more-than-textareas/" rel="noopener noreferrer"&gt;Grow!&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  25 CSS you should know
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F59jatoaxnoybla70lvuv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F59jatoaxnoybla70lvuv.png" alt="Adam on stage next to his slides showing @property" width="800" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's Adam giving a whirlwind tour about the latest and greatest CSS features. It'll be well worth your time!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=QW6GECIzvsw" rel="noopener noreferrer"&gt;Watch the talk&lt;/a&gt; &lt;a href="https://cascadiajs-2025.netlify.app/" rel="noopener noreferrer"&gt;Check the slides&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Is AI really saving time?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu9l90jrhje1tqf431xsk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu9l90jrhje1tqf431xsk.png" alt="But whenever someone uses an LLM to generate text, images or code in seconds, their time saving (if real at all) could cost someone else hours." width="800" height="133"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Theoretically, this post belongs in the "No code" section but Hidde phrased all my thoughts way better than I could, so that I want to highlight it. If you question AI time savings, you should read it!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hidde.blog/time-saving-time-wasting/" rel="noopener noreferrer"&gt;Evaluate&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  More perf metrics in more browsers!
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fufx6clh585u1riti5ruk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fufx6clh585u1riti5ruk.png" alt="Core Web Vitals and their support info. LCP (Chromium, Firefox, Safari TP), INP (Chromium, Firefox, Safari TP) and CLS (Chromium)" width="800" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When Google started pushing the Core Web Vitals metrics, they were pretty much a Chromium-only thing. Which was a bummer, because then we couldn't measure the experience of people using other browsers.&lt;/p&gt;

&lt;p&gt;The competition is slowly catching up though! Let me share some web vitals news:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/144#apis" rel="noopener noreferrer"&gt;Firefox 144 shipped &lt;code&gt;interactionId&lt;/code&gt;&lt;/a&gt; as part of the PerformanceEventTiming so that you can now evaluate the INP metric&lt;/li&gt;
&lt;li&gt;&lt;a href="https://webkit.org/blog/17447/release-notes-for-safari-technology-preview-229/" rel="noopener noreferrer"&gt;Safari TP 229 supports INP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://webkit.org/blog/17504/release-notes-for-safari-technology-preview-230/" rel="noopener noreferrer"&gt;Safari TP 230 supports LCP&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you now want to gather all these metrics across browsers, use &lt;a href="https://github.com/GoogleChrome/web-vitals" rel="noopener noreferrer"&gt;Google's &lt;code&gt;web-vitals&lt;/code&gt; library&lt;/a&gt; which should work for all browsers. Happy measuring!&lt;/p&gt;

&lt;h2&gt;
  
  
  Yet another CSS reset
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3yn91agmnbqajdjg63om.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3yn91agmnbqajdjg63om.png" alt=":where(div, article, header, footer) {   /* ...and other block elements */   display: flex;   flex-direction: column; } 2025-10-20 at 14.22.11" width="800" height="309"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Paweł joined the club of people blogging about their CSS reset preferences (I'm still flying without... 🫣). The post includes some good opinions and lots of links to go deeper! &lt;/p&gt;

&lt;p&gt;&lt;a href="https://pawelgrzybek.com/the-css-reset-again/" rel="noopener noreferrer"&gt;Reset&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Random MDN – &lt;code&gt;Object.freeze()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3fhffg7jhc5jl3w44kzz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3fhffg7jhc5jl3w44kzz.png" alt="// Freeze. const o = Object.freeze(obj);  // The object has become frozen. Object.isFrozen(obj); // === true" width="800" height="296"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From the unlimited MDN knowledge archive...&lt;/p&gt;

&lt;p&gt;Did you know that you can freeze JavaScript objects? Now you do. 🫵&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze" rel="noopener noreferrer"&gt;Freeze!&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  New on the baseline
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnyk1symg36i2bz0t7lpt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnyk1symg36i2bz0t7lpt.png" alt="details::details-content {   opacity: 0;   transition:     opacity 600ms,     content-visibility 600ms allow-discrete; }  details[open]::details-content {   opacity: 1; }" width="800" height="502"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Did you already celebrate that &lt;code&gt;::details-content&lt;/code&gt; went into the baseline with Firefox 143? If not, now's the time!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/::details-content" rel="noopener noreferrer"&gt;Spice up all these details&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Three valuable projects to have a look at
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/bodadotsh/npm-security-best-practices" rel="noopener noreferrer"&gt;bodadotsh/npm-security-best-practices&lt;/a&gt; – How to stay safe from NPM supply chain attacks.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/lirantal/npq" rel="noopener noreferrer"&gt;lirantal/npq&lt;/a&gt; – Safely install npm packages by auditing them pre-install stage.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/bgreenwell/doxx" rel="noopener noreferrer"&gt;bgreenwell/doxx&lt;/a&gt; – Expose the contents of &lt;code&gt;.docx&lt;/code&gt; files without leaving your terminal.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A new Tiny Helper
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2zm412ls9t0a9nxwyxvj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2zm412ls9t0a9nxwyxvj.png" alt="Create Beautiful Charts and Graphs Online  Transform your data into stunning visualizations with our free, powerful chart maker. No downloads, no sign-ups—just beautiful charts in seconds." width="800" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I must admit, this one is really cool! You can enter "Make Graph" put some data in and get some graphs out. I can totally see myself using this to create visualizations without code or Excel!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://makegraph.app/" rel="noopener noreferrer"&gt;Graph it!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Find more single-purpose online tools on &lt;a href="https://tiny-helpers.dev/" rel="noopener noreferrer"&gt;tiny-helpers.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Thought of the week
&lt;/h2&gt;

&lt;p&gt;You've probably noticed that I'm an RSS fan boy, so it's no surprise that Molly's post &lt;a href="https://www.citationneeded.news/curate-with-rss/" rel="noopener noreferrer"&gt;"Curate your own newspaper with RSS"&lt;/a&gt; was right down my alley.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Using RSS is a way to regain control over the information you read online.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Do you enjoy Web Weekly? | Did you learn something from this issue?
&lt;/h2&gt;

&lt;p&gt;❤️ If so, &lt;strong&gt;join 25 other Web Weekly supporters&lt;/strong&gt; and give back with a small monthly donation on &lt;a href="https://www.patreon.com/stefanjudis" rel="noopener noreferrer"&gt;Patreon&lt;/a&gt; or &lt;a href="https://github.com/sponsors/stefanjudis/" rel="noopener noreferrer"&gt;GitHub Sponsors&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  This is all, friends!
&lt;/h2&gt;

&lt;p&gt;Loved this email? Hated this email? I want to hear about it!&lt;/p&gt;

&lt;p&gt;If you think something needs improvement or something sparked some joy, &lt;a href="//mailto:stefan@webweekly.email"&gt;reply to this email because I want to know more&lt;/a&gt;!&lt;/p&gt;




&lt;p&gt;And with that, take care of yourself - mentally, physically, and emotionally.&lt;/p&gt;

&lt;p&gt;I'll see you next week! 👋&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>css</category>
    </item>
    <item>
      <title>Header and footer elements lose their roles in sectioning content</title>
      <dc:creator>Stefan Judis</dc:creator>
      <pubDate>Sun, 19 Oct 2025 22:00:00 +0000</pubDate>
      <link>https://dev.to/stefanjudis/header-and-footer-elements-lose-their-roles-in-sectioning-content-1iam</link>
      <guid>https://dev.to/stefanjudis/header-and-footer-elements-lose-their-roles-in-sectioning-content-1iam</guid>
      <description>&lt;p&gt;Using proper ARIA landmark roles allows assistive technologies to navigate a page. You probably know about some common ones: &lt;code&gt;main&lt;/code&gt; (&lt;code&gt;main&lt;/code&gt;), &lt;code&gt;header&lt;/code&gt; (&lt;code&gt;banner&lt;/code&gt;), &lt;code&gt;footer&lt;/code&gt; (&lt;code&gt;contentinfo&lt;/code&gt;). But do you know that &lt;code&gt;header&lt;/code&gt; and &lt;code&gt;footer&lt;/code&gt; don't always get assigned their initial ARIA roles?&lt;/p&gt;

&lt;p&gt;Here's an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;header&amp;gt;&lt;/span&gt;
  "Root" header
&lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;main&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;header&amp;gt;&lt;/span&gt;
    "Main" header
  &lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;footer&amp;gt;&lt;/span&gt;
    "Main" footer
  &lt;span class="nt"&gt;&amp;lt;/footer&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;footer&amp;gt;&lt;/span&gt;
  "Root" footer
&lt;span class="nt"&gt;&amp;lt;/footer&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This HTML snippet includes &lt;code&gt;header&lt;/code&gt; and &lt;code&gt;footer&lt;/code&gt; elements that are a) on the root level and b) included in a &lt;code&gt;main&lt;/code&gt; element. If you check the browser's accessibility tools, these elements get different roles assigned.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/f20lfrunubsq/1H8CPfkGJnjgmB6JmL299m/248fa332ced66a9eeb2c164618875a14/sectionheader.jpg" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/f20lfrunubsq/1H8CPfkGJnjgmB6JmL299m/248fa332ced66a9eeb2c164618875a14/sectionheader.jpg" alt="The  raw `header` endraw  element gets the role "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The header element loses its &lt;code&gt;banner&lt;/code&gt; role and depending on the browser gets &lt;code&gt;generic&lt;/code&gt; or &lt;code&gt;sectionheader&lt;/code&gt; assigned, which has an effect on assistive technologies. Here's Martin in his post &lt;a href="https://www.tempertemper.net/blog/page-headings-dont-belong-in-the-header" rel="noopener noreferrer"&gt;"Page headings don't belong in the header"&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The header would carry no semantic meaning, so screen reader users would be left wondering where the main page header landmark was.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can find similar behavior, though not identical, with the footer element.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/f20lfrunubsq/1aUEzJwlTSAnYAOwbrEj9X/da7f67bbd69ee5519a20014369887901/sectionfooter.jpg" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/f20lfrunubsq/1aUEzJwlTSAnYAOwbrEj9X/da7f67bbd69ee5519a20014369887901/sectionfooter.jpg" alt="Footer elements on the root level get  raw `contentinfo` endraw  assigned whereas  raw `footer` endraw  elements in a  raw `main` endraw  element get  raw `sectionfooter` endraw , "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Footer elements receive varying roles from &lt;code&gt;sectionfooter&lt;/code&gt; and &lt;code&gt;generic&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you look at the "ARIA in HTML" spec, you'll find some answers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://w3c.github.io/html-aria/#el-header" rel="noopener noreferrer"&gt;For the &lt;code&gt;header&lt;/code&gt; element&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If not a descendant of an &lt;code&gt;article&lt;/code&gt;, &lt;code&gt;aside&lt;/code&gt;, &lt;code&gt;main&lt;/code&gt;, &lt;code&gt;nav&lt;/code&gt; or &lt;code&gt;section&lt;/code&gt; element, or an element with &lt;code&gt;role=article&lt;/code&gt;, &lt;code&gt;complementary&lt;/code&gt;, &lt;code&gt;main&lt;/code&gt;, &lt;code&gt;navigation&lt;/code&gt; or &lt;code&gt;region&lt;/code&gt; then &lt;code&gt;role=banner&lt;/code&gt;. Otherwise, &lt;code&gt;role=generic&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://w3c.github.io/html-aria/#el-footer" rel="noopener noreferrer"&gt;For the &lt;code&gt;footer&lt;/code&gt; element&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If not a descendant of an &lt;code&gt;article&lt;/code&gt;, &lt;code&gt;aside&lt;/code&gt;, &lt;code&gt;main&lt;/code&gt;, &lt;code&gt;nav&lt;/code&gt; or &lt;code&gt;section&lt;/code&gt; element, or an element with &lt;code&gt;role=article&lt;/code&gt;, &lt;code&gt;complementary&lt;/code&gt;, &lt;code&gt;main&lt;/code&gt;, &lt;code&gt;navigation&lt;/code&gt; or &lt;code&gt;region&lt;/code&gt; then &lt;code&gt;role=contentinfo&lt;/code&gt;. Otherwise, &lt;code&gt;role=generic&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But what if you can't change the document structure or really want to have the &lt;code&gt;header&lt;/code&gt; element in a sectioning &lt;code&gt;main&lt;/code&gt;? If you really must, you can get out the hammer and add a landmark role yourself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;main&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;header&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"banner"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    "Main" header
  &lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm not sure how I feel about this, though.&lt;/p&gt;




&lt;p&gt;I've wondered why Chromium and Safari 26+ didn't follow the spec and didn't switch to the &lt;code&gt;generic&lt;/code&gt; role. It turns out, lucky me published this post right in the middle of some work-in-progress ARIA spec changes: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/w3c/aria/pull/1931" rel="noopener noreferrer"&gt;Introduction of &lt;code&gt;sectionheader&lt;/code&gt; and &lt;code&gt;sectionfooter&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/w3c/aria/pull/2543" rel="noopener noreferrer"&gt;ARIA mappings update&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/w3c/html-aam/issues/585" rel="noopener noreferrer"&gt;ARIA mappings update&lt;/a&gt;. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;sectionheader&lt;/code&gt; and &lt;code&gt;sectionfooter&lt;/code&gt; are new roles being worked on. Chromium and Safari started to ship the new semantics already. &lt;a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1893684" rel="noopener noreferrer"&gt;Firefox still has to implement the new aria mappings&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thanks to &lt;a href="https://matuzo.at/" rel="noopener noreferrer"&gt;Manuel&lt;/a&gt; and &lt;a href="https://lukewarlow.dev/" rel="noopener noreferrer"&gt;Luke&lt;/a&gt; pointing me in the right direction.&lt;/p&gt;




&lt;p&gt;Where does this lead us? You probably shouldn't rely on or use the new &lt;code&gt;sectionheader&lt;/code&gt; and &lt;code&gt;sectionfooter&lt;/code&gt; roles yet. Here's &lt;a href="https://bsky.app/profile/lukewarlow.dev/post/3m3me4mmhgc2f" rel="noopener noreferrer"&gt;Luke from Igalia giving some advice&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Browser support needs to improve but AT also won't necessarily understand these roles yet.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It will take some time until browsers have caught up and then still screen readers need to implement the new roles, too. This won't happen until tomorrow.&lt;/p&gt;

&lt;p&gt;But generally, whenever you use &lt;code&gt;header&lt;/code&gt; or &lt;code&gt;footer&lt;/code&gt; elements, make sure to double check the applied ARIA landmark roles and avoid placing the elements in sectioning content like the &lt;code&gt;main&lt;/code&gt; element. Otherwise, you might not provide the accessibility benefits you're hoping for.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>html</category>
      <category>a11y</category>
    </item>
    <item>
      <title>Creating Animated Accordions with the Details Element and Modern CSS</title>
      <dc:creator>Stefan Judis</dc:creator>
      <pubDate>Mon, 10 Mar 2025 10:36:56 +0000</pubDate>
      <link>https://dev.to/builderio/creating-animated-accordions-with-the-details-element-and-modern-css-4mio</link>
      <guid>https://dev.to/builderio/creating-animated-accordions-with-the-details-element-and-modern-css-4mio</guid>
      <description>&lt;p&gt;Accordions are everywhere these days. GitHub has them on their home page right now. Figma ships them, too. I've implemented so many custom accordions that I can't count them.&lt;/p&gt;

&lt;p&gt;But I must say, today’s accordions are pretty fancy. Here's what you'll find on github.com.&lt;/p&gt;



&lt;p&gt;The accordion opens and closes with a good-looking slide animation, and when you toggle the entries, the image on the right column reflects the currently open entry.&lt;/p&gt;

&lt;p&gt;How does this work?&lt;/p&gt;

&lt;p&gt;JavaScript toggles data attributes and classes, and &lt;a href="https://www.stefanjudis.com/snippets/how-to-animate-height-with-css-grid/" rel="noopener noreferrer"&gt;the good old CSS grid &lt;/a&gt;&lt;a href="https://www.stefanjudis.com/snippets/how-to-animate-height-with-css-grid/" rel="noopener noreferrer"&gt;&lt;code&gt;0fr&lt;/code&gt;&lt;/a&gt;&lt;a href="https://www.stefanjudis.com/snippets/how-to-animate-height-with-css-grid/" rel="noopener noreferrer"&gt; to &lt;/a&gt;&lt;a href="https://www.stefanjudis.com/snippets/how-to-animate-height-with-css-grid/" rel="noopener noreferrer"&gt;&lt;code&gt;1fr&lt;/code&gt;&lt;/a&gt;&lt;a href="https://www.stefanjudis.com/snippets/how-to-animate-height-with-css-grid/" rel="noopener noreferrer"&gt; trick&lt;/a&gt; is used to open the details content with a slide transition.&lt;/p&gt;

&lt;p&gt;What If I told you you can build the same thing using only HTML and modern CSS?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://main.d3739ajl2fez28.amplifyapp.com/examples/github-accordion" rel="noopener noreferrer"&gt;Check out this version that I built&lt;/a&gt;.&lt;/p&gt;



&lt;p&gt;This example is built without any JavaScript. Without JS? Yep, there’s not a single &lt;code&gt;script&lt;/code&gt; element.&lt;/p&gt;

&lt;p&gt;Let me show you how to create these fancy accordions using &lt;code&gt;details&lt;/code&gt; elements, the new &lt;code&gt;interpolate-size&lt;/code&gt; and &lt;code&gt;transition-behavior&lt;/code&gt; CSS properties, and the&lt;code&gt;:has()&lt;/code&gt; selector.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.builder.io%2Fapi%2Fv1%2Fimage%2Fassets%2FTEMP%2F9f11ebd14b76123baf1648c3956344aed699b361599ceac8b82b05b475a65703%3FplaceholderIfAbsent%3Dtrue%26apiKey%3DYJIGb4i01jvw0SRdL5Bt%26width%3D24" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.builder.io%2Fapi%2Fv1%2Fimage%2Fassets%2FTEMP%2F9f11ebd14b76123baf1648c3956344aed699b361599ceac8b82b05b475a65703%3FplaceholderIfAbsent%3Dtrue%26apiKey%3DYJIGb4i01jvw0SRdL5Bt%26width%3D24" alt="Caution icon" width="18" height="18"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Disclaimer: At the time of writing, some described CSS features are Chromium-only. However, all the features can be treated as progressive enhancement, and the accordion will still work for other browsers. The only thing missing will be the sliding animation.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Generating markup from your design&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Whenever I want to sketch something like this quick 50/50 layout, I reach for &lt;a href="https://www.builder.io/blog/figma-to-code-visual-copilot" rel="noopener noreferrer"&gt;Builder's Visual Copilot&lt;/a&gt;. Design something in Figma, import the design into Builder using &lt;a href="https://www.builder.io/c/docs/import-from-figma" rel="noopener noreferrer"&gt;the Figma plugin&lt;/a&gt;, and off you go.&lt;/p&gt;



&lt;p&gt;After Builder generates the component source code, you can either copy and paste it into your editor or go the fancy route and automatically add your codified designs to your project with a single &lt;code&gt;npx&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;You can even “talk” to your components, ask for improvements and refactorings, and export your designs to vanilla web HTML/CSS, React, Vue, and even Swift. It's pretty cool!&lt;/p&gt;

&lt;p&gt;But let's get to our vanilla accordion code!&lt;/p&gt;

&lt;h2&gt;
  
  
  The base setup
&lt;/h2&gt;

&lt;p&gt;After generating some juicy CSS, I cleaned things up a bit (AI ain't perfect yet) and ended up with the following HTML for our new accordion component.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"accordion"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"detailsColumn"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;details&lt;/span&gt; &lt;span class="na"&gt;open&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;summary&amp;gt;&lt;/span&gt;First entry&lt;span class="nt"&gt;&amp;lt;/summary&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/details&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;details&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;summary&amp;gt;&lt;/span&gt;Second entry&lt;span class="nt"&gt;&amp;lt;/summary&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/details&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;details&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;summary&amp;gt;&lt;/span&gt;Third entry&lt;span class="nt"&gt;&amp;lt;/summary&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/details&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"imagesColumn"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;details&lt;/code&gt; elements allow us to show and hide content by clicking on the visible &lt;code&gt;summary&lt;/code&gt; element. Additionally, the first element is already expanded by setting the &lt;code&gt;open&lt;/code&gt; attribute.&lt;/p&gt;



&lt;p&gt;Let's style the &lt;code&gt;details&lt;/code&gt; elements and make them a real accordion that only allows one entry to be open!&lt;/p&gt;

&lt;h2&gt;
  
  
  Removing the &lt;code&gt;details&lt;/code&gt; default styling
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;details&lt;/code&gt; default styles are valuable for quick designs, but we aim for something more polished here. So, where are these triangle markers coming from?&lt;/p&gt;

&lt;p&gt;The tiny triangles are defined in the user agent stylesheets. Unfortunately, Chromium/Firefox and Webkit render them in different ways.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.builder.io%2Fapi%2Fv1%2Fimage%2Fassets%252FYJIGb4i01jvw0SRdL5Bt%252F5e4692c18c8d4f17bf34a46ac8c2f1ef%3Fwidth%3D705" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.builder.io%2Fapi%2Fv1%2Fimage%2Fassets%252FYJIGb4i01jvw0SRdL5Bt%252F5e4692c18c8d4f17bf34a46ac8c2f1ef%3Fwidth%3D705" alt="A comparison of webkit and chromium/firefox, highlighting where the details default styling is located." width="705" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Chromium/Firefox defines a &lt;code&gt;display: list-item&lt;/code&gt; on the &lt;code&gt;summary&lt;/code&gt; element. Similar to &lt;code&gt;li&lt;/code&gt; element, these elements come with a stylable &lt;code&gt;::marker&lt;/code&gt; pseudo-element, which can also be adjusted using the &lt;code&gt;list-style&lt;/code&gt; property.&lt;/p&gt;

&lt;p&gt;To remove the triangles coming from &lt;code&gt;list-style-type: disclosure-open&lt;/code&gt;, you can either declare &lt;code&gt;list-style: none&lt;/code&gt; to remove all the &lt;code&gt;::marker&lt;/code&gt; elements or change the &lt;code&gt;display&lt;/code&gt; property to anything but &lt;code&gt;list-item&lt;/code&gt; to avoid rendering list styles at all.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.builder.io%2Fapi%2Fv1%2Fimage%2Fassets%252FYJIGb4i01jvw0SRdL5Bt%252F723117579ed2448fbf18b5fc93a2cb68%3Fwidth%3D705" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.builder.io%2Fapi%2Fv1%2Fimage%2Fassets%252FYJIGb4i01jvw0SRdL5Bt%252F723117579ed2448fbf18b5fc93a2cb68%3Fwidth%3D705" alt="A summary of the default user agent styles in Chromium." width="705" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Webkit's &lt;code&gt;details&lt;/code&gt; implementation is entirely different. For Apple's browser, we have to reach for the internal pseudo-element selector &lt;code&gt;::-webkit-details-marker&lt;/code&gt; and hide it with &lt;code&gt;display: none&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If we combine both approaches, we end up with the following CSS to remove the &lt;code&gt;details&lt;/code&gt; triangles altogether.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* Chromium / Firefox */&lt;/span&gt;
&lt;span class="nt"&gt;details&lt;/span&gt; &lt;span class="nt"&gt;summary&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;/* Either remove the list style ... */&lt;/span&gt;
  &lt;span class="nl"&gt;list-style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c"&gt;/* ... or change the `display` property to something else */&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* Webkit */&lt;/span&gt;
&lt;span class="nt"&gt;details&lt;/span&gt; &lt;span class="nt"&gt;summary&lt;/span&gt;&lt;span class="nd"&gt;::-webkit-details-marker&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Side note: it's not great that major browsers ship different details element implementations. Luckily, &lt;a href="https://github.com/web-platform-tests/interop/blob/main/2025/README.md#details-element" rel="noopener noreferrer"&gt;this topic is included in Interop 2025&lt;/a&gt;, so we will see improvements this year.&lt;/p&gt;

&lt;p&gt;Let's continue to make the elements a “real” accordion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting multiple &lt;code&gt;details&lt;/code&gt; to build an exclusive accordion
&lt;/h2&gt;

&lt;p&gt;Forcing all these &lt;code&gt;details&lt;/code&gt; elements to be open one at a time is a trivial task in 2025 because you only need to set the same &lt;code&gt;name&lt;/code&gt; attribute on all the elements.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;details&lt;/span&gt; &lt;span class="na"&gt;open&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"something"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;summary&amp;gt;&lt;/span&gt;First entry&lt;span class="nt"&gt;&amp;lt;/summary&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/details&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;details&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"something"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;summary&amp;gt;&lt;/span&gt;Second entry&lt;span class="nt"&gt;&amp;lt;/summary&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/details&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it? Yes!&lt;/p&gt;

&lt;p&gt;Connecting the &lt;code&gt;details&lt;/code&gt; elements with the same &lt;code&gt;name&lt;/code&gt; allows only one element to be open at a time. If one &lt;code&gt;details&lt;/code&gt; element is opened, the others will be closed.&lt;/p&gt;

&lt;p&gt;However, before you slap the same &lt;code&gt;name&lt;/code&gt; attribute on all your &lt;code&gt;details&lt;/code&gt; elements, be aware that &lt;strong&gt;exclusive accordions have accessibility and UX tradeoffs&lt;/strong&gt;. In many situations, accessing information included in multiple elements is beneficial. For example, if extensive FAQs only allow one question to be open at a time, it will be terrible UX if someone wants to compare paragraphs. I would say that an exclusive accordion is “okay” for this small marketing widget, though.&lt;/p&gt;

&lt;p&gt;With these few &lt;code&gt;details&lt;/code&gt; tweaks, we implemented the core accordion functionality, and the component started to look like something real after adding some styling.&lt;/p&gt;



&lt;h2&gt;
  
  
  Animating the &lt;code&gt;details&lt;/code&gt; content
&lt;/h2&gt;

&lt;p&gt;At this stage, our &lt;code&gt;details&lt;/code&gt; elements are connected and open only one at a time, but they're not looking perfect yet. The hidden content isn't animating or transitioning. How can we make the details content “slide open”?&lt;/p&gt;

&lt;p&gt;To do that, we can reach into our CSS magic bag!&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;::details-content&lt;/code&gt; — the expandable/collapsible &lt;code&gt;details&lt;/code&gt; content
&lt;/h3&gt;

&lt;p&gt;To animate and transition the hidden accordion content, we need to find a way to select it first.&lt;/p&gt;

&lt;p&gt;Wrapping everything in a &lt;code&gt;div&lt;/code&gt; might be an option, but this approach often leads to CSS hacks and spacing issues. Chromium browsers ship a new solution to avoid these wrapper elements — &lt;code&gt;::details-content&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The new pseudo-element lets us select all the content that's not the &lt;code&gt;summary&lt;/code&gt; element itself.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.builder.io%2Fapi%2Fv1%2Fimage%2Fassets%252FYJIGb4i01jvw0SRdL5Bt%252F6832a8c32c82480bad1a17842274fdf6%3Fwidth%3D705" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.builder.io%2Fapi%2Fv1%2Fimage%2Fassets%252FYJIGb4i01jvw0SRdL5Bt%252F6832a8c32c82480bad1a17842274fdf6%3Fwidth%3D705" alt="A preview of the Chromuim " width="705" height="497"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For example, if you want to color everything that shows up after clicking the &lt;code&gt;summary&lt;/code&gt; element in red, you can set a background color on the &lt;code&gt;::details-content&lt;/code&gt; pseudo-element.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;details&lt;/span&gt;&lt;span class="nd"&gt;::details-content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;red&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.builder.io%2Fapi%2Fv1%2Fimage%2Fassets%252FYJIGb4i01jvw0SRdL5Bt%252Fcf47110f276c4d52a68f1ec40033e36b%3Fwidth%3D705" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.builder.io%2Fapi%2Fv1%2Fimage%2Fassets%252FYJIGb4i01jvw0SRdL5Bt%252Fcf47110f276c4d52a68f1ec40033e36b%3Fwidth%3D705" alt="An accordion menu, with one list item highlighted in red." width="705" height="335"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Coloring this new pseudo-element isn't helpful, though, so let's use &lt;code&gt;::details-content&lt;/code&gt; for a sliding transition.&lt;/p&gt;

&lt;h3&gt;
  
  
  Animating height with &lt;strong&gt;&lt;code&gt;interpolate-size&lt;/code&gt;&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Now that we can select all the hidden content, you could think of adding an &lt;code&gt;overflow: hidden&lt;/code&gt; to transition the &lt;code&gt;height&lt;/code&gt; property, but, unfortunately, this won’t work.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* This won't work yet */&lt;/span&gt;
&lt;span class="nt"&gt;details&lt;/span&gt;&lt;span class="nd"&gt;::details-content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="m"&gt;0.3s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;details&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;open&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="nd"&gt;::details-content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can't animate from &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/length-percentage" rel="noopener noreferrer"&gt;a &lt;/a&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/length-percentage" rel="noopener noreferrer"&gt;&lt;code&gt;&amp;amp;lt;length-percentage&amp;amp;gt;&lt;/code&gt;&lt;/a&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/length-percentage" rel="noopener noreferrer"&gt; value&lt;/a&gt; like &lt;code&gt;0&lt;/code&gt; to an &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/Intrinsic_Size" rel="noopener noreferrer"&gt;intrinsic size value&lt;/a&gt; like &lt;code&gt;auto&lt;/code&gt; in CSS. Or can we?&lt;/p&gt;

&lt;p&gt;I have a surprise for you!&lt;/p&gt;

&lt;p&gt;In Chromium, you can animate from &lt;code&gt;200px&lt;/code&gt; (or other values) to &lt;code&gt;auto&lt;/code&gt; &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/interpolate-size" rel="noopener noreferrer"&gt;by setting &lt;/a&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/interpolate-size" rel="noopener noreferrer"&gt;&lt;code&gt;interpolate-size&lt;/code&gt;&lt;/a&gt;. The &lt;code&gt;allow-keywords&lt;/code&gt; value will instruct browsers to do some Math and allow transitioning from specific values to &lt;code&gt;auto&lt;/code&gt;, &lt;code&gt;min-content&lt;/code&gt;, and other keyword values. This new CSS property is a big deal!&lt;/p&gt;

&lt;p&gt;It should be safe to turn on &lt;code&gt;interpolate-size&lt;/code&gt; on the &lt;code&gt;:root&lt;/code&gt; element, and all included elements will inherit this new setting.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;interpolate-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;allow-keywords&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now look at this.&lt;/p&gt;



&lt;p&gt;This sliding animation isn't perfect yet, but opening the &lt;code&gt;details&lt;/code&gt; element looks pretty good already. However, what's up with the closing transition?&lt;/p&gt;

&lt;h3&gt;
  
  
  Transitioning properties like &lt;code&gt;display&lt;/code&gt; or &lt;code&gt;content-visibility&lt;/code&gt; with &lt;code&gt;transition-behavior&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;If you inspect Chromium's &lt;code&gt;details&lt;/code&gt; toggle behavior, you'll discover that the closed &lt;code&gt;::details-content&lt;/code&gt; element includes a &lt;code&gt;content-visibility: hidden;&lt;/code&gt;. This CSS property prevents our closing slide transition from being visible.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.builder.io%2Fapi%2Fv1%2Fimage%2Fassets%252FYJIGb4i01jvw0SRdL5Bt%252F06a90ba74c8645418ac2cd25aff10e14%3Fwidth%3D705" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.builder.io%2Fapi%2Fv1%2Fimage%2Fassets%252FYJIGb4i01jvw0SRdL5Bt%252F06a90ba74c8645418ac2cd25aff10e14%3Fwidth%3D705" alt="A comparison between closed and open details elements, showing that the closed details has a " width="705" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Whenever we close the &lt;code&gt;details&lt;/code&gt; element, &lt;code&gt;content-visibility: hidden;&lt;/code&gt; is applied to the &lt;code&gt;::details-content&lt;/code&gt; pseudo-element. &lt;code&gt;content-visibility&lt;/code&gt; makes any visible effect disappear immediately because there's nothing to transition when the value changes from &lt;code&gt;visible&lt;/code&gt; to &lt;code&gt;hidden&lt;/code&gt;. The element is just hidden right away.&lt;/p&gt;

&lt;p&gt;If you ever wanted to create an exit animation and realized that it wouldn’t work because &lt;code&gt;display: none&lt;/code&gt; will hide the element right away, this here is the same problem.&lt;/p&gt;

&lt;p&gt;The question is how should browsers transition discrete values (&lt;code&gt;hidden&lt;/code&gt;, &lt;code&gt;visible&lt;/code&gt;, …)?&lt;/p&gt;

&lt;p&gt;The default behavior is to flip the values immediately and ignore the fact that you defined a transition. This is why &lt;code&gt;display: none&lt;/code&gt; or &lt;code&gt;content-visibility: hidden&lt;/code&gt; are applied immediately.&lt;/p&gt;

&lt;p&gt;Luckily, there are new ways to change this behavior! To enable easier transitions, we now have &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/transition-behavior" rel="noopener noreferrer"&gt;the &lt;code&gt;transition-behavior&lt;/code&gt; property&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;details&lt;/span&gt;&lt;span class="nd"&gt;::details-content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c"&gt;/* Enable transitioning of `content-visibility` */&lt;/span&gt;
  &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="m"&gt;0.3s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content-visibility&lt;/span&gt; &lt;span class="m"&gt;0.3s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;transition-behavior&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;allow-discrete&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By setting &lt;code&gt;transition-behavior: allow-discrete&lt;/code&gt; we can tell the browser that it's okay to also transition properties that aren't based on numbers. If you allow discrete animations, the values won't be flipped immediately but at the 50% transition mark.&lt;/p&gt;

&lt;p&gt;For special cases like &lt;code&gt;display&lt;/code&gt; and &lt;code&gt;content-visibility&lt;/code&gt;, the values will only be flipped when the entire transition is over. This allows us to transition other properties and set &lt;code&gt;display: none&lt;/code&gt; at the end of a transition.&lt;/p&gt;

&lt;p&gt;When we turn on &lt;code&gt;transition-behavior&lt;/code&gt; for the &lt;code&gt;::details-content&lt;/code&gt;, the browser will only toggle to &lt;code&gt;content-visibility: hidden&lt;/code&gt; at the end of the specified transition (0.3s).&lt;/p&gt;

&lt;p&gt;And now check this out!&lt;/p&gt;



&lt;p&gt;This visual effect might not look like a big deal, but after fifteen years of web development, I'm absolutely amazed by being able to transition &lt;code&gt;height&lt;/code&gt; and animate from and to &lt;code&gt;display: none&lt;/code&gt;. This is huge!&lt;/p&gt;

&lt;p&gt;Now that our accordion looks good, how can we show images depending on the opened &lt;code&gt;details&lt;/code&gt; element?&lt;/p&gt;

&lt;h2&gt;
  
  
  Reacting to DOM changes with the &lt;code&gt;:has()&lt;/code&gt; selector
&lt;/h2&gt;

&lt;p&gt;Let's bring in the images in the right column of the accordion component.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"accordion"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- The left column --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"detailsColumn"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;details&lt;/span&gt; &lt;span class="na"&gt;open&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"something"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;summary&amp;gt;&lt;/span&gt;First entry&lt;span class="nt"&gt;&amp;lt;/summary&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/details&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;details&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"something"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;summary&amp;gt;&lt;/span&gt;Second entry&lt;span class="nt"&gt;&amp;lt;/summary&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/details&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- The right column --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"imagesColumn"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"1000"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"1000"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"1000"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"1000"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Side note: to keep things simpler, I decided to treat the images as “decorative” with an empty alt attribute (alt=””). This way, we can transition opacity to show and hide the images without worrying about their exposure in the accessibility tree.&lt;/p&gt;

&lt;p&gt;The accordion should, by definition, always include the same number of &lt;code&gt;details&lt;/code&gt; and &lt;code&gt;img&lt;/code&gt; elements. Would it be possible to show and hide images depending on which &lt;code&gt;details&lt;/code&gt; element is currently open?&lt;/p&gt;

&lt;p&gt;It might be a bit adventurous, but I love using &lt;code&gt;:has()&lt;/code&gt; for these cases.&lt;/p&gt;

&lt;p&gt;Let’s hide all the images with &lt;code&gt;opacity: 0;&lt;/code&gt; and scale them up to make them appear with a nice transition when they become visible.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.accordion&lt;/span&gt; &lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt; &lt;span class="m"&gt;0.3s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opacity&lt;/span&gt; &lt;span class="m"&gt;0.3s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, let’s bring in some &lt;code&gt;:has()&lt;/code&gt; magic to make the images appear when their &lt;code&gt;details&lt;/code&gt; counterpart was opened.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.accordion&lt;/span&gt;&lt;span class="nd"&gt;:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;details&lt;/span&gt;&lt;span class="nd"&gt;:nth-child&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)[&lt;/span&gt;&lt;span class="nt"&gt;open&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt; &lt;span class="nt"&gt;img&lt;/span&gt;&lt;span class="nd"&gt;:nth-child&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I know, this selector is a mouthful. Let's dissect it!&lt;/p&gt;

&lt;p&gt;From right to left: we want to show the image that is a first child (&lt;code&gt;img:nth-child(1)&lt;/code&gt;) inside of an &lt;code&gt;.accordion&lt;/code&gt; that includes an opened &lt;code&gt;details&lt;/code&gt; element that's also a first child (&lt;code&gt;details:nth-child(1)[open]&lt;/code&gt;). It takes a moment to wrap your head around it, I know…&lt;/p&gt;

&lt;p&gt;However, with this selector, we’re matching the position of the open &lt;code&gt;details&lt;/code&gt; element with the visible &lt;code&gt;img&lt;/code&gt; element, and we can extend it to cover all the included accordion entries.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.accordion&lt;/span&gt;&lt;span class="nd"&gt;:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;details&lt;/span&gt;&lt;span class="nd"&gt;:nth-child&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)[&lt;/span&gt;&lt;span class="nt"&gt;open&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt; &lt;span class="nt"&gt;img&lt;/span&gt;&lt;span class="nd"&gt;:nth-child&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
&lt;span class="nc"&gt;.accordion&lt;/span&gt;&lt;span class="nd"&gt;:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;details&lt;/span&gt;&lt;span class="nd"&gt;:nth-child&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;2&lt;/span&gt;&lt;span class="o"&gt;)[&lt;/span&gt;&lt;span class="nt"&gt;open&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt; &lt;span class="nt"&gt;img&lt;/span&gt;&lt;span class="nd"&gt;:nth-child&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;2&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
&lt;span class="nc"&gt;.accordion&lt;/span&gt;&lt;span class="nd"&gt;:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;details&lt;/span&gt;&lt;span class="nd"&gt;:nth-child&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;3&lt;/span&gt;&lt;span class="o"&gt;)[&lt;/span&gt;&lt;span class="nt"&gt;open&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt; &lt;span class="nt"&gt;img&lt;/span&gt;&lt;span class="nd"&gt;:nth-child&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;3&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
&lt;span class="nc"&gt;.accordion&lt;/span&gt;&lt;span class="nd"&gt;:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;details&lt;/span&gt;&lt;span class="nd"&gt;:nth-child&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;4&lt;/span&gt;&lt;span class="o"&gt;)[&lt;/span&gt;&lt;span class="nt"&gt;open&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt; &lt;span class="nt"&gt;img&lt;/span&gt;&lt;span class="nd"&gt;:nth-child&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;4&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
&lt;span class="nc"&gt;.accordion&lt;/span&gt;&lt;span class="nd"&gt;:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;details&lt;/span&gt;&lt;span class="nd"&gt;:nth-child&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;5&lt;/span&gt;&lt;span class="o"&gt;)[&lt;/span&gt;&lt;span class="nt"&gt;open&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt; &lt;span class="nt"&gt;img&lt;/span&gt;&lt;span class="nd"&gt;:nth-child&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;5&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
&lt;span class="nc"&gt;.accordion&lt;/span&gt;&lt;span class="nd"&gt;:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;details&lt;/span&gt;&lt;span class="nd"&gt;:nth-child&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;6&lt;/span&gt;&lt;span class="o"&gt;)[&lt;/span&gt;&lt;span class="nt"&gt;open&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt; &lt;span class="nt"&gt;img&lt;/span&gt;&lt;span class="nd"&gt;:nth-child&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;6&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now look at this!&lt;/p&gt;



&lt;p&gt;Of course, this approach comes with a significant trade-off. To cover all the cases, you must hardcode a number of entries in your CSS. This obviously isn't great, but I'm okay with shipping a few more selectors to avoid some JavaScript &lt;code&gt;onClick&lt;/code&gt; handlers.&lt;/p&gt;

&lt;p&gt;And now we're almost done; only one tiny feature is missing.&lt;/p&gt;

&lt;p&gt;You might have noticed it: when all &lt;code&gt;details&lt;/code&gt; elements are closed, all images are hidden and, unfortunately, we can't prevent the &lt;code&gt;details&lt;/code&gt; elements from getting closed.&lt;/p&gt;

&lt;p&gt;There's &lt;a href="https://github.com/whatwg/html/issues/10864" rel="noopener noreferrer"&gt;an open HTML specification issue about this problem&lt;/a&gt;, but it doesn't seem to have gotten any traction.&lt;/p&gt;

&lt;p&gt;To work around this problem, I think it’s fair game to bring in a fallback image. Let's add a &lt;code&gt;.fallback&lt;/code&gt; image at the end of the images container…&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"accordion"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"detailsCol"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"imagesColumn"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"1000"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"1000"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"1000"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"1000"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"fallback"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"1000"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"1000"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;… and show this image when there are no open &lt;code&gt;details&lt;/code&gt; elements inside our &lt;code&gt;.accordion&lt;/code&gt; container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/*
  Show the `.default` image when `.accordion`
  doesn't include open `details` elements
*/&lt;/span&gt;
&lt;span class="nc"&gt;.accordion&lt;/span&gt;&lt;span class="nd"&gt;:not&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;details&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;open&lt;/span&gt;&lt;span class="o"&gt;]))&lt;/span&gt; &lt;span class="nc"&gt;.fallback&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Et voilà!&lt;/p&gt;



&lt;p&gt;I might sound like a broken record, but I'm absolutely amazed by all this CSS magic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This was a wild ride, wasn't it?&lt;/p&gt;

&lt;p&gt;In this post, we covered how to animate elements from &lt;code&gt;height: 0;&lt;/code&gt; to &lt;code&gt;height: auto;&lt;/code&gt;. We transitioned &lt;code&gt;content-visibility&lt;/code&gt; from &lt;code&gt;visible&lt;/code&gt; to &lt;code&gt;hidden&lt;/code&gt;. And we wrote a funky &lt;code&gt;:has()&lt;/code&gt; selector to react to all these details elements' opening and closing state.&lt;/p&gt;

&lt;p&gt;All these new features show that the CSS evolution is in full swing, and it's enabling us to build things that we couldn’t build before. &lt;strong&gt;And I'm so here for building more with less code!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you have any questions, let us know in the comments, &lt;a href="//mailto:stefanjudis@gmail.com"&gt;shoot me an email&lt;/a&gt;, or tag us on social. And if you want to catch the next article teaching all this cutting-edge webdev stuff, subscribe to our newsletter below.&lt;/p&gt;

&lt;p&gt;Until then — talk soon!&lt;/p&gt;

&lt;p&gt;Written by&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;span&gt;Stefan Judis&lt;/span&gt;
&lt;/h2&gt;

&lt;p&gt;Stefan loves getting into web performance, new technologies, and accessibility, sends out &lt;a href="https://webweekly.email/" rel="noopener noreferrer"&gt;a weekly web development newsletter&lt;/a&gt; and enjoys sharing &lt;a href="https://www.stefanjudis.com/today-i-learned/" rel="noopener noreferrer"&gt;nerdy discoveries on his blog&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>css</category>
      <category>webdev</category>
      <category>html</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>🧢 Web Weekly newsletter 152 — Interop 2025, console.context() and Number.POSITIVE_INFINITY</title>
      <dc:creator>Stefan Judis</dc:creator>
      <pubDate>Wed, 19 Feb 2025 04:14:43 +0000</pubDate>
      <link>https://dev.to/webweekly/web-weekly-newsletter-152-interop-2025-consolecontext-and-numberpositiveinfinity-27o6</link>
      <guid>https://dev.to/webweekly/web-weekly-newsletter-152-interop-2025-consolecontext-and-numberpositiveinfinity-27o6</guid>
      <description>&lt;h2&gt;
  
  
  Guten Tag! Guten Tag! 👋
&lt;/h2&gt;

&lt;p&gt;What can we expect from Interop 2025? How can you give your &lt;code&gt;console.log&lt;/code&gt; statements more &lt;code&gt;context()&lt;/code&gt;? Is using gifs generally a good idea?&lt;/p&gt;

&lt;p&gt;Turn on the Web Weekly tune and find all the answers below. Enjoy!&lt;/p&gt;




&lt;p&gt;&lt;a href="https://pawelgrzybek.com/" rel="noopener noreferrer"&gt;Pawel&lt;/a&gt; listens to &lt;a href="https://youtu.be/Osy54f7FQno" rel="noopener noreferrer"&gt;“9th Wonder - LoveKiss”&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I can listen to this beat in a loop for the whole day. Just so good!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Do you want to share your favorite song with the Web Weekly community? &lt;a href="//mailto:stefan@webweekly.email"&gt;Hit reply&lt;/a&gt;; there are &lt;strong&gt;three more songs&lt;/strong&gt; left in the queue.&lt;/p&gt;




&lt;p&gt;If you enjoy Web Weekly, let others know by reposting it on your favorite social network. It really helps me grow this newsletter into something "real". 🫣&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🦋 &lt;a href="https://bsky.app/profile/stefanjudis.com/post/3lifu75igyc2z" rel="noopener noreferrer"&gt;Bluesky&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🐘 &lt;a href="https://front-end.social/@stefan/114021739969444684" rel="noopener noreferrer"&gt;Mastodon&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💼 &lt;a href="https://www.linkedin.com/posts/stefan-judis_you-might-know-that-im-running-this-small-activity-7297392706404700160-Xkpo" rel="noopener noreferrer"&gt;Linkedin&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thank you! 💙&lt;/p&gt;




&lt;p&gt;The browser makers came together and decided on the focus areas of Interop 2025. &lt;/p&gt;

&lt;p&gt;And if you now think, "Oh well... again, new stuff!?", I get it. However, the features included in Interop aren't browser APIs working in a single browser for the next decade. The promise is that all browsers support the listed features this year. These Interop features are supposed to enter the web soon'ish. For realz.&lt;/p&gt;

&lt;p&gt;The Interop focus areas this year include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CSS anchor positioning&lt;/li&gt;
&lt;li&gt;Core Web Vitals&lt;/li&gt;
&lt;li&gt;&lt;code&gt;::details-content&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;import ... with { type: "json" }&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;CSS &lt;code&gt;@scope&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;View Transitions&lt;/li&gt;
&lt;li&gt;and many more things...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://github.com/web-platform-tests/interop/blob/main/2025/README.md#modules" rel="noopener noreferrer"&gt;The "official Interop 2025 docs" are a good read&lt;/a&gt;, so check them out!&lt;/p&gt;

&lt;p&gt;But what's the current state and where are we today?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjdzjaabcs71sgju180vr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjdzjaabcs71sgju180vr.png" alt="Interop 2025 Dashboard showing that chromium is on a score 88 - 91, Firefox 52 and Safari at 55." width="800" height="761"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unsurprisingly, Chromium is leading the way in terms of new features, but I expect that things will move fast.&lt;/p&gt;

&lt;p&gt;I'm quite happy with Interop 2025, but if you're game for some critical thoughts, of course, &lt;a href="https://bsky.app/profile/infrequently.org/post/3li4ig3bjmsqf" rel="noopener noreferrer"&gt;Alex Russel never disappoints and shares some spicy takes&lt;/a&gt;.🌶️&lt;/p&gt;




&lt;h2&gt;
  
  
  Something that made me smile this week
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu0j9bge60a0ykcjcg0e3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu0j9bge60a0ykcjcg0e3.png" alt="GitHub contribution graph stickers forming the word " width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I resisted for over a year and didn't put stickers on my computer, but these are just too good!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.etsy.com/listing/1855699284/fake-developer-github-contribution-graph" rel="noopener noreferrer"&gt;Be a fake developer&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  New on the blog
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;TIL: &lt;a href="https://www.stefanjudis.com/today-i-learned/iterate-typescript-union-type/" rel="noopener noreferrer"&gt;How to iterate over TypeScript union types&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Let's stop using animated gifs
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Far9mpafxhmhb6yomwi26.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Far9mpafxhmhb6yomwi26.png" alt="Animated GIFs are everywhere you look: from social media and messaging services, to email and even on websites. So how do we use them accessibly? I’m afraid to say, the most accessible way to use animated GIFs is probably not to use them at all." width="800" height="258"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Oldie but goldie: Martin makes some good points against using animated gifs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.tempertemper.net/blog/accessible-animated-gifs-are-pointless" rel="noopener noreferrer"&gt;Reconsider&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  LCP !== LCP
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frxbg5ei2pji0gsksozr9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frxbg5ei2pji0gsksozr9.png" alt="Matt explaining LCP metrics" width="800" height="496"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Did you know that not every element is considered a valid LCP element? Or are you aware that Chromium and Firefox evaluate the LCP elements differently? Matt explains the details.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=aX7VRto8gVE" rel="noopener noreferrer"&gt;Understand LCP&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we should consider searchability
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F61f9allqhgbxb4lj18x3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F61f9allqhgbxb4lj18x3.png" alt='&amp;lt;a href="https://draft.community" aria-label="Slack"&amp;gt;   &amp;lt;svg.../&amp;gt;   &amp;lt;span class="hidden-until-found" hidden="until-found"&amp;gt;Slack&amp;lt;/span&amp;gt; &amp;lt;/a&amp;gt;' width="800" height="169"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How do you search for things on a web page? You might &lt;code&gt;CMD+f&lt;/code&gt; all the things. If you do, you've probably also encountered situations when a simple text search doesn't work.&lt;/p&gt;

&lt;p&gt;Schepp explains how to make content findable for the built-in browser search and assistive technology using &lt;code&gt;hidden="until-found"&lt;/code&gt; and some CSS trickery.&lt;/p&gt;

&lt;p&gt;It's an excellent post, and I wonder if this approach will become a &lt;code&gt;.sr-only&lt;/code&gt; class replacement.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://schepp.dev/posts/rethinking-find-in-page-accessibility-making-hidden-text-work-for-everyone/" rel="noopener noreferrer"&gt;Make things searchable&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;console.context()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fstcfdp5t8anhs6ocwhuk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fstcfdp5t8anhs6ocwhuk.png" alt="// let's create a custom logger const myLogger = console.context('my-logger') myLogger.log('hello from my logger')" width="800" height="195"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Today I learned that the Chromium &lt;code&gt;console&lt;/code&gt; includes a &lt;code&gt;context()&lt;/code&gt; method that allows you to create loggers, which you can easily filter in the JavaScript console.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://devtoolstips.org/tips/en/create-contextual-console-loggers/" rel="noopener noreferrer"&gt;Filter your logs&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;You're halfway through! Would you enjoy getting &lt;a href="https://www.webweekly.email" rel="noopener noreferrer"&gt;Web Weekly&lt;/a&gt; straight to your inbox?&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The wonderful weird web – Floor796
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl529sho1cpyc0bhpxvqw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl529sho1cpyc0bhpxvqw.png" alt="Scene from Floor796" width="800" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Floor796 is an absolute classic created in 2018, and the animated space station includes areas with a gazillion tiny details. The fun thing: the project is still being worked on and extended.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://floor796.com" rel="noopener noreferrer"&gt;Discover things&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Is AI hindering new tech adoption?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftxta1iyr7d4u3szggh34.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftxta1iyr7d4u3szggh34.png" alt="The cutoff means that models are strictly limited in knowledge up to a certain point. For instance, Anthropic’s latest models have a cutoff of April 2024, and OpenAI’s latest models have cutoffs of late 2023." width="800" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm really trying to lean into some AI coding features, but with all these new browser features shipping daily, I would be lying if I said I'm not concerned about the AI knowledge cut-off.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vale.rocks/posts/ai-is-stifling-tech-adoption" rel="noopener noreferrer"&gt;Be aware of the new stuff&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Speculation rules on &lt;code&gt;google.com&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpdygtl4rf3yc44f5ehak.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpdygtl4rf3yc44f5ehak.png" alt="{ " width="800" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You might have heard of the Chromium-only &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Speculation_Rules_API" rel="noopener noreferrer"&gt;Speculation Rules&lt;/a&gt;. They're supposed to be a &lt;code&gt;prefetch&lt;/code&gt; / &lt;code&gt;prerender&lt;/code&gt; alternative.&lt;/p&gt;

&lt;p&gt;I learned that Google Search ships them in production, and their implementation includes many tips and tricks about things to consider. I mean look at this snippet, &lt;code&gt;"anonymous-client-ip-when-cross-origin"&lt;/code&gt;, what? 🤯&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.chrome.com/blog/search-speculation-rules" rel="noopener noreferrer"&gt;Speculate&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A 💙 for CSS grid
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9u1bj9gporo7as16juwc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9u1bj9gporo7as16juwc.png" alt="An extensive definition of grid-template-areas." width="800" height="487"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I just love it when I see CSS grids being used for more than putting columns next to each other.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cloudfour.com/thinks/faux-containers-in-css-grids/" rel="noopener noreferrer"&gt;Power up your grids&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A massive Node.js ecosystem update
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbhx5mtmprlampmzrfbfs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbhx5mtmprlampmzrfbfs.png" alt="This change means that when Node.js 18 reaches end-of-life (EOL) in April 2025, library maintainers can finally drop their CJS builds and ship ESM-only packages with confidence." width="800" height="198"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This news might only be important for package maintainers, but after all these years of dancing the endless CommonJS vs ECMAScript modules dance, it's finally time to embrace modules.&lt;/p&gt;

&lt;p&gt;Node.js 20 and higher versions can now &lt;code&gt;require&lt;/code&gt; ECMAScript modules, which means that when Node.js 18 reaches the end of life in April, everybody can drop all these build steps compiling multiple module systems and "just ship" modules.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://socket.dev/blog/require-esm-backported-to-node-js-20" rel="noopener noreferrer"&gt;Go all-in with modules&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Random MDN – &lt;code&gt;Number.POSITIVE_INFINITY&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0hl7i6tusiqjck1v35em.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0hl7i6tusiqjck1v35em.png" alt="function checkNumber(bigNumber) {   if (bigNumber === Number.POSITIVE_INFINITY) {     return " width="800" height="285"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From the unlimited MDN knowledge archive...&lt;/p&gt;

&lt;p&gt;Here's one of the many famous JavaScript quirks: did you know there's a static data property called &lt;code&gt;POSITIVE_INFINITY&lt;/code&gt;? Now you do.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/POSITIVE_INFINITY" rel="noopener noreferrer"&gt;Discover infinity&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  TIL recap – media query boolean contexts
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F664y3wg9lsf5lcx5vg4z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F664y3wg9lsf5lcx5vg4z.png" alt="@media screen and (prefers-contrast) { }" width="800" height="188"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's a trick question: what values will be matched for the &lt;code&gt;prefers-contrast&lt;/code&gt; media feature query when you omit the value and use the so called "boolean context"?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.stefanjudis.com/today-i-learned/user-preference-feature-queries-have-a-boolean-context/" rel="noopener noreferrer"&gt;Query features!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Find more short web development learnings in &lt;a href="https://www.stefanjudis.com/today-i-learned/" rel="noopener noreferrer"&gt;my "Today I learned" section&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  New on the baseline — Styling scrollbars
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvpl7x1j999191mubgje5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvpl7x1j999191mubgje5.png" alt="Demo showing the scrollbar styling options" width="800" height="513"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Short'n'sweet: if styling the scrollbar is your jam, &lt;code&gt;scrollbar-color&lt;/code&gt; and &lt;code&gt;scrollbar-width&lt;/code&gt; work across modern browsers now!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://web.dev/blog/baseline-scrollbar-props" rel="noopener noreferrer"&gt;Be on brand&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Three valuable projects to have a look at
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/yassinebenaid/bunster" rel="noopener noreferrer"&gt;yassinebenaid/bunster&lt;/a&gt; – Compile shell scripts to static binaries.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/GyulyVGC/sniffnet" rel="noopener noreferrer"&gt;GyulyVGC/sniffnet&lt;/a&gt; – Comfortably monitor your Internet traffic.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/PatrickJS/awesome-cursorrules" rel="noopener noreferrer"&gt;PatrickJS/awesome-cursorrules&lt;/a&gt; – A list of awesome &lt;code&gt;.cursorrules&lt;/code&gt; files.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A new Tiny Helper
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fju74dh62ek0x4wfem0d7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fju74dh62ek0x4wfem0d7.png" alt="Video Compressor — Compress videos right in the browser by up to 90% for free. No upload needed." width="800" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Addy continues his tooling journey! There are now &lt;a href="https://bg.addy.ie/" rel="noopener noreferrer"&gt;a tool to remove backgrounds from images&lt;/a&gt;, &lt;a href="https://squish.addy.ie/" rel="noopener noreferrer"&gt;a tool to compress images&lt;/a&gt; and the newest member — a tool to compress videos.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://compress.addy.ie/" rel="noopener noreferrer"&gt;Compress your videos&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Find more single-purpose online tools on &lt;a href="https://tiny-helpers.dev/" rel="noopener noreferrer"&gt;tiny-helpers.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Thought of the week
&lt;/h2&gt;

&lt;p&gt;In the spirit of the web platform, &lt;a href="https://csswizardry.com/2025/01/build-for-the-web-build-on-the-web-build-with-the-web/" rel="noopener noreferrer"&gt;here's some wisdom from Harry&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Every layer of abstraction made in the browser moves you further from the platform, ties you further into framework lock-in, and moves you further away from fast.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Did you learn something from this issue?
&lt;/h2&gt;

&lt;p&gt;💙 If so, &lt;strong&gt;join 21 other Web Weekly readers&lt;/strong&gt; and give back with a small monthly donation on &lt;a href="https://www.patreon.com/stefanjudis" rel="noopener noreferrer"&gt;Patreon&lt;/a&gt; or &lt;a href="https://github.com/sponsors/stefanjudis/" rel="noopener noreferrer"&gt;GitHub Sponsors&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  This is all, friends!
&lt;/h2&gt;

&lt;p&gt;Loved this email? Hated this email? I want to hear about it!&lt;/p&gt;

&lt;p&gt;If you think something needs improvement or something sparked some joy, &lt;a href="//mailto:stefan@webweekly.email"&gt;reply to this email because I want to know more&lt;/a&gt;!&lt;/p&gt;




&lt;p&gt;And with that, take care of yourself - mentally, physically, and emotionally.&lt;/p&gt;

&lt;p&gt;I'll see you next week! 👋&lt;/p&gt;

</description>
      <category>webdev</category>
    </item>
    <item>
      <title>🧢 Web Weekly 151 — `popover` enters the baseline!</title>
      <dc:creator>Stefan Judis</dc:creator>
      <pubDate>Wed, 12 Feb 2025 18:49:33 +0000</pubDate>
      <link>https://dev.to/webweekly/web-weekly-popover-enters-the-baseline-240p</link>
      <guid>https://dev.to/webweekly/web-weekly-popover-enters-the-baseline-240p</guid>
      <description>&lt;h2&gt;
  
  
  Guten Tag! Guten Tag! 👋
&lt;/h2&gt;

&lt;p&gt;Do you know what the European Accessibility Act will mean to you? Are view transitions (SPA or MPA) ready for production yet? Have you heard that &lt;code&gt;popover&lt;/code&gt; is now on the baseline?&lt;/p&gt;

&lt;p&gt;Turn on the Web Weekly tune and find all the answers below. Enjoy!&lt;/p&gt;




&lt;p&gt;Khaled listens to &lt;a href="https://www.youtube.com/watch?v=bjjc59FgUpg" rel="noopener noreferrer"&gt;"Cinematic Orchestra feat. Patrick Watson - To Build A Home"&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I listen to this song every now and then for about 14 years now!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Do you want to share your favorite song with the Web Weekly community? &lt;a href="//mailto:stefan@webweekly.email"&gt;Hit reply&lt;/a&gt;; there are &lt;strong&gt;two more songs&lt;/strong&gt; left in the queue.&lt;/p&gt;




&lt;p&gt;I'm a day late, so I'll keep this week's intro short, but I want to &lt;strong&gt;welcome all the new subscribers who joined last week&lt;/strong&gt;! 👋&lt;/p&gt;

&lt;p&gt;It's great to see Web Weekly growing, and if you have any comments, questions, or want to send over "good web stuff"™, let me know! &lt;/p&gt;

&lt;p&gt;I'm all ears (eyes? 🤷) and enjoy every bit of webdev chatter in my inbox!&lt;/p&gt;

&lt;p&gt;With that — let's go!&lt;/p&gt;




&lt;h2&gt;
  
  
  Something that made me smile this week
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwob23nw3t9lt8nu4dsz3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwob23nw3t9lt8nu4dsz3.png" alt="Add random jumpscares to sites you're trying to avoid " width="800" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm unsure if getting scared is the right approach to breaking social media addiction, but if you try TabBoo let me know how it went. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://tabboo.xyz" rel="noopener noreferrer"&gt;Break bad habits&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  New on the blog
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.stefanjudis.com/today-i-learned/global-git-commit-templates/" rel="noopener noreferrer"&gt;How to apply a global Git commit template&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.stefanjudis.com/blog/a-well-known-avatar-url-would-be-dang-cool/" rel="noopener noreferrer"&gt;A well-known avatar URL would be dang cool&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.stefanjudis.com/today-i-learned/the-difference-ts-ignore-and-ts-expect-error/" rel="noopener noreferrer"&gt;The difference between &lt;code&gt;@ts-ignore&lt;/code&gt; and &lt;code&gt;@ts-expect-error&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The European Accessibility Act (EAA)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/f20lfrunubsq/46oMg7Ofr7nX2EBWK4Ndwv/7a7a72bce2e6f1c1db36e3ac9c91aeda/Screenshot_2025-02-10_at_20.45.31.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/f20lfrunubsq/46oMg7Ofr7nX2EBWK4Ndwv/7a7a72bce2e6f1c1db36e3ac9c91aeda/Screenshot_2025-02-10_at_20.45.31.png" alt="The European Accessibility Act for websites and apps"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The European Accessibility Act will go into effect on June 28, 2025, and Martijn shares what you need to know.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://martijnhols.nl/blog/the-european-accessibility-act-for-websites-and-apps" rel="noopener noreferrer"&gt;Understand the EAA&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  On native CSS nesting
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9vqjjn1rabqlqlatcauf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9vqjjn1rabqlqlatcauf.png" alt="CSS nesting: use with caution" width="800" height="264"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Andy argues that browsers shouldn't have shipped native CSS nesting because it's a solely developer convenience feature that also comes with some footguns. &lt;/p&gt;

&lt;p&gt;I'm unsure if I agree with this statement, but the article includes wise words worth pinning on your monitor. When you write code, ask yourself:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Could a junior developer understand this code?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;💯 Future me will smartass the heck out of this idea. Get ready!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://piccalil.li/blog/css-nesting-use-with-caution/" rel="noopener noreferrer"&gt;Understand nesting&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  It's about people
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fywnod1ccwdz8048noutk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fywnod1ccwdz8048noutk.png" alt="Stories of Web Users in - How People with Disabilities Use the Web — " width="800" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This page isn't new, but it's so valuable. The WEC Web Accessibility Initiative put together user stories about making digital products more accessible.&lt;/p&gt;

&lt;p&gt;Bookmark it for the next time when someone tells you that accessibility is "about X".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.w3.org/WAI/people-use-web/user-stories/" rel="noopener noreferrer"&gt;Build for people&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to favicon in 2025
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6fjx2ab0j9m376p861fh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6fjx2ab0j9m376p861fh.png" alt="Three HTML lines showing how to best include a favicon" width="800" height="188"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's the time of the year again when the wonderful Martians research how to load a favicon the best way. 👆 Do the necessary spring cleaning if your sites include more HTML than this to load a favicon.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://evilmartians.com/chronicles/how-to-favicon-in-2021-six-files-that-fit-most-needs" rel="noopener noreferrer"&gt;Clean up your favicons&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The wonderful weird web – Stripe's Black Friday stats
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fge877mm81i8xptvh6wr9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fge877mm81i8xptvh6wr9.png" alt="A box with LED number displays." width="800" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Stripe must be doing very well when they have the time and money to build info pages like this one. 👆 Either way, it's fun to play with all the Black Friday stripe transaction data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://bfcm.stripe.com/" rel="noopener noreferrer"&gt;Check the stats&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Stripe also released &lt;a href="https://stripe.dev/" rel="noopener noreferrer"&gt;this fabulous developer portal&lt;/a&gt; not too long ago.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Replace your animation libs with &lt;code&gt;startViewTransition()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb1xztiz12exnvaedt1uh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb1xztiz12exnvaedt1uh.png" alt="Stefan next to navigation UI components and the text " width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In my opinion, view transitions are the biggest update the web has seen in many years because we can build so many cool effects with so little effort now.&lt;/p&gt;

&lt;p&gt;I used &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/startViewTransition" rel="noopener noreferrer"&gt;&lt;code&gt;startViewTransition()&lt;/code&gt;&lt;/a&gt; to &lt;strong&gt;rebuild a navigation component that relied on a heavy JS animation lib with ~50 lines of CSS&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.builder.io/blog/view-transitions" rel="noopener noreferrer"&gt;Read the post&lt;/a&gt; &lt;a href="https://www.youtube.com/watch?v=prAHP9miQDY" rel="noopener noreferrer"&gt;Watch the video&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Get started with MPA view transitions
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjzcu6fplkrn1n3f3ni1y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjzcu6fplkrn1n3f3ni1y.png" alt="Cross-document view transitions deep dive!" width="800" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cross-document view transitions allow us to animate and transition elements across good old HTML navigations. &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@view-transition" rel="noopener noreferrer"&gt;The new &lt;code&gt;@view-transition&lt;/code&gt; at-rule&lt;/a&gt; has been supported in Chromium for a while, and Safari started supporting it last December.&lt;/p&gt;

&lt;p&gt;If you want to spice up your static HTML sites, Kevin and Bramus live coding session will get you up to speed!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.bram.us/2025/01/26/mpa-view-transitions-deep-dive/" rel="noopener noreferrer"&gt;Transition across pages&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Managing focus
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fry5dwbjsdtxh7arpopc9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fry5dwbjsdtxh7arpopc9.png" alt="button.focus({focusVisible: false});" width="800" height="181"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ollie explains the differences between &lt;code&gt;:focus&lt;/code&gt; and &lt;code&gt;:focus-visible&lt;/code&gt;, and why the &lt;code&gt;focus()&lt;/code&gt; DOM method needs an update.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://fullystacked.net/programmatic-focus-styles/" rel="noopener noreferrer"&gt;Focus!&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Some WebDev Trivia...
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq4bsywkfbp5mtxgiikgd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq4bsywkfbp5mtxgiikgd.png" alt="Why does target=" width="800" height="246"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I admit I've never thought about this and didn't know. But do you know?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://kyrylo.org/html/2024/10/25/why-does-target-blank-have-an-underscore-in-front.html" rel="noopener noreferrer"&gt;Travel in time&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to start a blog
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpnt6n67kgyo6n4zof44n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpnt6n67kgyo6n4zof44n.png" alt="Advice for a friend who wants to start a blog" width="800" height="288"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unsurprisingly, blogging had a huge impact on my career (heck, my life even?).&lt;/p&gt;

&lt;p&gt;Blogging helps me order my thoughts, learn new things, become a better writer, get invited to more job interviews, connect with people — the list goes on and on. I should probably blog about the benefits of blogging.&lt;/p&gt;

&lt;p&gt;If that sounds convincing and you wonder how to get started, here's &lt;a href="https://www.henrikkarlsson.xyz/p/start-a-blog" rel="noopener noreferrer"&gt;some solid advice&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;And don't forget to send me your RSS feed if you kick things off and start a new blog.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Random MDN – &lt;code&gt;:user-valid&lt;/code&gt; / &lt;code&gt;:user-invalid&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0tscgaj1k0ua3qlu2jmx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0tscgaj1k0ua3qlu2jmx.png" alt="input:user-invalid {   border: 2px solid red; }" width="800" height="276"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From the unlimited MDN knowledge archive...&lt;/p&gt;

&lt;p&gt;Do you know that there are CSS classes that match valid/invalid form elements only after the user interacts with them? Now you do!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:user-invalid" rel="noopener noreferrer"&gt;Validate at the right time&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  TIL recap – table &lt;code&gt;caption&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/f20lfrunubsq/7IsBzcE4a6dWfWfb8fKLJV/bc6f838fae5716d631f33e31c899309e/Screenshot_2025-02-10_at_20.23.49.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/f20lfrunubsq/7IsBzcE4a6dWfWfb8fKLJV/bc6f838fae5716d631f33e31c899309e/Screenshot_2025-02-10_at_20.23.49.png" alt="HTML table including a  raw `caption` endraw  element"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Be honest. Do all your HTML tables include the required caption element? &lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.stefanjudis.com/today-i-learned/the-for-accessibility-required-caption-element-in-html-tables/" rel="noopener noreferrer"&gt;Give your tables a proper name&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Find more short web development learnings in &lt;a href="https://www.stefanjudis.com/today-i-learned/" rel="noopener noreferrer"&gt;my "Today I learned" section&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  New on the baseline — &lt;code&gt;popover&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbx1a2m6oyllazat03m0c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbx1a2m6oyllazat03m0c.png" alt="HTML code that includes  raw `popovertarget` endraw  and  raw `popover` endraw  attributes." width="800" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Big news: the popover API is now "newly available" on the baseline, and if you're now thinking, "Hold on! Wasn't it baseline ready last year already?", that's right.&lt;/p&gt;

&lt;p&gt;But webdev, spec work and browser implementations are hard. Rachel explained what happened and shared insights on how the baseline baseline evaluation works.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://web.dev/blog/popover-baseline" rel="noopener noreferrer"&gt;Pop over!&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Classifieds &amp;amp; friends
&lt;/h2&gt;

&lt;p&gt;If you can't get enough of WebDev news, I can highly recommend &lt;a href="https://weeklyfoo.com/" rel="noopener noreferrer"&gt;Adam's WeeklyFoo newsletter&lt;/a&gt;. It pairs wonderfully with Web Weekly to catch all the relevant web dev news!&lt;/p&gt;

&lt;h2&gt;
  
  
  Three valuable projects to have a look at
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/sindresorhus/emittery" rel="noopener noreferrer"&gt;sindresorhus/emittery&lt;/a&gt; –  A simpl and modern async event emitter.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/localsend/localsend" rel="noopener noreferrer"&gt;localsend/localsend&lt;/a&gt; –  An open-source cross-platform alternative to AirDrop.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/yamadashy/repomix" rel="noopener noreferrer"&gt;yamadashy/repomix&lt;/a&gt; – A tool to pack your entire repository into a single, AI-friendly file.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A new Tiny Helper
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2w5agl4d4ok3fahtra1n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2w5agl4d4ok3fahtra1n.png" alt="Modern Font Stacks - System font stack CSS organized by typeface classification for every modern OS. The fastest fonts available. No downloading, no layout shifts, no flashes — just instant renders." width="800" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.stefanjudis.com/blog/load-the-default-os-font-with-css/" rel="noopener noreferrer"&gt;When I blogged about &lt;code&gt;font-family: system-ui&lt;/code&gt;&lt;/a&gt;, a few people pointed me to &lt;strong&gt;Modern Font Stacks&lt;/strong&gt;. What is it?&lt;/p&gt;

&lt;p&gt;Modern operating systems come with more installed fonts than &lt;code&gt;Arial&lt;/code&gt;. Modern Font Stacks lets you play and test available fonts, so you don't have to deal with optimal font loading strategies. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://modernfontstacks.com/" rel="noopener noreferrer"&gt;Use what's already there&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Find more single-purpose online tools on &lt;a href="https://tiny-helpers.dev/" rel="noopener noreferrer"&gt;tiny-helpers.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Thought of the week
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://addyo.substack.com/p/the-70-problem-hard-truths-about" rel="noopener noreferrer"&gt;Addy shared his thoughts on all this AI for coding madness happening right now&lt;/a&gt;, and his opinion might surprise you.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;AI tools help experienced developers more than beginners.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Did you learn something from this issue?
&lt;/h2&gt;

&lt;p&gt;💙 If so, &lt;strong&gt;join 21 other Web Weekly readers&lt;/strong&gt; and support this small indie newsletter with a small monthly donation on &lt;a href="https://www.patreon.com/stefanjudis" rel="noopener noreferrer"&gt;Patreon&lt;/a&gt; or &lt;a href="https://github.com/sponsors/stefanjudis/" rel="noopener noreferrer"&gt;GitHub Sponsors&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  This is all, friends!
&lt;/h2&gt;

&lt;p&gt;Loved this email? Hated this email? I want to hear about it!&lt;/p&gt;

&lt;p&gt;If you think something needs improvement or something sparked some joy, &lt;a href="//mailto:stefan@webweekly.email"&gt;reply to this email because I want to know more&lt;/a&gt;!&lt;/p&gt;




&lt;p&gt;And with that, take care of yourself - mentally, physically, and emotionally.&lt;/p&gt;

&lt;p&gt;I'll see you next week! 👋&lt;/p&gt;

</description>
      <category>webdev</category>
    </item>
    <item>
      <title>Replace your JavaScript Animation Library with View Transitions</title>
      <dc:creator>Stefan Judis</dc:creator>
      <pubDate>Wed, 05 Feb 2025 14:14:28 +0000</pubDate>
      <link>https://dev.to/builderio/replace-your-javascript-animation-library-with-view-transitions-d9e</link>
      <guid>https://dev.to/builderio/replace-your-javascript-animation-library-with-view-transitions-d9e</guid>
      <description>&lt;p&gt;I've always wanted the web to win, full stop. When I compared the web experience to mobile apps, however, it always felt like the web was losing. After all these years, the web still feels like a collection of documents linked without delight or an excellent user experience. Don't get me wrong, with the CSS evolution in full swing; we got many new and useful features, but it was still missing an easy-to-use API to animate, move, and even morph elements into each other…&lt;/p&gt;

&lt;p&gt;Even simple animations like the ones below required wild and unmaintainable CSS hacks or heavy JavaScript animation libraries.&lt;/p&gt;



&lt;p&gt;For those rooting for the web, 2025 has now exciting news! Both Chrome and Safari shipped a new and fairly straightforward way to add animations and transitions to your sites — &lt;strong&gt;say “Hello” to the View Transitions API&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Let me show you how view transitions work by recreating this animation effect with a few freckles of modern web technology. A web technology that is probably the most significant web platform update in years.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Generating markup from your design&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Whenever I want to quickly sketch something out (like the code for this blog post), I reach for &lt;a href="https://www.builder.io/blog/figma-to-code-visual-copilot" rel="noopener noreferrer"&gt;Builder's Visual Copilot&lt;/a&gt;. Design something in Figma, import the design into Builder using &lt;a href="https://www.builder.io/c/docs/import-from-figma" rel="noopener noreferrer"&gt;the Figma plugin&lt;/a&gt;, and off you go.&lt;/p&gt;

&lt;p&gt;With a single command, you'll be ready to pull your codified design into your codebase.&lt;/p&gt;



&lt;p&gt;Pure HTML, React, and even Swift exports are available to give you a headstart. It's pretty sweet!&lt;/p&gt;

&lt;p&gt;But let's get to some animation magic.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The base setup&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;To explain the view transition core concepts, we'll use a vanilla approach to rebuilding this animated navigation — HTML, CSS, and good old JavaScript to toggle an &lt;code&gt;active&lt;/code&gt; class.&lt;/p&gt;

&lt;p&gt;That will be it; there won't be animation libraries or other external dependencies. All included concepts can be applied to sites built with or without frameworks.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you're wondering how to apply view transitions to your React, Vue, or Angular app, you'll &lt;a href="https://developer.chrome.com/docs/web-platform/view-transitions/same-document#working_with_frameworks" rel="noopener noreferrer"&gt;find resources on web.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So, where do we get started?&lt;/p&gt;

&lt;p&gt;Here's the HTML we'll use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;main&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;nav&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"active"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Home&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Projects&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;About&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/nav&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing fancy — it's just a list of links.&lt;/p&gt;

&lt;p&gt;And here's some basic JavaScript to toggle the &lt;code&gt;active&lt;/code&gt; class on all the included navigation links on click.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allLinks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;allLinks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setActiveItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;setActiveItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;allLinks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;active&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;active&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CSS will be pretty common stuff, except for the background of the active link element. The active link’s background isn’t rendered on the link itself but rather a pseudo-element. We'll need this extra pseudo-element to animate and slide it around later on.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="nc"&gt;.active&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;inset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;linear-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="nb"&gt;right&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#6a11cb&lt;/span&gt; &lt;span class="m"&gt;0%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#2575fc&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;z-index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's a live preview of how the project looks so far:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/stefanjudis/embed/VYZgaeO?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Getting started with JavaScript view transitions&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;So, what are they — these view transitions? Here's what &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API" rel="noopener noreferrer"&gt;MDN has to say about them&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The &lt;strong&gt;View Transition API&lt;/strong&gt; provides a mechanism for easily creating animated transitions between different website views. This includes animating between DOM states in a single-page app (SPA), and animating the navigation between documents in a multi-page app (MPA).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;View transitions allow you to take screenshots of DOM elements, then modify the DOM and magically transition from the old view to the new DOM representation.&lt;/p&gt;

&lt;p&gt;In this article, we'll cover the animation between different DOM states triggered via JavaScript (the SPA mode), but we will cover cross-document transitions in the future here on the blog. Check the newsletter box in the footer if you want to catch the next article.&lt;/p&gt;

&lt;p&gt;But let's get started, and I'll explain the concepts along the way. At the core of view transitions is the &lt;code&gt;startViewTransition&lt;/code&gt; JavaScript method.&lt;/p&gt;

&lt;p&gt;Here's the getting-started example snippet from &lt;a href="https://developer.chrome.com/docs/web-platform/view-transitions/same-document" rel="noopener noreferrer"&gt;the view transitions web.dev guide&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleClick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Fallback for browsers that don't support this API:&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startViewTransition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;updateTheDOMSomehow&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// With a View Transition:&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startViewTransition&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;updateTheDOMSomehow&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To animate between DOM states, you pass a callback function to &lt;code&gt;startViewTransition&lt;/code&gt; that modifies the DOM. You could add new DOM nodes or add or remove classes; either way, the browser will transition between the old and new DOM states.&lt;/p&gt;

&lt;p&gt;A quick note about browser support: Safari and Chromium support the new API at the time of writing, and I recommend treating view transitions as a progressive enhancement. Chromium and Safari (90% global share) users will then see the new and fancy web. For me, that's good enough, and I bet the rest won't miss it.&lt;/p&gt;

&lt;p&gt;Luckily, view transitions support is easy to detect. If &lt;code&gt;document.startViewTransition&lt;/code&gt; is available on the &lt;code&gt;document&lt;/code&gt; object, you can use the API to create transitions and animations. If it's not, you update the DOM as usual, and people on older browsers or Firefox won't see smooth transitions. In my opinion, that's a good trade and better than loading tons of JavaScript for everybody.&lt;/p&gt;

&lt;p&gt;Let's add &lt;code&gt;startViewTransition&lt;/code&gt; to our example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allLinks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;allLinks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Fallback for browsers that don't support this API:&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startViewTransition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setActiveItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Update the DOM with a view transition&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startViewTransition&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setActiveItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code is now similar to the initial example; the only change is that &lt;code&gt;setActiveItem&lt;/code&gt; is now called by &lt;code&gt;startViewTransition&lt;/code&gt; if the browser supports it.&lt;/p&gt;

&lt;p&gt;And look at this, with this minor tweak, we already see a cross-fade happening for the active element:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/stefanjudis/embed/OPLdXoE?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Pretty cool, right? But what's going on, and how does this work?&lt;/p&gt;

&lt;p&gt;The browser will attach new pseudo-elements to the DOM whenever you start a new view transition. You can see these elements in the Chrome dev tools while a view transition is happening.&lt;/p&gt;



&lt;p&gt;Above, you see Chrome's animation tooling (you can open it with &lt;code&gt;CMD+Shift+p&lt;/code&gt; and search for “animation”) that I use to debug the triggered view transition. Thanks to the animation tooling, we can see the view transition pseudo-elements in the DOM and even slow down the animations. Very handy!&lt;/p&gt;

&lt;p&gt;&lt;code&gt;::view-transition-old()&lt;/code&gt; is a screenshot of the old DOM state whereas &lt;code&gt;::view-transition-new()&lt;/code&gt; is a &lt;strong&gt;live&lt;/strong&gt; representation of the new DOM state. The old DOM screenshot is then transitioned to the new live DOM presentation. By default, this is done with a fade and mix-blend transition. But what are we transitioning here?&lt;/p&gt;

&lt;p&gt;If you look closely, you see that by using &lt;code&gt;startViewTransition&lt;/code&gt;, the browser will take the root element (the &lt;code&gt;html&lt;/code&gt; element) take a screenshot of it, and fade to the newly updated HTML DOM state. Just like that? Yep, you can add cross-fade transitions to your sites with a JavaScript one-liner.&lt;/p&gt;

&lt;p&gt;That's fun, but why stop there? Let's build up our navigation component and animate more than the root element.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Use &lt;code&gt;view-transition-name&lt;/code&gt; to animate elements separately&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;When we look at our target components from the beginning, we want to animate the active nav element separately. How can we achieve this?&lt;/p&gt;

&lt;p&gt;For these use cases, we can specify in CSS that we want to have another animation group with the &lt;code&gt;view-transition-name&lt;/code&gt; property.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* Move the active link into its own view transition layer */&lt;/span&gt;
&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="nc"&gt;.active&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;view-transition-name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;active-nav-elem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And look at this: this animation effect isn't quite what we're after, and it's not perfect yet (notice this small visual glitch), but it's quite impressive for a single line of CSS.&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/stefanjudis/embed/pvzGErg?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;If you inspect the DOM, you'll discover that there are more &lt;code&gt;view-transition-&lt;/code&gt; pseudo-elements.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.builder.io%2Fapi%2Fv1%2Fimage%2Fassets%252FYJIGb4i01jvw0SRdL5Bt%252Fef9f096c61f941abb344afc90706391c%3Fwidth%3D705" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.builder.io%2Fapi%2Fv1%2Fimage%2Fassets%252FYJIGb4i01jvw0SRdL5Bt%252Fef9f096c61f941abb344afc90706391c%3Fwidth%3D705" alt="View transition pseudo-elements rendered in the DOM." width="705" height="345"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;View transition pseudo-elements rendered in the DOM.&lt;/p&gt;

&lt;p&gt;We still have the view transition elements for &lt;code&gt;root&lt;/code&gt;, but there are now also elements for our active link (&lt;code&gt;active-nav-elem&lt;/code&gt;), which was put into its own group by applying the &lt;code&gt;view-transition-name&lt;/code&gt; property.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;::view-transition-group(active-nav-elem)&lt;/code&gt; transitions the elements’ positioning, transform, width, and height properties. Remember, it's a screenshot of the old active link and a live presentation of the new active link, and the browser transitions from one to the other. It doesn't matter that transitioning active links are completely different DOM nodes. The browser will just figure it out and visually morph one element into the other. Wild!&lt;/p&gt;

&lt;p&gt;What you see above is that the active link view transition is rendered above on top of a cross-fade root view transition. That's why it looks like links are fading away under the active element, but, in practice, it's the entire root element doing a cross-fade. Pretty cool, right?&lt;/p&gt;

&lt;p&gt;Next, let's fix this visible glitch in the active element transition.&lt;/p&gt;

&lt;p&gt;The problem is that the transition group tries to transition two elements with different dimensions and aspect ratios. The “Home” link is smaller than the “Projects” link.&lt;/p&gt;

&lt;p&gt;To smoothen the transition, we can target the two pseudo-elements in CSS, tweak the animation, and define that they both should match in height while still doing the cross-fade.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* Apply the same height to ease the view transition */&lt;/span&gt;
&lt;span class="nd"&gt;::view-transition-old&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;active-nav-elem&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
&lt;span class="nd"&gt;::view-transition-new&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;active-nav-elem&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isn't this cool?&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/stefanjudis/embed/azoXmaE?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Even though it looks pretty fancy already, this isn't the effect we are looking for. How could we make the highlighted gradient box slide under the text?&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Apply view transitions to pseudo-elements&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Do you remember that the background gradient was done with a &lt;code&gt;::before&lt;/code&gt; pseudo-element instead of setting a background on the active link? Now that we know about the &lt;code&gt;view-transition-name&lt;/code&gt; property, we can animate the pseudo-element instead of the entire link.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* Move the background to its own view transition layer */&lt;/span&gt;
&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="nc"&gt;.active&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;view-transition-name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;active-nav-elem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And again, this doesn't look too bad for such a tiny change. Does it?&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/stefanjudis/embed/GgKzjYa?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;But the background view transition is now covering the text fragments. How can we move the text on top of the ongoing background transition?&lt;/p&gt;

&lt;p&gt;We can put all the links into their own view transition layers and control all the view transition pseudo-elements with a &lt;code&gt;z-index&lt;/code&gt;!&lt;/p&gt;

&lt;p&gt;This could be done right in CSS…&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* Move all the links into their own view transition layer */&lt;/span&gt;
&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="nd"&gt;:nth-child&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;view-transition-name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;nav-1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="nd"&gt;:nth-child&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;2&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;view-transition-name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;nav-2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="nd"&gt;:nth-child&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;3&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;view-transition-name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;nav-3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;…and this kind of works, but if you use a templating engine or a framework, you're better off defining the view transition names in HTML with inline styles. It's more maintainable and will guarantee that you don't need to touch your CSS if you update the number of navigation items.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;main&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;nav&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"active"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"view-transition-name: nav-1;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Home&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"view-transition-name: nav-2;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Projects&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"view-transition-name: nav-3;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;About&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/nav&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This looks better, but still not quite right.&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/stefanjudis/embed/emOxdXE?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;When navigating forward, the background is placed under the text fragments, but you still see the background covering the text when going backward. Why's that?&lt;/p&gt;

&lt;p&gt;We didn't apply the &lt;code&gt;z-index&lt;/code&gt; to all our view transitions yet!&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Applying &lt;code&gt;z-index&lt;/code&gt; to separate view transitions&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;By default, view transitions are stacked on top of each other depending on where they are defined in the DOM. If the active link pseudo-element is first in our navigation, the view transition pseudo-elements look as follows.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.builder.io%2Fapi%2Fv1%2Fimage%2Fassets%252FYJIGb4i01jvw0SRdL5Bt%252Fc18462ba955b4713b1f343e2b849f5cc%3Fwidth%3D705" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.builder.io%2Fapi%2Fv1%2Fimage%2Fassets%252FYJIGb4i01jvw0SRdL5Bt%252Fc18462ba955b4713b1f343e2b849f5cc%3Fwidth%3D705" alt="View transition pseudo-lements highlight in DevTools. The ::view-transition-group(active-nav-elem) is highlighted on third position." width="705" height="289"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;View transition pseudo-lements highlight in DevTools. The &lt;code&gt;::view-transition-group(active-nav-elem)&lt;/code&gt; is highlighted on third position.&lt;/p&gt;

&lt;p&gt;We have the first text link (&lt;code&gt;nav-1&lt;/code&gt;) and then the pseudo-element on top of it. And going on, the &lt;code&gt;nav-2&lt;/code&gt; and &lt;code&gt;nav-3&lt;/code&gt; text elements are on top of the background pseudo-element.&lt;/p&gt;

&lt;p&gt;If we transition from the third link being active, the &lt;code&gt;active-nav-elem&lt;/code&gt; transition layer comes last and covers all the text links when it transitions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.builder.io%2Fapi%2Fv1%2Fimage%2Fassets%252FYJIGb4i01jvw0SRdL5Bt%252Fd37f9457a6b7438996d0c63edac2103b%3Fwidth%3D705" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.builder.io%2Fapi%2Fv1%2Fimage%2Fassets%252FYJIGb4i01jvw0SRdL5Bt%252Fd37f9457a6b7438996d0c63edac2103b%3Fwidth%3D705" alt="View transition pseudo-lements highlight in DevTools. The ::view-transition-group(active-nav-elem) is highlighted on last position." width="705" height="307"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;View transition pseudo-lements highlight in DevTools. The &lt;code&gt;::view-transition-group(active-nav-elem)&lt;/code&gt; is highlighted on last position.&lt;/p&gt;

&lt;p&gt;We need to apply a &lt;code&gt;z-index&lt;/code&gt; to our text layers to fix this. But how?&lt;/p&gt;

&lt;p&gt;To apply custom styles like the &lt;code&gt;z-index&lt;/code&gt; to the transition layers, we can target the transition groups in CSS.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* Stack the text over the animated background */&lt;/span&gt;
&lt;span class="nd"&gt;::view-transition-group&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;nav-1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;z-index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nd"&gt;::view-transition-group&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;nav-2&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;z-index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nd"&gt;::view-transition-group&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;nav-3&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;z-index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But hold on, isn't this the problem of having a hardcoded number of items in our CSS? That's correct, and there's a solution, but I wouldn't recommend using it in production yet.&lt;/p&gt;

&lt;p&gt;You could rely on the &lt;code&gt;view-transition-class&lt;/code&gt; property to target multiple view transition pseudo-elements, but &lt;a href="https://caniuse.com/mdn-css_properties_view-transition-class" rel="noopener noreferrer"&gt;its browser support isn't great yet&lt;/a&gt; (Chromium and recent Safari). MDN doesn't even have a docs page for it.&lt;/p&gt;

&lt;p&gt;View transition classes work similarly to “normal” classes. Give multiple elements the same view transition class and target the resulting layers with the view-transition pseudo-element selectors.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* Define a view transition class to target multiple view transition layers */&lt;/span&gt;
&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;view-transition-class&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;nav-item&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* Apply styles to multiple view transition layers */&lt;/span&gt;
&lt;span class="nd"&gt;::view-transition-group&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;.nav-item&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;z-index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're on Chromium or a recent Safari, here's the fixed stacking context with cutting-edge CSS.&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/stefanjudis/embed/dPbapLQ?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;So, as of now, I recommend hardcoding the view transition layers until &lt;code&gt;view-transition-class&lt;/code&gt; has better browser support. It’s not pretty, but it's not too bad, either. &lt;a href="https://frontendmasters.com/blog/view-transitions-staggering/" rel="noopener noreferrer"&gt;This article is excellent if you want to read more about view transition classes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There's one last bit of fanciness I want to add, though!&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Controlling view transitions with custom easing functions&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The background pseudo-element is now smoothly sliding below all the text links and it's looking pretty good, but I think it could look a bit more fun. Let's make it bounce and apply some custom easing functions to the transition group, that's wrapping the background element.&lt;/p&gt;

&lt;p&gt;Remember, view transitions are all about CSS; we can go in, apply a custom easing function, and even control the time of our background element transition.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--bounce&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;linear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="m"&gt;0.271&lt;/span&gt; &lt;span class="m"&gt;8.8%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="m"&gt;0.542&lt;/span&gt; &lt;span class="m"&gt;19.9%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="m"&gt;0.837&lt;/span&gt; &lt;span class="m"&gt;34.2%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="m"&gt;44.7%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="m"&gt;0.943&lt;/span&gt; &lt;span class="m"&gt;51.1%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="m"&gt;0.925&lt;/span&gt; &lt;span class="m"&gt;57.5%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="m"&gt;0.937&lt;/span&gt; &lt;span class="m"&gt;63.1%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="m"&gt;77.4%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="m"&gt;0.991&lt;/span&gt; &lt;span class="m"&gt;84.2%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* Tweak the active background animation */&lt;/span&gt;
&lt;span class="nd"&gt;::view-transition-group&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;active-nav-elem&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;animation-timing-function&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--bounce&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;animation-duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.375s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you haven't seen &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/easing-function/linear" rel="noopener noreferrer"&gt;the &lt;code&gt;linear()&lt;/code&gt; easing function&lt;/a&gt; yet, it allows us to create bouncy effects like the one below. You probably don't want to code these by hand, however, which is why I recommend using &lt;a href="https://easingwizard.com/" rel="noopener noreferrer"&gt;the Easing Wizard&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And now, look at the final result!&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/stefanjudis/embed/WbePaKO?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;I don't know about you, but I'm pretty excited about all this!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Quick side note: if you now go ahead and create animations heavily moving DOM elements around, be a good web citizen, and allow people to opt-out by &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion" rel="noopener noreferrer"&gt;using &lt;code&gt;@media (prefers-reduced-motion) {}&lt;/code&gt;&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Conclusion&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;View transitions allow us to finally push the web to the next level and I'm so there for it!&lt;/p&gt;

&lt;p&gt;I hope this small example shows the power of the new View Transitions API. We needed roughly 50 lines of CSS to build something that would usually require thousands of lines of JavaScript locking your CPU. The future is bright (and animated).&lt;/p&gt;

&lt;p&gt;If you want to learn more about view transitions, let us know in the comments, &lt;a href="//mailto:stefanjudis@gmail.com"&gt;shoot me an email&lt;/a&gt;, or tag us on social. If we get great feedback, we'll publish more articles on the matter soon. Oh, and make sure to subscribe to our newsletter below if you want to catch more articles on this topic.&lt;/p&gt;

&lt;p&gt;Until then — have fun dropping some heavy animation libraries from your code bases!&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/prAHP9miQDY"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Written by&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;span&gt;Stefan Judis&lt;/span&gt;
&lt;/h2&gt;

&lt;p&gt;Stefan loves getting into web performance, new technologies, and accessibility, sends out &lt;a href="https://webweekly.email/" rel="noopener noreferrer"&gt;a weekly web development newsletter&lt;/a&gt; and enjoys sharing &lt;a href="https://www.stefanjudis.com/today-i-learned/" rel="noopener noreferrer"&gt;nerdy discoveries on his blog&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>css</category>
      <category>animation</category>
    </item>
    <item>
      <title>Animate hero elements with scroll-driven CSS animations</title>
      <dc:creator>Stefan Judis</dc:creator>
      <pubDate>Mon, 16 Dec 2024 13:19:45 +0000</pubDate>
      <link>https://dev.to/builderio/animate-hero-elements-with-scroll-driven-css-animations-77</link>
      <guid>https://dev.to/builderio/animate-hero-elements-with-scroll-driven-css-animations-77</guid>
      <description>&lt;p&gt;When you navigate to github.com (you must be logged out because otherwise you'll enter the GitHub app), you'll be greeted by this landing page.&lt;/p&gt;



&lt;p&gt;It's a well-designed centered hero design with a sign-up box. It stands out that the hero text fades away while moving behind the code editor once you scroll down. How can you achieve such an effect?&lt;/p&gt;

&lt;p&gt;I was curious and kicked off a quick reverse engineering session to discover that everything's built with React, and the application listens to &lt;code&gt;scroll&lt;/code&gt; events. When scrolling, the JS updates the hero text's &lt;code&gt;scale&lt;/code&gt; and &lt;code&gt;opacity&lt;/code&gt; properties in a &lt;code&gt;requestAnimationFrame&lt;/code&gt; loop. In short, a lot is going on to create this effect!&lt;/p&gt;

&lt;p&gt;In this article, I will explain how to &lt;strong&gt;achieve a similar effect with modern (and future) CSS and zero JavaScript&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you want to look at the final example, check a &lt;a href="https://main.d3739ajl2fez28.amplifyapp.com/examples/github-hero" rel="noopener noreferrer"&gt;deployed demo version&lt;/a&gt; online!&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Some prep work&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;To have some code ready, I quickly designed a hero section in Figma and &lt;a href="https://www.builder.io/c/docs/import-from-figma" rel="noopener noreferrer"&gt;used the Builder Figma plugin&lt;/a&gt; to generate some React code with &lt;a href="https://www.builder.io/blog/figma-to-code-visual-copilot" rel="noopener noreferrer"&gt;Visual Copilot&lt;/a&gt;.&lt;/p&gt;



&lt;p&gt;&lt;span&gt;While my Figma-to-code result is less pretty than the GitHub example, this workflow lets me create something to work off in five minutes.&lt;/span&gt;&lt;/p&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Sliding the hero image over the text with &lt;code&gt;position: sticky&lt;/code&gt;&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A reasonable first step to recreate the GitHub effect is to slide the hero image over the hero text.&lt;/p&gt;

&lt;p&gt;To do this, we can stick the hero text at the top while scrolling. By using &lt;code&gt;position: sticky&lt;/code&gt;, we can place an element inside a container and "make it stick" somewhere when people scroll around.&lt;/p&gt;

&lt;p&gt;To give the hero text some air, we'll also add some space using the top CSS property.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.hero-text&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;/* Make the element sticky */&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sticky&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c"&gt;/* Give the element some space at the top */&lt;/span&gt;
  &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we add &lt;code&gt;position: sticky&lt;/code&gt;, we'll discover that the hero text scrolls on top of the other scrolling elements. This is exactly what you want in most sticky scenarios, but in our case, we want the sticky element to go below the rest.&lt;/p&gt;



&lt;p&gt;To make the hero text "slide under", we need to create a new stacking context for the other elements. &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_positioned_layout/Understanding_z-index/Stacking_context" rel="noopener noreferrer"&gt;There are many ways to do this&lt;/a&gt;, but I like to set &lt;code&gt;isolation: isolate&lt;/code&gt; to create a new stacking context because it's a nifty one-liner, and more people should know about the &lt;code&gt;isolation&lt;/code&gt; CSS property.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.hero-image&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;/* Create a new stacking context */&lt;/span&gt;
  &lt;span class="py"&gt;isolation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;isolate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And voila — look at this!&lt;/p&gt;



&lt;p&gt;That's not too shabby, isn't it? Now, we only need to add the scroll-fade behavior. How can we add it?&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Adding CSS-only scroll-driven animations&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;If you're reading this blog, you're probably aware of the ongoing CSS evolution. Let me tell you, there hasn't been a better time to be a web developer. To name a few recent CSS features: we now have container queries, the &lt;code&gt;:has()&lt;/code&gt; selector entered the stage, and view transitions make animating things easier than ever before.&lt;/p&gt;

&lt;p&gt;The modern web is fantastic, and one other exciting feature in the making is scroll-driven CSS animations. Scroll what? Yes, you heard that right!&lt;/p&gt;

&lt;h3&gt;
  
  
  What are scroll-driven CSS animations?
&lt;/h3&gt;

&lt;p&gt;Scroll-driven animations allow you to remove all these custom JavaScript scroll handlers and use the CSS Animations API to add scroll animation behavior.&lt;/p&gt;

&lt;p&gt;The API runs off the main thread and provides a massive performance boost compared to thousands of firing scroll handlers in JavaScript land. With scroll-driven CSS animations, you can create scroll effects with less and more performant code.&lt;/p&gt;

&lt;p&gt;The new CSS feature is currently supported only by Chromiums, but &lt;a href="https://github.com/WebKit/standards-positions/issues/152" rel="noopener noreferrer"&gt;Webkit is open to this platform addition&lt;/a&gt;, and Firefox ships an initial implementation behind a flag! Native scroll-driven animations will be coming to the web!&lt;/p&gt;

&lt;p&gt;If Chromium-only isn't good enough to use the new CSS feature yet, I understand. However, with Chromium-based browsers having a global market share of over 70%, it's fair to treat scroll effects as progressive enhancement. Browsers that support scroll-driven animations will show users some eye candy, while the other browsers will only render a headline on top of a hero image.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Side note: if scroll-driven animations are essential for your site, &lt;a href="https://github.com/flackr/scroll-timeline" rel="noopener noreferrer"&gt;there is also a polyfill&lt;/a&gt;. I haven't used it, but it might be a valid alternative to add custom scroll behavior.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But let's get to it and add some eye candy!&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding your first scroll-driven animation
&lt;/h3&gt;

&lt;p&gt;Scroll-driven animations are based on good old &lt;code&gt;@keyframes&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;fade-out&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can define a &lt;code&gt;fade-out&lt;/code&gt; animation that makes an element disappear by scaling it down and reducing its opacity. Next, we take our &lt;code&gt;hero-text&lt;/code&gt; class and set our scroll-driven animation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.hero-text&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;/* Set the animation */&lt;/span&gt;
  &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;fade-out&lt;/span&gt; &lt;span class="n"&gt;linear&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c"&gt;/* Connect the animation to the nearest scroller's scroll progress */&lt;/span&gt;
  &lt;span class="py"&gt;animation-timeline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;scroll&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And look at this: two added CSS declarations give us something to work off of already.&lt;/p&gt;



&lt;p&gt;Let's understand what's going on here!&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;hero-text&lt;/code&gt; class now sets the &lt;code&gt;fade-out&lt;/code&gt; keyframes using the &lt;code&gt;animation&lt;/code&gt; property. It isn't a "normal" &lt;code&gt;animation&lt;/code&gt; declaration, though.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;animation&lt;/code&gt; shorthand doesn't define an &lt;code&gt;animation-duration&lt;/code&gt;. Usually, time controls the keyframe animation progress, but in our case, the &lt;code&gt;animation-duration&lt;/code&gt; is set to &lt;code&gt;auto&lt;/code&gt; because we want to make it dependent on scroll progress and a scroll timeline.&lt;/p&gt;

&lt;p&gt;Below the &lt;code&gt;animation&lt;/code&gt; declaration, you'll see the new &lt;code&gt;animation-timeline&lt;/code&gt; property. Theoretically, &lt;code&gt;animation-timeline&lt;/code&gt; is also part of the &lt;code&gt;animation&lt;/code&gt; shorthand, but you can only use it to reset a timeline. To define an animation other than &lt;code&gt;auto&lt;/code&gt; we must define an animation followed by a specified &lt;code&gt;animation-timeline&lt;/code&gt;. Order matters in this case!&lt;/p&gt;

&lt;p&gt;But I got ahead of myself; what's an animation timeline?&lt;/p&gt;

&lt;h3&gt;
  
  
  Specifying an animation timeline
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;An animation timeline specifies the timeline used to control the progress of a CSS keyframe animation&lt;/strong&gt;. Usually, you control your animations by time which maps to &lt;code&gt;animation-timeline: auto&lt;/code&gt;. But with scroll-driven animations, there are now two new animation scroll timelines: scroll progress (&lt;code&gt;animation-timeline: scroll()&lt;/code&gt;) and view progress (&lt;code&gt;animation-timeline: view()&lt;/code&gt;) timelines.&lt;/p&gt;

&lt;h3&gt;
  
  
  Animating the hero element on scroll progress (&lt;code&gt;scroll()&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;A scroll progress timeline defined with &lt;strong&gt;the &lt;code&gt;scroll()&lt;/code&gt; CSS function maps the scroll progress of a wrapping scroll container&lt;/strong&gt; (also called scroller) to values from &lt;code&gt;0%&lt;/code&gt; to &lt;code&gt;100%&lt;/code&gt; and applies them to a keyframe animation. In our case, the nearest scroll container is the &lt;code&gt;html&lt;/code&gt; element. When scrolling down, the overall document scroll progress controls the &lt;code&gt;fade-out&lt;/code&gt; animation.&lt;/p&gt;

&lt;p&gt;And while this works, you might now wonder what happens with massively long HTML documents.&lt;/p&gt;



&lt;p&gt;&lt;em&gt;When the fade-out animation relies on document scroll progress, a** longer document affects the animation progress.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;scroll()&lt;/code&gt; comes in handy if you want to display a document scroll progress indicator, but for our hero animation, it doesn't make sense to use a scroll progress timeline. If you scroll 500px down in a 2000px high document, the animation will progress by 25%. But when the document grows to 10000px, the scroll timeline will only progress to 5%. The animation will be barely noticeable. Our animation should rely on something other than the document length!&lt;/p&gt;

&lt;p&gt;Luckily, we can use another scroll timeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  Animating the hero element on view progress (&lt;code&gt;view()&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;If you don't want to consider the overall scroll progress, there's another way to control scroll-driven animations. &lt;strong&gt;The new &lt;code&gt;view()&lt;/code&gt; CSS function allows you to consider the visibility of an element inside a scroller&lt;/strong&gt;. The &lt;code&gt;view()&lt;/code&gt; function is quite a beast to wrap your head around, but most importantly, view progress timelines are also wildly powerful!&lt;/p&gt;

&lt;p&gt;I will only go into some of the details here (check the resources below if you want to learn more), but let's take a step back to rethink how the visual effect should work.&lt;/p&gt;

&lt;p&gt;We want to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Start the keyframe animation at &lt;code&gt;0%&lt;/code&gt; when our hero starts moving off the screen.&lt;/li&gt;
&lt;li&gt;Finish the keyframe animation at &lt;code&gt;100%&lt;/code&gt; when the hero disappears.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To achieve this behavior, we have to adjust the animation range, and of course, there's another new CSS property for it — say hello to &lt;code&gt;animation-range&lt;/code&gt;. &lt;code&gt;animation-range&lt;/code&gt; enables us to define the start and end of our scroll-driven animation. For our &lt;code&gt;fade-out&lt;/code&gt; animation, we're interested in kicking off the animation when an element starts disappearing (&lt;code&gt;0%&lt;/code&gt;) until it's entirely gone (&lt;code&gt;100%&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;For this use case, we can add an exit animation range. Let's go ahead and put this into the CSS.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.hero-text&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;fade-out&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;ease-in-out&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;animation-timeline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="c"&gt;/* Apply a custom animation range */&lt;/span&gt;
  &lt;span class="py"&gt;animation-range&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;0%&lt;/span&gt; &lt;span class="n"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you add the custom animation range, you'll discover that, for our hero animation, it doesn't work, though.&lt;/p&gt;



&lt;p&gt;We defined the hero text to be sticky, and this positioning implies that it will leave the scroller only when the surrounding container is moving off the screen. The hero text animation is applied, but it's not visible because our hero dummy image covers the scroll exit animation.&lt;/p&gt;

&lt;p&gt;When we remove the stickiness, we'll discover that our &lt;code&gt;animation-range&lt;/code&gt; works appropriately. What can we do now that we want to animate a sticky element?&lt;/p&gt;

&lt;h3&gt;
  
  
  Attaching a named view progress timeline to animate sticky elements
&lt;/h3&gt;

&lt;p&gt;Of course, the new scroll-driven animations have a solution to this problem, too. To make our animation visible, we need to control the animation depending on the view progress of another element with a named view progress timeline.&lt;/p&gt;

&lt;p&gt;In our CSS, we can go into the wrapping hero container and specify a &lt;code&gt;view-timeline-name&lt;/code&gt;…&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.hero-container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;/* Define a named view progress timeline */&lt;/span&gt;
  &lt;span class="py"&gt;view-timeline-name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;--hero-scroll&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;… to then change our hero text to use the view timeline instead of &lt;code&gt;view()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.hero-text&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;fade-out&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;ease-in-out&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c"&gt;/* Specify the container animation timeline */&lt;/span&gt;
  &lt;span class="py"&gt;animation-timeline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;--hero-scroll&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;animation-range&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;0%&lt;/span&gt; &lt;span class="n"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now check this out!&lt;/p&gt;



&lt;p&gt;Our hero text element fades out and scales down right when the surrounding container starts leaving the screen. And how much CSS did this take us to write? It was only a few CSS declarations and one keyframe animation. I'm amazed!&lt;/p&gt;

&lt;p&gt;Let's consider a few more things to be good web citizens, though.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Watching out for browser support and user preferences&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;First, some folks can feel discomfort or even sickness when faced with unexpected movements on screen. To build &lt;em&gt;"a good web™"&lt;/em&gt;, we can add a CSS &lt;code&gt;reduced-motion&lt;/code&gt; media query to allow people to opt out of moving animations.&lt;/p&gt;

&lt;p&gt;Second, while creating the demo, I discovered that Firefox's flagged implementation doesn't support the &lt;code&gt;animation-range&lt;/code&gt; property yet. To resolve this, we can also add a &lt;code&gt;@supports&lt;/code&gt; feature query to avoid a broken animation in browsers that don't support scroll-driven animations yet.&lt;/p&gt;



&lt;p&gt;If you ask me, the fallback looks excellent as well, but of course, you could also remove the stickiness if you don't like the image sliding over the text.&lt;/p&gt;

&lt;p&gt;So, here's the final code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@supports&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;animation-range&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefers-reduced-motion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;no-preference&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;.hero-container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="py"&gt;view-timeline-name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;--hero-scroll&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;.hero-text&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;fade-out&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;ease-in-out&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="py"&gt;animation-timeline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;--hero-scroll&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="py"&gt;animation-range&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;0%&lt;/span&gt; &lt;span class="n"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.hero-text&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sticky&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.hero-image&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;isolation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;isolate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;fade-out&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isn't this magical and fun? I'm a big fan!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;So, with only a few lines of modern CSS, we rebuilt this JavaScript-heavy effect on GitHub's landing page. Our scroll animation is now more maintainable and performant because we're not relying on any JavaScript blocking the main thread!&lt;/p&gt;

&lt;p&gt;And because we're relying on modern CSS paired with progressive enhancement, we can give users the best experience if their browsers support it. Win-win!&lt;/p&gt;

&lt;p&gt;If you want to see the effect in action, here's the &lt;a href="https://main.d3739ajl2fez28.amplifyapp.com/examples/github-hero" rel="noopener noreferrer"&gt;deployed version&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Are you hooked already? I indeed am!&lt;/p&gt;

&lt;h3&gt;
  
  
  Next steps
&lt;/h3&gt;

&lt;p&gt;If you want to learn more about scroll-driven animations, there's a single place to go. Bramus from the Chrome DevRel team, aka "Mr. ScrollAnimation", maintains &lt;a href="https://scroll-driven-animations.style/" rel="noopener noreferrer"&gt;scroll-driven-animations.style&lt;/a&gt;. The site lists many tools, resources, and visual examples.&lt;/p&gt;

&lt;p&gt;If you prefer learning with videos, &lt;a href="https://www.youtube.com/playlist?list=PLNYkxOF6rcICM3ttukz9x5LCNOHfWBVnn" rel="noopener noreferrer"&gt;Bramus has also released a free course on YouTube&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And if you wonder how to debug scroll-driven animations, &lt;a href="https://chromewebstore.google.com/detail/scroll-driven-animations/ojihehfngalmpghicjgbfdmloiifhoce?hl=en" rel="noopener noreferrer"&gt;this Chrome extension is invaluable for figuring out what's going on&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And now we're done, have fun animating, and let me know how it goes!&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/xTU1qWZHm10"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Written by&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;span&gt;Stefan Judis&lt;/span&gt;
&lt;/h2&gt;

&lt;p&gt;Stefan loves getting into web performance, new technologies, and accessibility, sends out &lt;a href="https://webweekly.email/" rel="noopener noreferrer"&gt;a weekly web development newsletter&lt;/a&gt; and enjoys sharing &lt;a href="https://www.stefanjudis.com/today-i-learned/" rel="noopener noreferrer"&gt;nerdy discoveries on his blog&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>css</category>
      <category>webdev</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Forms without an accessible name are not exposed as ARIA landmarks</title>
      <dc:creator>Stefan Judis</dc:creator>
      <pubDate>Fri, 19 Jul 2024 13:51:23 +0000</pubDate>
      <link>https://dev.to/stefanjudis/forms-without-an-accessible-name-are-not-exposed-as-aria-landmarks-3132</link>
      <guid>https://dev.to/stefanjudis/forms-without-an-accessible-name-are-not-exposed-as-aria-landmarks-3132</guid>
      <description>&lt;p&gt;Suppose you want to be a good web citizen; you use semantic and accessible HTML. Elements "leading places" are &lt;code&gt;a&lt;/code&gt; elements, your navigation lives in a &lt;code&gt;nav&lt;/code&gt; element, buttons are ... well ... &lt;code&gt;button&lt;/code&gt;s, and your form controls are surrounded by a good old &lt;code&gt;form&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Using the correct elements gives your HTML meaning. Semantic markup helps machines understand what they're dealing with. Googlebot slurping in your site will understand the important sections of your articles because it finds their headings. Assistive technology like screen readers can offer features and a better UX to navigate your site. Throw away the &lt;code&gt;div&lt;/code&gt; soup and go for semantic HTML. It's great stuff!&lt;/p&gt;

&lt;p&gt;For example, I can fire up VoiceOver on my Mac and navigate my site via all the exposed ARIA landmark regions exposed from semantic HTML.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2Ff20lfrunubsq%2F3yBTYonLkC2xNZvQhDOr7d%2F24442c4f218b5685b7726837ba927fd7%2FScreenshot_2024-07-19_at_14.10.33.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2Ff20lfrunubsq%2F3yBTYonLkC2xNZvQhDOr7d%2F24442c4f218b5685b7726837ba927fd7%2FScreenshot_2024-07-19_at_14.10.33.png" alt="Voice over showing the landmarks available on stefanjudis.com"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;banner&lt;/code&gt; maps to the &lt;code&gt;header&lt;/code&gt; element, &lt;code&gt;navigation&lt;/code&gt; is the &lt;code&gt;nav&lt;/code&gt; element. And if an element has an accessible name, it's also listed next to its ARIA role. Good stuff!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you look at the &lt;code&gt;navigation&lt;/code&gt; landmark, I added the accessible names of &lt;code&gt;Main&lt;/code&gt; and &lt;code&gt;Footer&lt;/code&gt; because the page has two nav elements. The articles are enriched with their title, too.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But hold on! I know that there's a form on the page. Why isn't the newsletter signup form shown in the landmarks overview from VoiceOver? &lt;/p&gt;

&lt;p&gt;As a first step, I opened the devtools to inspect the accessibility panel.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2Ff20lfrunubsq%2F7FVZHZqP7bzzSh2P1CBH1J%2F7561ec38867e9b1240b9a2b3352b79ae%2FScreenshot_2024-07-19_at_13.37.08.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2Ff20lfrunubsq%2F7FVZHZqP7bzzSh2P1CBH1J%2F7561ec38867e9b1240b9a2b3352b79ae%2FScreenshot_2024-07-19_at_13.37.08.png" alt="Accessibility panel of Chrome DevTools highlighting that the form has a role of "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;role=generic&lt;/code&gt;? My form element didn't come with an accessible &lt;code&gt;form&lt;/code&gt; role but &lt;code&gt;generic&lt;/code&gt;? I feel betrayed. &lt;/p&gt;

&lt;p&gt;A quick look at &lt;a href="https://www.w3.org/TR/html-aria/#docconformance" rel="noopener noreferrer"&gt;the ARIA specs&lt;/a&gt; unveils the answer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2Ff20lfrunubsq%2F2pdqXzC9d5BcwiucERzYkb%2F87adc4dccd7d71d5dda6d8ad13bd4471%2FScreenshot_2024-07-19_at_13.27.56.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2Ff20lfrunubsq%2F2pdqXzC9d5BcwiucERzYkb%2F87adc4dccd7d71d5dda6d8ad13bd4471%2FScreenshot_2024-07-19_at_13.27.56.png" alt="ARIA specification highlighting that "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A form is not exposed as landmark region unless it has been provided an accessible name.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Wow! If you're not setting accessible names on your forms, they won't have the &lt;code&gt;form&lt;/code&gt; role, which means they're not marked as forms and thus hard to discover and inaccessible. &lt;/p&gt;

&lt;p&gt;How can you provide an accessible name?&lt;/p&gt;

&lt;p&gt;There are many different ways, and each depends on the element. &lt;/p&gt;

&lt;p&gt;A heading, for example, receives its accessible name from its content. Easy. Form controls receive it from their connected &lt;code&gt;label&lt;/code&gt;. Label your inputs, folks! It really depends on the element. If you're curious and want to learn more, read the guide &lt;a href="https://www.w3.org/WAI/ARIA/apg/practices/names-and-descriptions/" rel="noopener noreferrer"&gt;"Providing Accessible Names and Descriptions"&lt;/a&gt;. But what about forms, then?&lt;/p&gt;

&lt;p&gt;Here's a bit more guidance from &lt;a href="https://w3c.github.io/aria/#form" rel="noopener noreferrer"&gt;the WAI-ARIA 1.3 spec&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Authors &lt;strong&gt;MUST&lt;/strong&gt; give each element with role form a brief label that describes the purpose of the form. Authors &lt;strong&gt;SHOULD&lt;/strong&gt; reference a visible label with aria-labelledby if a visible label is present. Authors SHOULD include the label inside of a heading whenever possible.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That makes a lot of sense. If you build up a form, there should be something nearby telling what this form is about. If it is, you can connect the form with the element representing its purpose via &lt;code&gt;aria-labelledby&lt;/code&gt; and provide a nice accessible name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;aria-labelledby=&lt;/span&gt;&lt;span class="s"&gt;"form-heading"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"form-heading"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Newsletter sign up&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- more form stuff --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you can't include a visual heading for "reasons", you can also fall back to providing a string value via &lt;code&gt;aria-label&lt;/code&gt;. But note that a form including a visual explanation of what it's about comes with a better user experience, and &lt;a href="https://adrianroselli.com/2019/11/aria-label-does-not-translate.html#Update05" rel="noopener noreferrer"&gt;&lt;code&gt;aria-label&lt;/code&gt; may also break if folks use in-browser translations&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Newsletter sign up"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- more form stuff --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With these tweaks, my &lt;code&gt;form&lt;/code&gt; now shows up in VoiceOver, is enriched with an accessible name and has a proper &lt;code&gt;form&lt;/code&gt; role. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2Ff20lfrunubsq%2F1u5iXXMqxGjxBq6jlCC9Dj%2F55540717c3e6e88e8a37d18f71a07788%2FScreenshot_2024-07-19_at_14.15.01.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2Ff20lfrunubsq%2F1u5iXXMqxGjxBq6jlCC9Dj%2F55540717c3e6e88e8a37d18f71a07788%2FScreenshot_2024-07-19_at_14.15.01.png" alt="VoiceOver and Chrome accessibility devtools showing the form with a proper "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Success!&lt;/p&gt;

</description>
      <category>html</category>
      <category>webdev</category>
    </item>
    <item>
      <title>TIL — How to exclude elements from being shown in Google Search results</title>
      <dc:creator>Stefan Judis</dc:creator>
      <pubDate>Fri, 19 Jan 2024 06:14:02 +0000</pubDate>
      <link>https://dev.to/stefanjudis/til-how-to-exclude-elements-from-being-shown-in-google-search-results-2hl8</link>
      <guid>https://dev.to/stefanjudis/til-how-to-exclude-elements-from-being-shown-in-google-search-results-2hl8</guid>
      <description>&lt;p&gt;Let's look at what happens when I google myself.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2Ff20lfrunubsq%2F33jyg4GdWdfFqpMbYOxo5J%2F48dcbbf14017688bd9250a717420ed18%2FScreenshot_2024-01-18_at_17.45.26.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2Ff20lfrunubsq%2F33jyg4GdWdfFqpMbYOxo5J%2F48dcbbf14017688bd9250a717420ed18%2FScreenshot_2024-01-18_at_17.45.26.png" alt="Developer, writer and speaker | Stefan Judis Web Development — Heyooo, I'm Stefan! I write and speak about web stuff. · New on the blog · My Weekly Newsetter · Most popular in the last 30 days · Hacker News Hits." width="800" height="223"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Of course, I know how my site is structured, and I can tell that Google displays my site's title and the description includes some of the root page's headlines. The description is not the worst, but it certainly could be better. &lt;/p&gt;

&lt;p&gt;Is there a way to exclude elements from being shown in the Google snippet?&lt;/p&gt;

&lt;p&gt;Today I learned that there is! And the solution is based on an HTML data attribute: &lt;code&gt;data-nosnippet&lt;/code&gt;. Here's the example snippet from &lt;a href="https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#data-nosnippet-attr" rel="noopener noreferrer"&gt;the Google Search Central docs&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;This text can be shown in a snippet
&lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;data-nosnippet&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;and this part would not be shown&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;data-nosnippet&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;not in snippet&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;data-nosnippet=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;also not in snippet&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;data-nosnippet=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;also not in snippet&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;data-nosnippet&lt;/code&gt; works on &lt;code&gt;div&lt;/code&gt;, &lt;code&gt;span&lt;/code&gt; and &lt;code&gt;section&lt;/code&gt; elements, and it doesn't care what value you'll give it. &lt;code&gt;data-nosnippet="true"&lt;/code&gt; and &lt;code&gt;data-nosnippet="false"&lt;/code&gt; have the same result — signaling that this element shouldn't pop up in the Google Search results. Nice!&lt;/p&gt;

&lt;p&gt;If you're unhappy with the search results, add some juicy data attributes now. But remember to trigger a reindex in the Search Console. Otherwise, it might take a while until your changes have an effect.&lt;/p&gt;

</description>
      <category>html</category>
      <category>seo</category>
    </item>
    <item>
      <title>Using Playwright to Monitor Third-Party Resources That Could Impact User Experience</title>
      <dc:creator>Stefan Judis</dc:creator>
      <pubDate>Wed, 15 Feb 2023 16:03:57 +0000</pubDate>
      <link>https://dev.to/checkly/using-playwright-to-monitor-third-party-resources-that-could-impact-user-experience-2lek</link>
      <guid>https://dev.to/checkly/using-playwright-to-monitor-third-party-resources-that-could-impact-user-experience-2lek</guid>
      <description>&lt;p&gt;Today’s web consists of lots of 3rd party resources. Let it be your fonts, transformed and optimized media assets, or analytics and ad scripts, many sites out there include resources that they don’t own. Your website probably has a lot of those dependencies, too!&lt;/p&gt;

&lt;p&gt;And while implementing third-party resources has downsides for performance and &lt;a href="https://csswizardry.com/2019/05/self-host-your-static-assets/" rel="noopener noreferrer"&gt;you should self-host your assets when possible&lt;/a&gt;, sometimes relying on external files is unavoidable. But be warned — using them can sometimes &lt;strong&gt;cause real headaches.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As &lt;a href="https://chriscoyier.net/2023/02/03/end-to-end-tests-with-content-blockers/" rel="noopener noreferrer"&gt;Chris Coyier pointed out&lt;/a&gt;, external resources are often integrated too tightly into your own code, or implemented the wrong way, so that things can really go sideways. Ad blockers could block your external scripts or a service vendor could be down. Both situations can result in frontend downtime, poor UX, or entirely broken functionality on your end.&lt;/p&gt;

&lt;p&gt;And while Chris asks if you could detect the cases of broken sites due to ad blockers with end-to-end testing, I think it’s more about all the 3rd party code running on the internet.&lt;/p&gt;

&lt;p&gt;But heck yeah, testing and monitoring of failing external resources is possible!&lt;/p&gt;

&lt;p&gt;Let’s have a look at an example that could be affected by 3rd party code and ways to monitor your stack with Playwright to guarantee that no external providers or ad blockers can mess with your business. 🫡&lt;/p&gt;

&lt;h2&gt;
  
  
  Examples of problematic external resource usage
&lt;/h2&gt;

&lt;p&gt;But first, let’s look at patterns that can break your sites.&lt;/p&gt;

&lt;p&gt;In my experience, there are two challenging 3rd party resource scenarios.&lt;/p&gt;

&lt;h3&gt;
  
  
  Slow and render-blocking resources
&lt;/h3&gt;

&lt;p&gt;Years ago, I worked in an ecommerce startup and we were serving ad banners via an external provider. It was a quick solution to receive click statistics while providing marketers with a comfortable way to handle images on the site.&lt;/p&gt;

&lt;p&gt;The banners were implemented with synchronous script elements above the fold.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/javascript"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"http://some-provider.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we had plenty of these elements embedded in a slideshow. The scripts would load and replace containers with content coming from elsewhere. It worked beautifully… until it didn’t.&lt;/p&gt;

&lt;p&gt;A synchronous script element stops the HTML parser and forces it to wait for the script to be downloaded and executed. The browser only continues parsing and rendering once this is done so that this script resulting in an embedded image can literally block anything on your site.&lt;/p&gt;

&lt;p&gt;And that’s what exactly happened to me back in the day.&lt;/p&gt;

&lt;p&gt;The script was loaded fairly quickly from Berlin, Germany and we didn’t notice any delays in rendering. But one day our third-party vendor experienced an outage. And interestingly, requests wouldn’t fail but just hang and time out eventually.&lt;/p&gt;

&lt;p&gt;The result: a browser started rendering our site, stopped at the slideshow and waited for the scripts to time out. Our customers were looking at a white page until this happened and our site was entirely broken. &lt;strong&gt;A third-party vendor brought our frontend down&lt;/strong&gt; because we implemented synchronous scripts. Be aware that every external dependency is another risk for your sites!&lt;/p&gt;

&lt;p&gt;That’s why it’s generally better to self-host your assets and avoid critical render-blocking elements. No matter if you’re using ad tech, error monitoring scripts, or external stylesheets, make sure to double-check what happens when these resources fail. I doubt you want your system to be affected because of someone else's downtime.&lt;/p&gt;

&lt;p&gt;That’s not the only situation where third-party resources can lead to trouble, though. Let’s come back to Chris’ example.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tightly coupled JavaScript dependencies
&lt;/h3&gt;

&lt;p&gt;Chris mentioned that he experiences broken sites on a regular basis. Some sites would fail in essential situations like making an order. The culprit: a thrown JavaScript exception.&lt;/p&gt;

&lt;p&gt;That’s interesting because it’s unlikely that web developers aren’t testing their most important functionality. So what’s happening?&lt;/p&gt;

&lt;p&gt;If you’re an ad blocker user there’s a high chance that this tiny browser extension is the reason. And that’s not the ad blocker’s fault. It’s doing what it’s supposed to do — blocking trackers (most likely third-party scripts). But the issue is often that developers expect these scripts to be loaded. For example, if your website’s JavaScript expects a &lt;code&gt;sendTracking&lt;/code&gt; method to be available in the global window object, an ad blocker can easily mess with the site and cause JavaScript to fall on its nose.&lt;/p&gt;

&lt;p&gt;Here’s Chris’ example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// https://tracking-website.com/tracking-script.js&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trackThing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// report data&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// /script/index.js&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// this throws an exception if `tracking-script.js` was blocked&lt;/span&gt;
  &lt;span class="nf"&gt;trackThing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;page loaded or something&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;tracking-script.js&lt;/code&gt; fails or is blocked by a browser extension your code throws an exception and could blow up your entire app. If you’re implementing external JavaScript functionality, you should make sure that things are still functional in error cases because I bet you prefer processing an order over tracking it.&lt;/p&gt;

&lt;p&gt;But how can you go around these two problems and guarantee that you won’t be running into frontend downtimes based on synchronous resources or “death by ad blocker” scenarios in the future?&lt;/p&gt;

&lt;h2&gt;
  
  
  How to test and monitor risky third-party resources with Playwright
&lt;/h2&gt;

&lt;p&gt;Let’s take a simple HTML example running on my localhost:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;http-equiv=&lt;/span&gt;&lt;span class="s"&gt;"X-UA-Compatible"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"IE=edge"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Frontend downtime&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css"&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt;
&lt;span class="na"&gt;integrity=&lt;/span&gt;&lt;span class="s"&gt;"sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD"&lt;/span&gt; &lt;span class="na"&gt;crossorigin=&lt;/span&gt;&lt;span class="s"&gt;"anonymous"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Hello world&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"&lt;/span&gt;
&lt;span class="na"&gt;integrity=&lt;/span&gt;&lt;span class="s"&gt;"sha384-w76AqPfDkMBDXo30jS1Sgez6pr3x5MlQ1ZAGC+nuZB+EYdgRZgiwxhTBTkF7CXvN"&lt;/span&gt;
&lt;span class="na"&gt;crossorigin=&lt;/span&gt;&lt;span class="s"&gt;"anonymous"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;bootstrap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Alert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOrCreateInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#myAlert&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It includes Bootstrap’s CSS and JavaScript, renders the headline “Hello world” and tries to access &lt;code&gt;bootstrap.Alert&lt;/code&gt; which is available in the global window scope.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note that in this case, both external resources are render-blocking.&lt;/strong&gt; The stylesheet has to be loaded before anything appears on the screen and even though the script is placed at the end of the document, it will still block rendering (there’s just nothing to render after it).&lt;/p&gt;

&lt;p&gt;A quick Playwright test for this page could look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// @ts-check&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;has the correct title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:8080&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// evaluate the largest contentful paint&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;largestContentfulPaint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PerformanceObserver&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getEntries&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="c1"&gt;// the last entry is the largest contentful paint&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;largestPaintEntry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;largestPaintEntry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startTime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;largest-contentful-paint&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;buffered&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;largestContentfulPaint&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/Frontend downtime/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Playwright navigates to localhost and &lt;a href="https://www.checklyhq.com/learn/headless/basics-performance/#largest-contentful-paint-api-largest-contentful-paint" rel="noopener noreferrer"&gt;evaluates the popular largest contentful paint metric by injecting custom JavaScript into the page&lt;/a&gt;. When you run the script with &lt;code&gt;npx playwright test&lt;/code&gt; you’ll see the following in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Running 1 test using 1 worker
[chromium] › example.spec.js:4:1 › has the correct title
CLP: 116.39999999850988ms

1 passed (837ms)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wonderful! The largest contentful paint is a hundred milliseconds long and everything works. But what if the Bootstrap resources on &lt;code&gt;jsdeliver.net&lt;/code&gt; are slow to respond?&lt;/p&gt;

&lt;h3&gt;
  
  
  How to emulate slow third-party resources with Playwright
&lt;/h3&gt;

&lt;p&gt;Let’s find out and delay requests by ten seconds that aren’t fetching resources from the same origin with &lt;a href="https://playwright.dev/docs/api/class-page#page-route" rel="noopener noreferrer"&gt;Playwright’s &lt;code&gt;page.route&lt;/code&gt; method&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// @ts-check&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;works with slows resources&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;**&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requestURL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requestURL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/http:&lt;/span&gt;&lt;span class="se"&gt;\/\/&lt;/span&gt;&lt;span class="sr"&gt;localhost:8080/&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Delaying &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;requestURL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:8080&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;largestContentfulPaint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PerformanceObserver&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getEntries&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="c1"&gt;// the last entry is the largest contentful paint&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;largestPaintEntry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;largestPaintEntry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startTime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;largest-contentful-paint&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;buffered&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`CLP: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;largestContentfulPaint&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;ms`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// Expect a title "to contain" a substring.&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/Frontend downtime/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test still passes but its test duration skyrocketed from 800ms to 10 seconds and similarly, the largest contentful paint also joined at the ten seconds mark.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Running 1 test using 1 worker
[chromium] › example.spec.js:4:1 › has the correct title
Delaying https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css
Delaying https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js

CLP: 10186.10000000149ms

1 passed (11.0s)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What is going on? &lt;a href="https://www.youtube.com/watch?v=18Cc5Ejgu2o&amp;amp;list=PLMZDRUOi3a8NtMq3PUS5iJc2pee38rurc&amp;amp;index=11" rel="noopener noreferrer"&gt;Let’s create a Playwright trace and debug this test.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh6.googleusercontent.com%2F9Es1t47UzsKOwnByrLbyJ0iztC6W3ZL3t51S3mIWof3gQWCtKaL1WeYkKemRIKzmhB868slQB7F_1E6t_7zjtqG8Vad9z9YsJ08uOew72z9zR4KTvsTUyFvWV8FfWKF6VGjN4XJyUOL39ldpLkBZpcY" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh6.googleusercontent.com%2F9Es1t47UzsKOwnByrLbyJ0iztC6W3ZL3t51S3mIWof3gQWCtKaL1WeYkKemRIKzmhB868slQB7F_1E6t_7zjtqG8Vad9z9YsJ08uOew72z9zR4KTvsTUyFvWV8FfWKF6VGjN4XJyUOL39ldpLkBZpcY" alt="Playwright Trace showing a page.goto call taking 10s" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are two things to note in this Playwright trace: first, the test duration increased to ten seconds because Playwright’s &lt;code&gt;page.goto&lt;/code&gt; waits for the page &lt;code&gt;onload&lt;/code&gt; event which is heavily delayed by the slowed-down stylesheet.&lt;/p&gt;

&lt;p&gt;And second, and probably worse than a slow test case, the user experience suffered tremendously because the page only started rendering after ten seconds.&lt;/p&gt;

&lt;p&gt;After running a quick Playwright test, you now know that there are 3rd party resources that could really harm your overall performance and you can check if they’re necessary or if you should implement them in another way.&lt;/p&gt;

&lt;p&gt;But what if these requests fail or are blocked by browser extensions?&lt;/p&gt;

&lt;h3&gt;
  
  
  How to block requests with Playwright
&lt;/h3&gt;

&lt;p&gt;So far we’ve only been delaying third-party resources, but what happens when we block them entirely? Luckily, &lt;a href="https://playwright.dev/docs/api/class-page#page-route" rel="noopener noreferrer"&gt;blocking resources is quickly done with &lt;code&gt;page.route&lt;/code&gt;&lt;/a&gt;, too.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;works with blocked 3rd party resources&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;**&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requestURL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requestURL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/http:&lt;/span&gt;&lt;span class="se"&gt;\/\/&lt;/span&gt;&lt;span class="sr"&gt;localhost:8080/&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// monitor emitted page errors&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pageerror&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:8080&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you run the script the test case fails because the page emits &lt;code&gt;pageerrors&lt;/code&gt;. The inline JavaScript tried to call a &lt;code&gt;Bootstrap&lt;/code&gt; method that wasn’t available because of resource blocking similar to how an ad blocker would do it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Running 1 tests using 1 workers
[chromium] › example.spec.js:43:1 › works with blocked 3rd party resources
[
  Error [ReferenceError]: bootstrap is not defined at http://localhost:8080/:18:11
]
1) [chromium] › example.spec.js:43:1 › works with blocked 3rd party resources ====================

Error: expect(received).toBe(expected) // Object.is equality
    Expected: 0
    Received: 1

    60 |
    61 |   console.log(errors)
  &amp;gt; 62 |   expect(errors.length).toBe(0)
       |                         ^
    63 | })
    64 |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tracking page errors is a handy way to monitor your JavaScript in a synthetic lab setup. In the case of ad blockers and canceled requests, you could still perform your end-to-end tests and evaluate if a blocked script affects your site at all.&lt;/p&gt;

&lt;p&gt;Either way, &lt;code&gt;page.route&lt;/code&gt; is an easy-to-use way to emulate network conditions with Playwright.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what’s next?
&lt;/h2&gt;

&lt;p&gt;Here’s the takeaway: &lt;strong&gt;whenever you implement and rely on resources you don’t control, make sure that they’re not affecting or blowing up your application!&lt;/strong&gt; People use browser extensions, networks are flaky, services go down… many things that can go wrong.&lt;/p&gt;

&lt;p&gt;Know how your app handles slow responses or requests that might become victims of ad blockers.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Note: This post’s code snippets implement page request handling on a test-case basis for simplicity. &lt;a href="https://www.youtube.com/watch?v=2O7dyz6XO2s" rel="noopener noreferrer"&gt;I recommend looking into Playwright’s fixtures&lt;/a&gt; to not clutter your tests and focus on your end-to-end logic. Work with a nicely abstracted &lt;code&gt;pageWithSlowThirdParties&lt;/code&gt; object instead!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But how should you go about these two scenarios then? Should you run all your deployment end-to-end tests with changed third-party network settings?&lt;/p&gt;

&lt;h3&gt;
  
  
  Frontend testing vs. monitoring
&lt;/h3&gt;

&lt;p&gt;End-to-end testing with resource constraints is a great step to gain confidence and guarantee that your deployed code works no matter if your dependencies are slow or unavailable. But honestly, testing end-to-end is only the first step to real confidence because between today’s deployment and tomorrow plenty of other things can go wrong.&lt;/p&gt;

&lt;p&gt;While end-to-end testing guarantees that your site works at one moment in time and helps to evaluate regressions in your source code, if you’re relying on third-party resources, your third-party provider’s downtime can still affect your site. And that’s sometimes unavoidable but at least, you should be aware of your dependencies’ effects on your site and bet on end-to-end monitoring. Be the first one to know when your vendors are struggling!&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;First, if you’re relying on resources that you don’t own, check how their unavailability could affect your frontend. Opt for self-hosting your assets, and if that’s impossible, rely on asynchronous and fail-safe implementations.&lt;/p&gt;

&lt;p&gt;Second, test your site during deployments and stop, if new and risky dependencies were added to your frontend. A script element pointing to another CDN is quickly implemented. Be aware of your external resources and how they affect your application.&lt;/p&gt;

&lt;p&gt;And third, if you have to rely on external resources, start monitoring your frontend, run your end-to-end tests on a schedule, and ensure that your site—and also those external resources!—are up and running. Don’t be me, coming to work only to learn that your customers have been looking at a blank screen 30 seconds long for the last 8 hours.&lt;/p&gt;

&lt;p&gt;And maybe you want to give Checkly a try, &lt;a href="https://www.checklyhq.com/product/synthetic-monitoring/?utm_medium=organic-social&amp;amp;utm_source=dev.to&amp;amp;utm_campaign=devrel"&gt;we’ll run your Playwright tests for you&lt;/a&gt;. 😉&lt;/p&gt;

</description>
      <category>playwright</category>
      <category>javascript</category>
      <category>performance</category>
    </item>
    <item>
      <title>Playwright Tips From the Checkly Community</title>
      <dc:creator>Stefan Judis</dc:creator>
      <pubDate>Wed, 01 Feb 2023 12:38:26 +0000</pubDate>
      <link>https://dev.to/checkly/playwright-tips-from-the-checkly-community-d96</link>
      <guid>https://dev.to/checkly/playwright-tips-from-the-checkly-community-d96</guid>
      <description>&lt;p&gt;The Checkly community recently came together to talk Playwright &lt;a href="https://www.checklyhq.com/slack?utm_medium=referral&amp;amp;utm_source=dev.to&amp;amp;utm_campaign=devrel&amp;amp;utm_content=playwright-ama"&gt;in our public Slack&lt;/a&gt;! My fellow Playwright ambassador &lt;a href="https://www.linkedin.com/in/linkedjohnhill/" rel="noopener noreferrer"&gt;John Hill&lt;/a&gt; and I invited everyone to ask questions and share tips about Microsoft’s stellar end-to-end testing tool. &lt;/p&gt;

&lt;p&gt;And there were plenty of them! Let’s have a look at my favorite questions (and learnings) coming from the community! Are you ready for a mixed bag of Playwright tricks? Let’s go!&lt;/p&gt;

&lt;h2&gt;
  
  
  How should you handle parallelism in your end-to-end tests?
&lt;/h2&gt;

&lt;p&gt;In general, it’s best practice to parallelize as many tests as possible to avoid seeing your test duration going through the roof. No one likes to wait thirty minutes for a green deployment light and &lt;strong&gt;a test suite that takes forever (or flakiness) is the main reason your test efforts fail&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;But as usual in software development, things are complicated and every complex project comes with its own challenges when it comes to testing. &lt;/p&gt;

&lt;p&gt;A tricky case is test cases that involve resource updates. If all your tests run in parallel it’s easy to discover race conditions where one test creates or deletes a resource while another one is testing it, too! This problem is tricky to solve and there is no ideal solution but &lt;a href="https://twitter.com/tim_nolet/" rel="noopener noreferrer"&gt;our CTO Tim Nolet&lt;/a&gt; shared a possible approach:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd736pp7ejkld88cect4m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd736pp7ejkld88cect4m.png" alt="There is not one easy fix here. One thing we do with E2E tests here at Checkly is: 1) run non-destructive tests in full parallel. These can be unit tests but also E2E tests that don't rely on state. 2) run destructive / stateful tests separately and strictly serial. You can speed this up by using sharding and using a shard indicator to create / destroy any databases you might spin up." width="800" height="216"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For non-destructive tests that are not relying on a state in your database, Tim recommends going all in with parallel testing. Your test cases could range from checking that a modal pops up after a button click or previews that are rendered when someone interacts with a text field. All these tests can run independently and in parallel.&lt;/p&gt;

&lt;p&gt;With stateful tests, it’s a different story, though. If resource creation and updates are at play, it’s sometimes unavoidable to run tests sequentially to avoid one test messing with another one.&lt;/p&gt;

&lt;p&gt;But how do you separate and run these different tests in your Playwright project?&lt;/p&gt;

&lt;p&gt;To implement Tim’s recommendation in your project, you have to familiarize yourself with how Playwright handles parallelism.&lt;/p&gt;

&lt;p&gt;In general, &lt;strong&gt;Playwright runs different test files in parallel, and all tests in a file are run sequentially&lt;/strong&gt;. But watch out, the &lt;code&gt;fullyParallel&lt;/code&gt; global config changes this behavior and runs every test case in parallel regardless of where it’s defined.&lt;/p&gt;

&lt;p&gt;The default behavior lets you run tests that are in a single file sequential. For example, if you want to create, update and delete resources after another, having tests in a single file guarantees execution order. &lt;/p&gt;

&lt;p&gt;That’s a good start, but unfortunately, it doesn’t guarantee that other spec files aren’t interfering with a similar resource and this again could lead to false positives. Luckily Playwright is highly configurable and you can disable parallelism via &lt;a href="https://playwright.dev/docs/test-parallel#disable-parallelism" rel="noopener noreferrer"&gt;the CLI or Playwright config&lt;/a&gt; by defining the number of workers running your tests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx playwright &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The global &lt;code&gt;workers&lt;/code&gt; configuration enables you to run tests with different parallelization settings. Independent and stateless tests can be run fully parallel whereas sequential state-dependent tests can run in a sequence.&lt;/p&gt;

&lt;p&gt;To split tests, &lt;a href="https://playwright.dev/docs/test-annotations" rel="noopener noreferrer"&gt;leverage custom annotations&lt;/a&gt; or file path conventions to run Playwright in full parallelized or sequential mode. The example &lt;code&gt;package.json&lt;/code&gt; below leverages different directories to separate parallel from sequential test cases.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"parallel"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"index.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test:parallel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx playwright test tests/parallel/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test:sequence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx playwright test tests/sequence/ --workers=1"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"keywords"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"author"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"license"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ISC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"devDependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@playwright/test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^1.29.2"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this setup, you can run &lt;code&gt;npm run test:parallel&lt;/code&gt; and &lt;code&gt;npm run:sequence&lt;/code&gt; without running into test conflicts. By separating the stateless and stateful tests, you’ll be able to reduce test collisions and run independent tests as quickly as possible.&lt;/p&gt;

&lt;p&gt;But end-to-end tests that interact with the same resources are only one cause for flakiness. There are plenty of others!&lt;/p&gt;

&lt;h2&gt;
  
  
  What are common reasons for flaky tests and how can you avoid them?
&lt;/h2&gt;

&lt;p&gt;There are many reasons for flaky tests. &lt;a href="https://twitter.com/rag0g" rel="noopener noreferrer"&gt;Giovanni Rago&lt;/a&gt;, our Head of Customer Solutions, shared his top flakiness offenders.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnp95fv4spv1w4q1ki1k5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnp95fv4spv1w4q1ki1k5.png" alt="I'd say my top 3 in causes for flakiness is as follows: 1. scripts with sub optimal waiting (yes, there's plenty of cases out there for using explicit waiting, unfortunately) 2. non-deterministic behaviour or similar curve balls the target system throws your way 3. test data or test setup issues" width="800" height="233"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As Gio states, suboptimal waiting is a common problem in UI and end-to-end testing. &lt;/p&gt;

&lt;p&gt;To test your application it has to have reached a particular state, JavaScript probably needs to be loaded or potential API calls had to resolve. There are multiple ways to go about testing your UIs.&lt;/p&gt;

&lt;p&gt;But before going into explicit waiting scenarios, make sure to check Playwright’s &lt;a href="https://www.youtube.com/watch?v=j-QLpb6Tmg0&amp;amp;list=PLMZDRUOi3a8NtMq3PUS5iJc2pee38rurc&amp;amp;index=5" rel="noopener noreferrer"&gt;auto-waiting mechanisms&lt;/a&gt; and &lt;a href="https://www.youtube.com/watch?v=j-QLpb6Tmg0&amp;amp;list=PLMZDRUOi3a8NtMq3PUS5iJc2pee38rurc&amp;amp;index=5" rel="noopener noreferrer"&gt;web-first assertions&lt;/a&gt;. With all the built-in auto-waiting mechanisms you rarely need to worry about explicit waiting. For example, if you want to test a UI flow that includes a component that only becomes visible eventually, Playwright actions such as &lt;code&gt;click&lt;/code&gt; wait for elements to become visible.&lt;/p&gt;

&lt;p&gt;These features enable you to test highly asynchronous UIs with code that looks synchronous.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Playwright retries to click this button until it works or times out&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But as Gio says, sometimes these built-in waiting mechanisms aren’t enough and you have to wait for an explicit event to happen or a UI state to be reached. John Hill takes things to another level and recommends watching specific network requests to make functionality deterministic. &lt;/p&gt;

&lt;p&gt;If you click a button, that triggers an API call, you can wait for the API call to resolve and test if the UI reacted accordingly. &lt;/p&gt;

&lt;p&gt;As John states:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If your browser has a HTTP API Backend, the #1 way to add determinism to your tests is to wait on the specific network requests to resolve.&lt;br&gt;
99% of the flake I’ve experienced when testing a flaky frontend app comes from a flaky and unreliable backend. &lt;br&gt;
&lt;strong&gt;The frontend tests are just the bearer of bad news.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you want to check an example of this approach, &lt;a href="https://github.com/akhenry/openmct-yamcs/blob/80b9d96979a27fec4f60e1cc3f1c84b4162eab65/tests/e2e/yamcs/network.e2e.spec.js#L33" rel="noopener noreferrer"&gt;here’s the code John and the team at Open MCT run to test UIs request-dependent at Nasa&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How can you emulate custom keyboard interactions in Playwright?
&lt;/h2&gt;

&lt;p&gt;If you’re working on a SaaS product and aim for stellar UX, you might implement keyboard shortcuts and interactions. These could be challenging to test, but luckily, Playwright also supports a way to control a virtual keyboard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://playwright.dev/docs/api/class-keyboard" rel="noopener noreferrer"&gt;Playwright’s keyboard functionality enables you to interact with input elements and pages&lt;/a&gt; like a real user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keyboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;press&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Meta+KeyA&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keyboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;This is a line of text&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This functionality is great for shortcuts but also valuable for highly custom and complex components. One example is a JS-based code editor like Monaco.&lt;/p&gt;

&lt;p&gt;Millions of developers use Monaco because it’s built into VS Code (and app.checklyhq.com 😉). But how does it work under the hood and how would you test a custom editor if it’s embedded in your application?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://microsoft.github.io/monaco-editor/playground.html" rel="noopener noreferrer"&gt;Open the Monaco Playground&lt;/a&gt; to look at Microsoft’s open-source editor component. When you inspect the editor, you’ll see that to offer syntax highlighting, there are divs and spans all over the editor component. But how could you edit and test these and interact with the editor?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhtuoiniw65s5raa21h5a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhtuoiniw65s5raa21h5a.png" alt="Monaco editor with DevTools" width="800" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Monaco and other code editor components usually include a hidden or transparent textarea to catch keyboard events and make things more accessible. The textarea keystrokes are captured, canvas elements render code suggestions, and spans/divs display highlighted code. There’s a lot of magic involved and it’s pretty challenging to test.&lt;/p&gt;

&lt;p&gt;Luckily, Playwright’s virtual keyboard helps out here and as the Playwright core team member Max Schmitt shared, &lt;a href="https://github.com/microsoft/playwright/issues/14126" rel="noopener noreferrer"&gt;&lt;code&gt;page.keyboard&lt;/code&gt; is at your service to interact with Monaco&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://microsoft.github.io/monaco-editor/playground.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;monacoEditor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.monaco-editor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;nth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;monacoEditor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keyboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;press&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Meta+KeyA&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keyboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;This is a line of text&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;png&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This functionality allows testing and simulating keyboard interactions going beyond filling an input with a string value. Press specific key combinations, jump to the beginning of a line, or hit the “delete” key three times… It’s up to you to mimic your users’ keyboard behavior!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Related Reading: &lt;a href="https://blog.checklyhq.com/customizing-monaco/?utm_medium=referral&amp;amp;utm_source=dev.to&amp;amp;utm_campaign=devrel&amp;amp;utm_content=playwright-ama"&gt;How we added custom languages, code completion and highlighting to the Monaco editor&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Real users are an unpredictable bunch, though. How can you guarantee that your application isn’t breaking when you “attack” it with your end-to-end tests?&lt;/p&gt;

&lt;h2&gt;
  
  
  How can you catch JavaScript errors in long end-to-end testing flows?
&lt;/h2&gt;

&lt;p&gt;It’s always good to monitor if your application throws JavaScript exceptions when users interact with it. Luckily, reacting to JavaScript errors is straightforward in Playwright by leveraging emitted page events such as &lt;code&gt;pageerror&lt;/code&gt;. React and count exceptions with a few lines of code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Log all uncaught errors to the terminal&lt;/span&gt;
&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pageerror&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;exception&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Uncaught exception: "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Related video: if you want to learn how these work, &lt;a href="https://www.youtube.com/watch?v=fYDgtLpRhq0&amp;amp;list=PLMZDRUOi3a8NtMq3PUS5iJc2pee38rurc&amp;amp;index=14" rel="noopener noreferrer"&gt;have a look at one of our Playwright tips on YouTube&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;John Hill again puts things to the next level and shared how &lt;a href="https://github.com/nasa/openmct/blob/70074c52c85b728e7d2ef7c623943ba76e8a8089/e2e/baseFixtures.js#L130-L154" rel="noopener noreferrer"&gt;they implement JavaScript error tracking with Playwright fixtures&lt;/a&gt;. His approach allows you to collect all JS errors until the end of your test and only fail it then.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
   &lt;span class="na"&gt;failOnConsoleError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;option&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
   &lt;span class="cm"&gt;/**
    * Extends the base page class to enable console log error detection.
    * @see {@link https://github.com/microsoft/playwright/discussions/11690 Github Discussion}
    */&lt;/span&gt;
   &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;failOnConsoleError&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="c1"&gt;// Capture any console errors during test execution&lt;/span&gt;
       &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
       &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;console&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
       &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

       &lt;span class="c1"&gt;// Assert against console errors during teardown&lt;/span&gt;
       &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;failOnConsoleError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
           &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
               &lt;span class="nx"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;soft&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s2"&gt;`Console error detected: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;_consoleMessageToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;not&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
           &lt;span class="p"&gt;);&lt;/span&gt;
       &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The example leverages fixtures and soft assertions and if you’re curious, have a look at &lt;a href="https://github.com/nasa/openmct/blob/70074c52c85b728e7d2ef7c623943ba76e8a8089/e2e/baseFixtures.js#L130-L154" rel="noopener noreferrer"&gt;John’s provided example code&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;These were my favorite Playwright learnings from our Community AMA session and I continue to be amazed at how far end-to-end testing has come. If you want to keep learning about Playwright, &lt;a href="https://www.youtube.com/watch?list=PLMZDRUOi3a8NtMq3PUS5iJc2pee38rurc" rel="noopener noreferrer"&gt;check out our Checkly YouTube channel&lt;/a&gt; where I share all these nitty-gritty tricks as I discover them. And if you have any questions, &lt;a href="https://www.checklyhq.com/slack?utm_medium=referral&amp;amp;utm_source=dev.to&amp;amp;utm_campaign=devrel&amp;amp;utm_content=playwright-ama"&gt;don’t hesitate to drop into Slack, I’m happy to help&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;And lastly, you might know that testing is only the beginning of shipping stellar products. If you want to start monitoring your apps and be confident that your site works at all times, this is what we do at Checkly. &lt;a href="https://www.checklyhq.com/?utm_medium=referral&amp;amp;utm_source=dev.to&amp;amp;utm_campaign=devrel&amp;amp;utm_content=playwright-ama"&gt;Sign up and run Playwright tests at any time from around the world&lt;/a&gt;. It’s free and pretty cool, trust me! 😉&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>playwright</category>
      <category>testing</category>
    </item>
    <item>
      <title>TIL — Why Custom properties don't work with the url() CSS function</title>
      <dc:creator>Stefan Judis</dc:creator>
      <pubDate>Fri, 23 Sep 2022 18:48:20 +0000</pubDate>
      <link>https://dev.to/stefanjudis/til-custom-properties-dont-work-with-the-url-css-function-15ie</link>
      <guid>https://dev.to/stefanjudis/til-custom-properties-dont-work-with-the-url-css-function-15ie</guid>
      <description>&lt;p&gt;Custom properties and CSS parsing are always good for surprises like &lt;a href="https://www.stefanjudis.com/today-i-learned/the-surprising-behavior-of-important-css-custom-properties/" rel="noopener noreferrer"&gt;&lt;code&gt;!important&lt;/code&gt; behaving slightly differently&lt;/a&gt; or &lt;a href="https://www.stefanjudis.com/today-i-learned/custom-properties-affect-how-invalid-css-declarations-are-handled/" rel="noopener noreferrer"&gt;properties being "invalid at computed value time"&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Today I discovered yet another surprise — custom properties don't work in combination with the &lt;code&gt;url()&lt;/code&gt; function. 😲&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.something&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;/* this doesn't work */&lt;/span&gt;
  &lt;span class="py"&gt;--image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;://&lt;/span&gt;&lt;span class="n"&gt;jo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jpg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sx"&gt;url(var(--image)&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/csstree/csstree/issues/197#issuecomment-1183839492" rel="noopener noreferrer"&gt;Roman Dvornov describes the details quite well in a GitHub issue&lt;/a&gt;, but let me give you a condensed explanation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two modes of &lt;code&gt;url()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Your &lt;code&gt;url()&lt;/code&gt;-containing CSS will be parsed differently, depending on how you use &lt;code&gt;url()&lt;/code&gt;. There's an old and a newer way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Old: &lt;code&gt;url(https://jo.com/image.jpg)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Newer: &lt;code&gt;url('https://jo.com/image.jpg')&lt;/code&gt; or &lt;code&gt;url("https://jo.com/image.jpg")&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The problem of the legacy &lt;code&gt;url()&lt;/code&gt; token
&lt;/h3&gt;

&lt;p&gt;And these missing quotes of the old way might seem like a tiny detail, but they affect how your CSS is parsed.&lt;/p&gt;

&lt;p&gt;Without quotes, the &lt;code&gt;url()&lt;/code&gt; syntax looks like a CSS function, but it isn't. CSS parsers will treat it as a single token, as so-called &lt;code&gt;url-token&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.something&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sx"&gt;url(https://ja.com/image.jpg)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="err"&gt;//&lt;/span&gt;          &lt;span class="err"&gt;\---------------------------/&lt;/span&gt;
  &lt;span class="err"&gt;//&lt;/span&gt;            &lt;span class="err"&gt;without&lt;/span&gt; &lt;span class="err"&gt;quotes&lt;/span&gt; &lt;span class="err"&gt;this&lt;/span&gt; &lt;span class="err"&gt;☝️&lt;/span&gt; &lt;span class="err"&gt;is&lt;/span&gt;
  &lt;span class="err"&gt;//&lt;/span&gt;               &lt;span class="err"&gt;a&lt;/span&gt; &lt;span class="err"&gt;single&lt;/span&gt; &lt;span class="err"&gt;CSS&lt;/span&gt; &lt;span class="err"&gt;token&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And this entire token from &lt;code&gt;url(&lt;/code&gt; to the closing &lt;code&gt;)&lt;/code&gt; enforces parentheses, whitespace characters, single quotes (') and double quotes (") to be escaped with a backslash.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;url()&lt;/code&gt; with a quoted string, on the other hand, is a normal and flexible CSS function notation, which is parsed part by part and works as expected.&lt;/p&gt;

&lt;p&gt;But now, guess what happens when you want to use a custom property in combination with &lt;code&gt;url()&lt;/code&gt;?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.something&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;/* this doesn't work */&lt;/span&gt;
  &lt;span class="py"&gt;--image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;://&lt;/span&gt;&lt;span class="n"&gt;jo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jpg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sx"&gt;url(var(--image)&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="err"&gt;//&lt;/span&gt;                &lt;span class="err"&gt;☝️&lt;/span&gt; &lt;span class="err"&gt;"No&lt;/span&gt; &lt;span class="err"&gt;quotes?&lt;/span&gt; &lt;span class="err"&gt;Cool,&lt;/span&gt; &lt;span class="err"&gt;that's&lt;/span&gt; &lt;span class="err"&gt;a&lt;/span&gt; &lt;span class="err"&gt;url-token!"&lt;/span&gt;
&lt;span class="err"&gt;//&lt;/span&gt;                &lt;span class="err"&gt;😢&lt;/span&gt; &lt;span class="err"&gt;"Too&lt;/span&gt; &lt;span class="err"&gt;bad&lt;/span&gt; &lt;span class="err"&gt;though,&lt;/span&gt; &lt;span class="err"&gt;`(`&lt;/span&gt; &lt;span class="err"&gt;isn't&lt;/span&gt; &lt;span class="err"&gt;allowed&lt;/span&gt; &lt;span class="err"&gt;in&lt;/span&gt; &lt;span class="err"&gt;here..."&lt;/span&gt;
&lt;span class="err"&gt;//&lt;/span&gt;                &lt;span class="err"&gt;❌&lt;/span&gt; &lt;span class="err"&gt;"I'll&lt;/span&gt; &lt;span class="err"&gt;throw&lt;/span&gt; &lt;span class="err"&gt;everything&lt;/span&gt; &lt;span class="err"&gt;away!"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because there are no quotes, this declaration is parsed as a &lt;code&gt;url-token&lt;/code&gt;. And unfortunately, the &lt;code&gt;(&lt;/code&gt; in &lt;code&gt;var(--image)&lt;/code&gt; isn't escaped, so the parser throws an error and invalidates the entire CSS declaration. &lt;/p&gt;

&lt;p&gt;And this legacy &lt;code&gt;url-token&lt;/code&gt; parsing is why you can't use custom variables inside of &lt;code&gt;url()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The solution to work around the legacy &lt;code&gt;url()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;What's the usual solution, when there's a problem with old web legacy? It's inventing new stuff because we can't break the web and just change things.&lt;/p&gt;

&lt;p&gt;To enable custom properties in &lt;code&gt;url()&lt;/code&gt; there's a new alias in town. The &lt;code&gt;src()&lt;/code&gt; notation behaves the same way as &lt;code&gt;url()&lt;/code&gt; but without this weird legacy &lt;code&gt;url-token&lt;/code&gt; logic.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.something&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;/* this works! (theoretically) */&lt;/span&gt;
  &lt;span class="py"&gt;--image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;://&lt;/span&gt;&lt;span class="n"&gt;jo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jpg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--image&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Problem solved! But yet, no browser supports &lt;code&gt;src()&lt;/code&gt; (I couldn't find browser support information but tested current Chrome, Safari and Firefox), so it's time for us to wait. 🤷‍♂️&lt;/p&gt;

&lt;p&gt;CSS parsing and custom properties — always good for a surprise!&lt;/p&gt;




&lt;p&gt;If you want to read more about this topic, here are some resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/csstree/csstree/issues/197#issuecomment-1183839492" rel="noopener noreferrer"&gt;Roman Dvornov's stellar explanation of this topic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.w3.org/TR/css-values/#urls" rel="noopener noreferrer"&gt;The spec defining &lt;code&gt;src()&lt;/code&gt; and &lt;code&gt;url()&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/w3c/csswg-drafts/issues/541" rel="noopener noreferrer"&gt;The spec change adding the &lt;code&gt;src()&lt;/code&gt; alias&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>css</category>
    </item>
  </channel>
</rss>
