<?xml version="1.0" encoding="UTF-8"?><rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title>Articles</title><link>https://web.dev/articles</link><atom:link href="http://rss.144-124-237-35.sslip.io/web/articles" rel="self" type="application/rss+xml"></atom:link><description>Articles - Powered by AtomRSS</description><generator>AtomRSS</generator><webMaster>contact@atomgroup.dev (AtomRSS)</webMaster><language>en</language><image><url>https://web.dev/_pwa/web/icons/icon-144x144.png</url><title>Articles</title><link>https://web.dev/articles</link></image><lastBuildDate>Sun, 09 Aug 2026 14:10:17 GMT</lastBuildDate><ttl>5</ttl><item><title>Files and directories patterns</title><description>&lt;section class=&quot;devsite-landing-row devsite-landing-row-1-up devsite-landing-row-75 devsite-landing-row-hero devsite-landing-row-no-image-background&quot; background=&quot;grey&quot; header-position=&quot;top&quot;&gt;
    &lt;div class=&quot;devsite-landing-row-inner&quot;&gt;

    
      

      
      

      

        &lt;div class=&quot;devsite-landing-row-group&quot;&gt;
        
          &lt;div class=&quot;devsite-landing-row-item&quot; description-position=&quot;bottom&quot;&gt;

  
    
&lt;div class=&quot;devsite-landing-row-item-media
            &quot;&gt;
  
    &lt;figure class=&quot;devsite-landing-row-item-image&quot;&gt;
  
    
  
  &lt;picture&gt;
    
    &lt;img alt=&quot;&quot; src=&quot;https://web.dev/static/articles/files/hero.png&quot; srcset=&quot;https://web.dev/static/articles/files/hero_36.png 36w,https://web.dev/static/articles/files/hero_48.png 48w,https://web.dev/static/articles/files/hero_72.png 72w,https://web.dev/static/articles/files/hero_96.png 96w,https://web.dev/static/articles/files/hero_480.png 480w,https://web.dev/static/articles/files/hero_720.png 720w,https://web.dev/static/articles/files/hero_856.png 856w,https://web.dev/static/articles/files/hero_960.png 960w,https://web.dev/static/articles/files/hero_1440.png 1440w,https://web.dev/static/articles/files/hero_1920.png 1920w,https://web.dev/static/articles/files/hero_2880.png 2880w&quot; sizes=&quot;(max-width: 600px) 100vw, (max-width: 840px) 50vw, 342px&quot; fetchpriority=&quot;high&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;/picture&gt;
  
  
&lt;/figure&gt;
  
&lt;/div&gt;


    
    &lt;div class=&quot;devsite-landing-row-item-description&quot;&gt;

      

      &lt;div class=&quot;devsite-landing-row-item-body&quot;&gt;
        

        
    &lt;h3 id=&quot;files-and-directories-patterns&quot; data-text=&quot;Files and directories patterns&quot; class=&quot;hide-from-toc no-link&quot; tabindex=&quot;0&quot;&gt;
      
    
        Files and directories patterns
      
  
    &lt;/h3&gt;
  

        
          &lt;div class=&quot;devsite-landing-row-item-description-content&quot;&gt;
            A collection of common patterns to deal with files and directories.
          &lt;/div&gt;
        

        

        
      &lt;/div&gt;
    &lt;/div&gt;
    
  

&lt;/div&gt;
        
        &lt;/div&gt;
      

    
    &lt;/div&gt;
  &lt;/section&gt;

  &lt;section class=&quot;devsite-landing-row devsite-landing-row-1-up&quot; header-position=&quot;top&quot;&gt;
    &lt;div class=&quot;devsite-landing-row-inner&quot;&gt;

    
      

      
      

      

        &lt;div class=&quot;devsite-landing-row-group&quot;&gt;
        
          &lt;div class=&quot;devsite-landing-row-item devsite-landing-row-item-no-media&quot; description-position=&quot;bottom&quot;&gt;

  
    

    
    &lt;div class=&quot;devsite-landing-row-item-description&quot;&gt;

      

      &lt;div class=&quot;devsite-landing-row-item-body&quot;&gt;
        

        

        
          &lt;div class=&quot;devsite-landing-row-item-description-content&quot;&gt;
            &lt;devsite-dynamic-content initial-results=&quot;12&quot; items-per-page=&quot;12&quot; maxresults=&quot;500&quot; pagination-type=&quot;numeric&quot; query=&quot;project_url:/articles/files&quot; sortorder=&quot;recency&quot; template=&quot;list&quot;&gt;
&lt;/devsite-dynamic-content&gt;

          &lt;/div&gt;
        

        

        
      &lt;/div&gt;
    &lt;/div&gt;
    
  

&lt;/div&gt;
        
        &lt;/div&gt;
      

    
    &lt;/div&gt;
  &lt;/section&gt;

  

  
</description><link>https://web.dev/articles/files?hl=en</link><guid isPermaLink="false">https://web.dev/articles/files?hl=en</guid><pubDate>Wed, 29 Jul 2026 12:48:36 GMT</pubDate></item><item><title>Preload responsive images</title><description>&lt;p&gt;&lt;style&gt;
  .wd-author__links {
    display: flex;
  }
  &lt;/style&gt;

&lt;/p&gt;&lt;p&gt;&lt;/p&gt;

&lt;p&gt;
&lt;/p&gt;&lt;p class=&quot;wd-pubdates&quot; style=&quot;font-size: 90%; border-bottom: 1px solid var(--devsite-ref-palette--grey200);color: var(--devsite-ref-palette--blue-grey700);&quot;&gt;
  Published: September 30, 2019, Last updated: July 10, 2026
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;You can preload responsive images, which can let your images load
significantly faster by helping the browser identify the correct image
from a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;srcset&lt;/code&gt; before it renders the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;img&lt;/code&gt; tag.&lt;/p&gt;

&lt;h3 id=&quot;responsive&quot; data-text=&quot;Responsive images overview&quot; tabindex=&quot;-1&quot;&gt;Responsive images overview&lt;/h3&gt;

&lt;p&gt;
  
    
    
    
    
  
  
  
    
      
    
  
    
      
    
  
    
      
    
   

    
    
      
      &lt;/p&gt;&lt;div class=&quot;wd-browser-compat&quot; translate=&quot;no&quot;&gt;
        &lt;p&gt;Browser Support&lt;/p&gt;
        &lt;ul&gt;
          
  
  
    
      
        
          
          
        
      
    
    
  

  
  
  
  
    
  
  
  
    
  

  &lt;li data-support=&quot;yes&quot;&gt;
    &lt;img alt=&quot;Chrome: 73.&quot; src=&quot;data:image/svg+xml,%3Csvg xmlns=&#39;http://www.w3.org/2000/svg&#39; width=&#39;24&#39; height=&#39;24&#39; viewBox=&#39;-10 -10 276 276&#39;%3E%3ClinearGradient id=&#39;a&#39; x1=&#39;145&#39; x2=&#39;34&#39; y1=&#39;253&#39; y2=&#39;61&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%231e8e3e&#39;/%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%2334a853&#39;/%3E%3C/linearGradient%3E%3ClinearGradient id=&#39;b&#39; x1=&#39;111&#39; x2=&#39;222&#39; y1=&#39;254&#39; y2=&#39;62&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%23fcc934&#39;/%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%23fbbc04&#39;/%3E%3C/linearGradient%3E%3ClinearGradient id=&#39;c&#39; x1=&#39;17&#39; x2=&#39;239&#39; y1=&#39;80&#39; y2=&#39;80&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%23d93025&#39;/%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%23ea4335&#39;/%3E%3C/linearGradient%3E%3Ccircle cx=&#39;128&#39; cy=&#39;128&#39; r=&#39;64&#39; fill=&#39;%23fff&#39;/%3E%3Cpath fill=&#39;url(%23a)&#39; d=&#39;M96 183a64 64 0 0 1-23-23L17 64a128 128 0 0 0 111 192l55-96a64 64 0 0 1-87 23Z&#39;/%3E%3Cpath fill=&#39;url(%23b)&#39; d=&#39;M192 128a64 64 0 0 1-9 32l-55 96A128 128 0 0 0 239 64H128a64 64 0 0 1 64 64Z&#39;/%3E%3Ccircle cx=&#39;128&#39; cy=&#39;128&#39; r=&#39;52&#39; fill=&#39;%231a73e8&#39;/%3E%3Cpath fill=&#39;url(%23c)&#39; d=&#39;M96 73a64 64 0 0 1 32-9h111a128 128 0 0 0-222 0l56 96a64 64 0 0 1 23-87Z&#39;/%3E%3C/svg%3E&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
    &lt;span aria-hidden=&quot;true&quot;&gt;
      
        73
      
    &lt;/span&gt;
  &lt;/li&gt;
  
  
          
  
  
    
      
        
          
          
        
      
    
    
  

  
  
  
  
    
  
  
  
    
  

  &lt;li data-support=&quot;yes&quot;&gt;
    &lt;img alt=&quot;Edge: 79.&quot; src=&quot;data:image/svg+xml,%3Csvg xmlns=&#39;http://www.w3.org/2000/svg&#39; xmlns:xlink=&#39;http://www.w3.org/1999/xlink&#39; width=&#39;24&#39; height=&#39;24&#39; viewBox=&#39;0 0 27600 27600&#39;%3E%3ClinearGradient id=&#39;A&#39; gradientUnits=&#39;userSpaceOnUse&#39;/%3E%3ClinearGradient id=&#39;B&#39; x1=&#39;6870&#39; x2=&#39;24704&#39; y1=&#39;18705&#39; y2=&#39;18705&#39; xlink:href=&#39;%23A&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%230c59a4&#39;/%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%23114a8b&#39;/%3E%3C/linearGradient%3E%3ClinearGradient id=&#39;C&#39; x1=&#39;16272&#39; x2=&#39;5133&#39; y1=&#39;10968&#39; y2=&#39;23102&#39; xlink:href=&#39;%23A&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%231b9de2&#39;/%3E%3Cstop offset=&#39;.16&#39; stop-color=&#39;%231595df&#39;/%3E%3Cstop offset=&#39;.67&#39; stop-color=&#39;%230680d7&#39;/%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%230078d4&#39;/%3E%3C/linearGradient%3E%3CradialGradient id=&#39;D&#39; cx=&#39;16720&#39; cy=&#39;18747&#39; r=&#39;9538&#39; xlink:href=&#39;%23A&#39;%3E%3Cstop offset=&#39;.72&#39; stop-opacity=&#39;0&#39;/%3E%3Cstop offset=&#39;.95&#39; stop-opacity=&#39;.53&#39;/%3E%3Cstop offset=&#39;1&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;E&#39; cx=&#39;7130&#39; cy=&#39;19866&#39; r=&#39;14324&#39; gradientTransform=&#39;matrix(.14843 -.98892 .79688 .1196 -8759 25542)&#39; xlink:href=&#39;%23A&#39;%3E%3Cstop offset=&#39;.76&#39; stop-opacity=&#39;0&#39;/%3E%3Cstop offset=&#39;.95&#39; stop-opacity=&#39;.5&#39;/%3E%3Cstop offset=&#39;1&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;F&#39; cx=&#39;2523&#39; cy=&#39;4680&#39; r=&#39;20243&#39; gradientTransform=&#39;matrix(-.03715 .99931 -2.12836 -.07913 13579 3530)&#39; xlink:href=&#39;%23A&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%2335c1f1&#39;/%3E%3Cstop offset=&#39;.11&#39; stop-color=&#39;%2334c1ed&#39;/%3E%3Cstop offset=&#39;.23&#39; stop-color=&#39;%232fc2df&#39;/%3E%3Cstop offset=&#39;.31&#39; stop-color=&#39;%232bc3d2&#39;/%3E%3Cstop offset=&#39;.67&#39; stop-color=&#39;%2336c752&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;G&#39; cx=&#39;24247&#39; cy=&#39;7758&#39; r=&#39;9734&#39; gradientTransform=&#39;matrix(.28109 .95968 -.78353 .22949 24510 -16292)&#39; xlink:href=&#39;%23A&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%2366eb6e&#39;/%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%2366eb6e&#39; stop-opacity=&#39;0&#39;/%3E%3C/radialGradient%3E%3Cpath id=&#39;H&#39; d=&#39;M24105 20053a9345 9345 0 01-1053 472 10202 10202 0 01-3590 646c-4732 0-8855-3255-8855-7432 0-1175 680-2193 1643-2729-4280 180-5380 4640-5380 7253 0 7387 6810 8137 8276 8137 791 0 1984-230 2704-456l130-44a12834 12834 0 006660-5282c220-350-168-757-535-565z&#39;/%3E%3Cpath id=&#39;I&#39; d=&#39;M11571 25141a7913 7913 0 01-2273-2137 8145 8145 0 01-1514-4740 8093 8093 0 013093-6395 8082 8082 0 011373-859c312-148 846-414 1554-404a3236 3236 0 012569 1297 3184 3184 0 01636 1866c0-21 2446-7960-8005-7960-4390 0-8004 4166-8004 7820 0 2319 538 4170 1212 5604a12833 12833 0 007684 6757 12795 12795 0 003908 610c1414 0 2774-233 4045-656a7575 7575 0 01-6278-803z&#39;/%3E%3Cpath id=&#39;J&#39; d=&#39;M16231 15886c-80 105-330 250-330 566 0 260 170 512 472 723 1438 1003 4149 868 4156 868a5954 5954 0 003027-839 6147 6147 0 001133-850 6180 6180 0 001910-4437c26-2242-796-3732-1133-4392-2120-4141-6694-6525-11668-6525-7011 0-12703 5635-12798 12620 47-3654 3679-6605 7996-6605 350 0 2346 34 4200 1007 1634 858 2490 1894 3086 2921 618 1067 728 2415 728 2952s-271 1333-780 1990z&#39;/%3E%3Cuse fill=&#39;url(%23B)&#39; xlink:href=&#39;%23H&#39;/%3E%3Cuse fill=&#39;url(%23D)&#39; opacity=&#39;.35&#39; xlink:href=&#39;%23H&#39;/%3E%3Cuse fill=&#39;url(%23C)&#39; xlink:href=&#39;%23I&#39;/%3E%3Cuse fill=&#39;url(%23E)&#39; opacity=&#39;.4&#39; xlink:href=&#39;%23I&#39;/%3E%3Cuse fill=&#39;url(%23F)&#39; xlink:href=&#39;%23J&#39;/%3E%3Cuse fill=&#39;url(%23G)&#39; xlink:href=&#39;%23J&#39;/%3E%3C/svg%3E&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
    &lt;span aria-hidden=&quot;true&quot;&gt;
      
        79
      
    &lt;/span&gt;
  &lt;/li&gt;
  
  
          
  
  
    
      
        
          
          
        
      
    
    
  

  
  
  
  
    
  
  
  
    
  

  &lt;li data-support=&quot;yes&quot;&gt;
    &lt;img alt=&quot;Firefox: 78.&quot; src=&quot;data:image/svg+xml,%3Csvg xmlns=&#39;http://www.w3.org/2000/svg&#39; width=&#39;24&#39; height=&#39;24&#39; viewBox=&#39;0 0 512 512&#39;%3E%3Cdefs%3E%3CradialGradient id=&#39;ff-b&#39; cx=&#39;428.5&#39; cy=&#39;55.1&#39; r=&#39;501&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23ffbd4f&#39;/%3E%3Cstop offset=&#39;.2&#39; stop-color=&#39;%23ffac31&#39;/%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23ff9d17&#39;/%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23ff980e&#39;/%3E%3Cstop offset=&#39;.4&#39; stop-color=&#39;%23ff563b&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff3750&#39;/%3E%3Cstop offset=&#39;.7&#39; stop-color=&#39;%23f5156c&#39;/%3E%3Cstop offset=&#39;.8&#39; stop-color=&#39;%23eb0878&#39;/%3E%3Cstop offset=&#39;.9&#39; stop-color=&#39;%23e50080&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-c&#39; cx=&#39;245.4&#39; cy=&#39;259.9&#39; r=&#39;501&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23960e18&#39;/%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23b11927&#39; stop-opacity=&#39;.7&#39;/%3E%3Cstop offset=&#39;.4&#39; stop-color=&#39;%23db293d&#39; stop-opacity=&#39;.3&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23f5334b&#39; stop-opacity=&#39;.1&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff3750&#39; stop-opacity=&#39;0&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-d&#39; cx=&#39;305.8&#39; cy=&#39;-58.6&#39; r=&#39;363&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23fff44f&#39;/%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23ffdc3e&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff9d12&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff980e&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-e&#39; cx=&#39;190&#39; cy=&#39;390.8&#39; r=&#39;238.6&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%233a8ee6&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%235c79f0&#39;/%3E%3Cstop offset=&#39;.7&#39; stop-color=&#39;%239059ff&#39;/%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%23c139e6&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-f&#39; cx=&#39;252.2&#39; cy=&#39;201.3&#39; r=&#39;126.5&#39; gradientTransform=&#39;matrix(1 0 0 1 -48 31)&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.2&#39; stop-color=&#39;%239059ff&#39; stop-opacity=&#39;0&#39;/%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%238c4ff3&#39; stop-opacity=&#39;.1&#39;/%3E%3Cstop offset=&#39;.8&#39; stop-color=&#39;%237716a8&#39; stop-opacity=&#39;.5&#39;/%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%236e008b&#39; stop-opacity=&#39;.6&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-g&#39; cx=&#39;239.1&#39; cy=&#39;34.6&#39; r=&#39;171.6&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%23ffe226&#39;/%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23ffdb27&#39;/%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23ffc82a&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ffa930&#39;/%3E%3Cstop offset=&#39;.7&#39; stop-color=&#39;%23ff7e37&#39;/%3E%3Cstop offset=&#39;.8&#39; stop-color=&#39;%23ff7139&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-h&#39; cx=&#39;374&#39; cy=&#39;-74.3&#39; r=&#39;732.2&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23fff44f&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff980e&#39;/%3E%3Cstop offset=&#39;.6&#39; stop-color=&#39;%23ff5634&#39;/%3E%3Cstop offset=&#39;.7&#39; stop-color=&#39;%23ff3647&#39;/%3E%3Cstop offset=&#39;.9&#39; stop-color=&#39;%23e31587&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-i&#39; cx=&#39;304.6&#39; cy=&#39;7.1&#39; r=&#39;536.4&#39; gradientTransform=&#39;rotate(84 303 4)&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%23fff44f&#39;/%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23ffe847&#39;/%3E%3Cstop offset=&#39;.2&#39; stop-color=&#39;%23ffc830&#39;/%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23ff980e&#39;/%3E%3Cstop offset=&#39;.4&#39; stop-color=&#39;%23ff8b16&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff672a&#39;/%3E%3Cstop offset=&#39;.6&#39; stop-color=&#39;%23ff3647&#39;/%3E%3Cstop offset=&#39;.7&#39; stop-color=&#39;%23e31587&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-j&#39; cx=&#39;235&#39; cy=&#39;98.1&#39; r=&#39;457.1&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23fff44f&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff980e&#39;/%3E%3Cstop offset=&#39;.6&#39; stop-color=&#39;%23ff5634&#39;/%3E%3Cstop offset=&#39;.7&#39; stop-color=&#39;%23ff3647&#39;/%3E%3Cstop offset=&#39;.9&#39; stop-color=&#39;%23e31587&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-k&#39; cx=&#39;355.7&#39; cy=&#39;124.9&#39; r=&#39;500.3&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23fff44f&#39;/%3E%3Cstop offset=&#39;.2&#39; stop-color=&#39;%23ffe141&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ffaf1e&#39;/%3E%3Cstop offset=&#39;.6&#39; stop-color=&#39;%23ff980e&#39;/%3E%3C/radialGradient%3E%3ClinearGradient id=&#39;ff-a&#39; x1=&#39;446.9&#39; y1=&#39;76.8&#39; x2=&#39;47.9&#39; y2=&#39;461.8&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23fff44f&#39;/%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23ffe847&#39;/%3E%3Cstop offset=&#39;.2&#39; stop-color=&#39;%23ffc830&#39;/%3E%3Cstop offset=&#39;.4&#39; stop-color=&#39;%23ff980e&#39;/%3E%3Cstop offset=&#39;.4&#39; stop-color=&#39;%23ff8b16&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff672a&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff3647&#39;/%3E%3Cstop offset=&#39;.7&#39; stop-color=&#39;%23e31587&#39;/%3E%3C/linearGradient%3E%3ClinearGradient id=&#39;ff-l&#39; x1=&#39;442.1&#39; y1=&#39;74.8&#39; x2=&#39;102.6&#39; y2=&#39;414.3&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.2&#39; stop-color=&#39;%23fff44f&#39; stop-opacity=&#39;.8&#39;/%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23fff44f&#39; stop-opacity=&#39;.6&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23fff44f&#39; stop-opacity=&#39;.2&#39;/%3E%3Cstop offset=&#39;.6&#39; stop-color=&#39;%23fff44f&#39; stop-opacity=&#39;0&#39;/%3E%3C/linearGradient%3E%3C/defs%3E%3Cpath d=&#39;M479 166c-11-25-32-52-49-60a249 249 0 0 1 25 73c-27-68-73-95-111-155a255 255 0 0 1-8-14 44 44 0 0 1-4-9 1 1 0 0 0 0-1 1 1 0 0 0-1 0c-60 35-81 101-83 134a120 120 0 0 0-66 25 71 71 0 0 0-6-5 111 111 0 0 1-1-58c-25 11-44 29-58 44-9-12-9-52-8-60l-8 4a175 175 0 0 0-24 21 210 210 0 0 0-22 26 203 203 0 0 0-32 73l-1 2-2 15a229 229 0 0 0-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121zM202 355l3 1-3-1zm55-145zm198-31z&#39; fill=&#39;url(%23ff-a)&#39;/%3E%3Cpath d=&#39;M479 166c-11-25-32-52-49-60 14 26 22 53 25 72v1a207 207 0 0 1-206 279c-113-3-212-87-231-197-3-17 0-26 2-40-2 11-3 14-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121z&#39; fill=&#39;url(%23ff-b)&#39;/%3E%3Cpath d=&#39;M479 166c-11-25-32-52-49-60 14 26 22 53 25 72v1a207 207 0 0 1-206 279c-113-3-212-87-231-197-3-17 0-26 2-40-2 11-3 14-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121z&#39; fill=&#39;url(%23ff-c)&#39;/%3E%3Cpath d=&#39;m362 195 1 1a130 130 0 0 0-22-29C266 92 322 5 331 0c-60 35-81 101-83 134l9-1c45 0 84 25 105 62z&#39; fill=&#39;url(%23ff-d)&#39;/%3E%3Cpath d=&#39;M257 210c-1 6-22 26-29 26-68 0-80 41-80 41 3 35 28 64 57 79l4 2 7 3a107 107 0 0 0 31 6c120 6 143-143 57-186 22-4 45 5 58 14-21-37-60-62-105-62l-9 1a120 120 0 0 0-66 25l17 16c16 16 58 33 58 35z&#39; fill=&#39;url(%23ff-e)&#39;/%3E%3Cpath d=&#39;M257 210c-1 6-22 26-29 26-68 0-80 41-80 41 3 35 28 64 57 79l4 2 7 3a107 107 0 0 0 31 6c120 6 143-143 57-186 22-4 45 5 58 14-21-37-60-62-105-62l-9 1a120 120 0 0 0-66 25l17 16c16 16 58 33 58 35z&#39; fill=&#39;url(%23ff-f)&#39;/%3E%3Cpath d=&#39;m171 151 5 3a111 111 0 0 1-1-58c-25 11-44 29-58 44 1 0 36 0 54 11z&#39; fill=&#39;url(%23ff-g)&#39;/%3E%3Cpath d=&#39;M18 261a242 242 0 0 0 231 197 207 207 0 0 0 206-279c8 56-20 110-64 146-86 71-169 43-186 31l-3-1c-50-24-71-70-67-110-42 0-57-35-57-35s38-28 89-4c46 22 90 4 90 4 0-2-42-19-58-35l-17-16a71 71 0 0 0-6-5l-5-3c-18-11-52-11-54-11-9-12-9-51-8-60l-8 4a175 175 0 0 0-24 21 210 210 0 0 0-22 26 203 203 0 0 0-32 73c0 1-9 38-5 57z&#39; fill=&#39;url(%23ff-h)&#39;/%3E%3Cpath d=&#39;M341 167a130 130 0 0 1 22 29 46 46 0 0 1 4 3c55 50 26 121 24 126 44-36 72-90 64-146-27-68-73-95-111-155a255 255 0 0 1-8-14 44 44 0 0 1-4-9 1 1 0 0 0 0-1 1 1 0 0 0-1 0c-9 5-65 92 10 167z&#39; fill=&#39;url(%23ff-i)&#39;/%3E%3Cpath d=&#39;M367 199a46 46 0 0 0-4-3l-1-1c-13-9-36-18-58-15 86 44 63 193-57 187a107 107 0 0 1-31-6 131 131 0 0 1-11-5c17 12 99 39 186-31 2-5 31-76-24-126z&#39; fill=&#39;url(%23ff-j)&#39;/%3E%3Cpath d=&#39;M148 277s12-41 80-41c7 0 28-20 29-26s-44 18-90-4c-51-24-89 4-89 4s15 35 57 35c-4 40 16 85 67 110l3 1c-29-15-54-44-57-79z&#39; fill=&#39;url(%23ff-k)&#39;/%3E%3Cpath d=&#39;M479 166c-11-25-32-52-49-60a249 249 0 0 1 25 73c-27-68-73-95-111-155a255 255 0 0 1-8-14 44 44 0 0 1-4-9 1 1 0 0 0 0-1 1 1 0 0 0-1 0c-60 35-81 101-83 134l9-1c45 0 84 25 105 62-13-9-36-18-58-14 86 43 63 192-57 186a107 107 0 0 1-31-6 131 131 0 0 1-11-5l-3-1 3 1c-29-15-54-44-57-79 0 0 12-41 80-41 7 0 28-20 29-26 0-2-42-19-58-35l-17-16a71 71 0 0 0-6-5 111 111 0 0 1-1-58c-25 11-44 29-58 44-9-12-9-52-8-60l-8 4a175 175 0 0 0-24 21 210 210 0 0 0-22 26 203 203 0 0 0-32 73l-1 2-2 15a279 279 0 0 0-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121zm-24 13z&#39; fill=&#39;url(%23ff-l)&#39;/%3E%3C/svg%3E&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
    &lt;span aria-hidden=&quot;true&quot;&gt;
      
        78
      
    &lt;/span&gt;
  &lt;/li&gt;
  
  
          
  
  
    
      
        
          
          
        
      
    
    
  

  
  
  
  
    
  
  
  
    
  

  &lt;li data-support=&quot;yes&quot;&gt;
    &lt;img alt=&quot;Safari: 17.2.&quot; src=&quot;data:image/svg+xml,%3Csvg xmlns=&#39;http://www.w3.org/2000/svg&#39; xmlns:xlink=&#39;http://www.w3.org/1999/xlink&#39; width=&#39;24&#39; height=&#39;24&#39; viewBox=&#39;195 190 135 135&#39;%3E%3Cdefs%3E%3ClinearGradient id=&#39;s-a&#39; x1=&#39;132.6&#39; x2=&#39;134.4&#39; y1=&#39;111.7&#39; y2=&#39;-105.3&#39; xlink:href=&#39;%23s-b&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%23d2d2d2&#39; /%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23f2f2f2&#39; /%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%23fff&#39; /%3E%3C/linearGradient%3E%3ClinearGradient id=&#39;s-b&#39; gradientUnits=&#39;userSpaceOnUse&#39; /%3E%3ClinearGradient id=&#39;s-c&#39; x1=&#39;65.4&#39; x2=&#39;67.4&#39; y1=&#39;115.7&#39; y2=&#39;17.1&#39; xlink:href=&#39;%23s-b&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%23005ad5&#39; /%3E%3Cstop offset=&#39;.2&#39; stop-color=&#39;%230875f0&#39; /%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23218cee&#39; /%3E%3Cstop offset=&#39;.6&#39; stop-color=&#39;%2327a5f3&#39; /%3E%3Cstop offset=&#39;.8&#39; stop-color=&#39;%2325aaf2&#39; /%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%2321aaef&#39; /%3E%3C/linearGradient%3E%3ClinearGradient id=&#39;s-d&#39; x1=&#39;158.7&#39; x2=&#39;176.3&#39; y1=&#39;96.7&#39; y2=&#39;79.5&#39; xlink:href=&#39;%23s-b&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%23c72e24&#39; /%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%23fd3b2f&#39; /%3E%3C/linearGradient%3E%3CradialGradient id=&#39;s-i&#39; cx=&#39;-69.9&#39; cy=&#39;69.3&#39; r=&#39;54&#39; gradientTransform=&#39;matrix(.9 -.01 .04 2.72 -9 -120)&#39; xlink:href=&#39;%23s-b&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%2324a5f3&#39; stop-opacity=&#39;0&#39; /%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%231e8ceb&#39; /%3E%3C/radialGradient%3E%3CradialGradient id=&#39;s-j&#39; cx=&#39;109.3&#39; cy=&#39;13.8&#39; r=&#39;93.1&#39; gradientTransform=&#39;matrix(-.02 1.1 -1.04 -.02 137 -115)&#39; xlink:href=&#39;%23s-b&#39;%3E%3Cstop offset=&#39;0&#39; stop-opacity=&#39;0&#39; /%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%235488d6&#39; stop-opacity=&#39;0&#39; /%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%235d96eb&#39; /%3E%3C/radialGradient%3E%3C/defs%3E%3Crect width=&#39;220&#39; height=&#39;220&#39; x=&#39;22&#39; y=&#39;-107&#39; fill=&#39;url(%23s-a)&#39; ry=&#39;49&#39; transform=&#39;matrix(.57 0 0 .57 187 256)&#39; /%3E%3Cg transform=&#39;translate(194 190)&#39;%3E%3Ccircle cx=&#39;67.8&#39; cy=&#39;67.7&#39; fill=&#39;url(%23s-c)&#39; paint-order=&#39;stroke fill markers&#39; r=&#39;54&#39; /%3E%3Ccircle cx=&#39;-69.9&#39; cy=&#39;69.3&#39; fill=&#39;url(%23s-i)&#39; transform=&#39;translate(138 -2)&#39; r=&#39;54&#39; /%3E%3C/g%3E%3Cellipse cx=&#39;120&#39; cy=&#39;14.2&#39; fill=&#39;url(%23s-j)&#39; rx=&#39;93.1&#39; ry=&#39;93.7&#39; transform=&#39;matrix(.58 0 0 .58 192 250)&#39; /%3E%3Cg transform=&#39;matrix(.58 0 0 .57 197 182)&#39;%3E%3Cpath fill=&#39;%23cac7c8&#39; d=&#39;M46 192h1l72-48-7-9-66 57Z&#39; /%3E%3Cpath fill=&#39;%23fbfffc&#39; d=&#39;M46 191v1l66-57-7-9-59 65Z&#39; /%3E%3Cpath fill=&#39;url(%23s-d)&#39; d=&#39;m119 144-7-9 66-57-59 66Z&#39; /%3E%3Cpath fill=&#39;%23fb645c&#39; d=&#39;m105 126 7 9 66-57-1-1-72 49Z&#39; /%3E%3C/g%3E%3Cpath stroke=&#39;%23fff&#39; stroke-linecap=&#39;round&#39; stroke-miterlimit=&#39;1&#39; stroke-width=&#39;1.3&#39; d=&#39;m287 278 3-2m-12-17 8-2m-8-3h4m-4-13 8 2m-8 3h4m-1-13 7 3m-4-11 7 4m-2-11 6 6m0-12 6 7m1-11 4 6m4-10 3 7m5-9 2 7m15-7-1 7m10-5-3 7m11-4-4 7m11-2-5 6m16 7-7 4m10 4-7 3m10 6-8 1m8 16-8-2m5 10-7-3m4 11-7-4m2 11-6-5m0 11-5-6m-2 11-4-7m-4 11-3-8m-6 10-1-8m-16 8 2-8m-10 5 3-7m-11 4 4-7m-11 2 5-6m-8 3 3-3m4 8 2-3m5 8 2-4m6 7 1-4m8 5v-4m8 4v-4m9 3-1-4m9 1-2-4m9 0-2-4m9-2-3-3m8-4-3-2m8-5-4-2m7-6-4-1m5-8h-4m4-8h-4m3-9-4 1m1-9-4 2m-1-9-3 2m-2-9-3 3m-4-8-2 3m-5-8-2 4m-6-6-1 3m-8-5v4m-8-4v4m-9-2 1 3m-9 0 2 3m-9 1 2 3m-9 2 3 3m-8 4 3 2m-8 5 4 2m-7 6 4 1m-4 25 4-1m-2 5 7-3m-6 7 4-2m-2 6 7-4m-13-21h8m41-41v-8m0 99v-8m49-42h-8&#39; transform=&#39;translate(-65 8)&#39; /%3E%3C/svg%3E&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
    &lt;span aria-hidden=&quot;true&quot;&gt;
      
        17.2
      
    &lt;/span&gt;
  &lt;/li&gt;
  
  
        &lt;/ul&gt;
        
        &lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/HTMLLinkElement/imageSrcset&quot;&gt;Source&lt;/a&gt;&lt;/p&gt;
        
      &lt;/div&gt;
    
    
    
  &lt;p&gt;&lt;/p&gt;

&lt;p&gt;Suppose you&#39;re browsing the web on a screen that&#39;s 300 pixels wide, and the page
requests an image 1500 pixels wide. That page has wasted a lot of your mobile
data because your screen can&#39;t do anything with all that extra resolution.
Ideally, the browser would fetch a version of the image that&#39;s just a &lt;em&gt;little&lt;/em&gt;
wider than your screen size, for example, 325 pixels. This ensures a
high-resolution image without wasting data, and lets the image load faster.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://web.dev/articles/serve-responsive-images#serve_multiple_image_versions&quot;&gt;Responsive images&lt;/a&gt;
let browsers fetch different image resources for different devices. If you don&#39;t
use an &lt;a href=&quot;https://web.dev/articles/image-cdns&quot;&gt;image CDN&lt;/a&gt;, save multiple dimensions for each
image and specify them in the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;srcset&lt;/code&gt; attribute. The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;w&lt;/code&gt; value tells the
browser the width of each version, so it can choose the appropriate version for
any device:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;HTML&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;img src=&quot;small.jpg&quot; srcset=&quot;small.jpg 500w, medium.jpg 1000w, large.jpg 1500w&quot; alt=&quot;…&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;h3 id=&quot;preload&quot; data-text=&quot;Preload overview&quot; tabindex=&quot;-1&quot;&gt;Preload overview&lt;/h3&gt;

&lt;p&gt;
  
    
    
    
    
  
  
  
    
      
    
  
    
      
    
  
    
      
    
  
    
      
    
  
    
      
    
   

    
    
      
      &lt;/p&gt;&lt;div class=&quot;wd-browser-compat&quot; translate=&quot;no&quot;&gt;
        &lt;p&gt;Browser Support&lt;/p&gt;
        &lt;ul&gt;
          
  
  
    
      
        
          
          
        
      
    
    
  

  
  
  
  
    
  
  
  
    
  

  &lt;li data-support=&quot;yes&quot;&gt;
    &lt;img alt=&quot;Chrome: 50.&quot; src=&quot;data:image/svg+xml,%3Csvg xmlns=&#39;http://www.w3.org/2000/svg&#39; width=&#39;24&#39; height=&#39;24&#39; viewBox=&#39;-10 -10 276 276&#39;%3E%3ClinearGradient id=&#39;a&#39; x1=&#39;145&#39; x2=&#39;34&#39; y1=&#39;253&#39; y2=&#39;61&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%231e8e3e&#39;/%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%2334a853&#39;/%3E%3C/linearGradient%3E%3ClinearGradient id=&#39;b&#39; x1=&#39;111&#39; x2=&#39;222&#39; y1=&#39;254&#39; y2=&#39;62&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%23fcc934&#39;/%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%23fbbc04&#39;/%3E%3C/linearGradient%3E%3ClinearGradient id=&#39;c&#39; x1=&#39;17&#39; x2=&#39;239&#39; y1=&#39;80&#39; y2=&#39;80&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%23d93025&#39;/%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%23ea4335&#39;/%3E%3C/linearGradient%3E%3Ccircle cx=&#39;128&#39; cy=&#39;128&#39; r=&#39;64&#39; fill=&#39;%23fff&#39;/%3E%3Cpath fill=&#39;url(%23a)&#39; d=&#39;M96 183a64 64 0 0 1-23-23L17 64a128 128 0 0 0 111 192l55-96a64 64 0 0 1-87 23Z&#39;/%3E%3Cpath fill=&#39;url(%23b)&#39; d=&#39;M192 128a64 64 0 0 1-9 32l-55 96A128 128 0 0 0 239 64H128a64 64 0 0 1 64 64Z&#39;/%3E%3Ccircle cx=&#39;128&#39; cy=&#39;128&#39; r=&#39;52&#39; fill=&#39;%231a73e8&#39;/%3E%3Cpath fill=&#39;url(%23c)&#39; d=&#39;M96 73a64 64 0 0 1 32-9h111a128 128 0 0 0-222 0l56 96a64 64 0 0 1 23-87Z&#39;/%3E%3C/svg%3E&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
    &lt;span aria-hidden=&quot;true&quot;&gt;
      
        50
      
    &lt;/span&gt;
  &lt;/li&gt;
  
  
          
  
  
    
      
        
          
          
        
      
    
    
  

  
  
  
  
    
  
  
  
    
  

  &lt;li data-support=&quot;yes&quot;&gt;
    &lt;img alt=&quot;Edge: 79.&quot; src=&quot;data:image/svg+xml,%3Csvg xmlns=&#39;http://www.w3.org/2000/svg&#39; xmlns:xlink=&#39;http://www.w3.org/1999/xlink&#39; width=&#39;24&#39; height=&#39;24&#39; viewBox=&#39;0 0 27600 27600&#39;%3E%3ClinearGradient id=&#39;A&#39; gradientUnits=&#39;userSpaceOnUse&#39;/%3E%3ClinearGradient id=&#39;B&#39; x1=&#39;6870&#39; x2=&#39;24704&#39; y1=&#39;18705&#39; y2=&#39;18705&#39; xlink:href=&#39;%23A&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%230c59a4&#39;/%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%23114a8b&#39;/%3E%3C/linearGradient%3E%3ClinearGradient id=&#39;C&#39; x1=&#39;16272&#39; x2=&#39;5133&#39; y1=&#39;10968&#39; y2=&#39;23102&#39; xlink:href=&#39;%23A&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%231b9de2&#39;/%3E%3Cstop offset=&#39;.16&#39; stop-color=&#39;%231595df&#39;/%3E%3Cstop offset=&#39;.67&#39; stop-color=&#39;%230680d7&#39;/%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%230078d4&#39;/%3E%3C/linearGradient%3E%3CradialGradient id=&#39;D&#39; cx=&#39;16720&#39; cy=&#39;18747&#39; r=&#39;9538&#39; xlink:href=&#39;%23A&#39;%3E%3Cstop offset=&#39;.72&#39; stop-opacity=&#39;0&#39;/%3E%3Cstop offset=&#39;.95&#39; stop-opacity=&#39;.53&#39;/%3E%3Cstop offset=&#39;1&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;E&#39; cx=&#39;7130&#39; cy=&#39;19866&#39; r=&#39;14324&#39; gradientTransform=&#39;matrix(.14843 -.98892 .79688 .1196 -8759 25542)&#39; xlink:href=&#39;%23A&#39;%3E%3Cstop offset=&#39;.76&#39; stop-opacity=&#39;0&#39;/%3E%3Cstop offset=&#39;.95&#39; stop-opacity=&#39;.5&#39;/%3E%3Cstop offset=&#39;1&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;F&#39; cx=&#39;2523&#39; cy=&#39;4680&#39; r=&#39;20243&#39; gradientTransform=&#39;matrix(-.03715 .99931 -2.12836 -.07913 13579 3530)&#39; xlink:href=&#39;%23A&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%2335c1f1&#39;/%3E%3Cstop offset=&#39;.11&#39; stop-color=&#39;%2334c1ed&#39;/%3E%3Cstop offset=&#39;.23&#39; stop-color=&#39;%232fc2df&#39;/%3E%3Cstop offset=&#39;.31&#39; stop-color=&#39;%232bc3d2&#39;/%3E%3Cstop offset=&#39;.67&#39; stop-color=&#39;%2336c752&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;G&#39; cx=&#39;24247&#39; cy=&#39;7758&#39; r=&#39;9734&#39; gradientTransform=&#39;matrix(.28109 .95968 -.78353 .22949 24510 -16292)&#39; xlink:href=&#39;%23A&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%2366eb6e&#39;/%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%2366eb6e&#39; stop-opacity=&#39;0&#39;/%3E%3C/radialGradient%3E%3Cpath id=&#39;H&#39; d=&#39;M24105 20053a9345 9345 0 01-1053 472 10202 10202 0 01-3590 646c-4732 0-8855-3255-8855-7432 0-1175 680-2193 1643-2729-4280 180-5380 4640-5380 7253 0 7387 6810 8137 8276 8137 791 0 1984-230 2704-456l130-44a12834 12834 0 006660-5282c220-350-168-757-535-565z&#39;/%3E%3Cpath id=&#39;I&#39; d=&#39;M11571 25141a7913 7913 0 01-2273-2137 8145 8145 0 01-1514-4740 8093 8093 0 013093-6395 8082 8082 0 011373-859c312-148 846-414 1554-404a3236 3236 0 012569 1297 3184 3184 0 01636 1866c0-21 2446-7960-8005-7960-4390 0-8004 4166-8004 7820 0 2319 538 4170 1212 5604a12833 12833 0 007684 6757 12795 12795 0 003908 610c1414 0 2774-233 4045-656a7575 7575 0 01-6278-803z&#39;/%3E%3Cpath id=&#39;J&#39; d=&#39;M16231 15886c-80 105-330 250-330 566 0 260 170 512 472 723 1438 1003 4149 868 4156 868a5954 5954 0 003027-839 6147 6147 0 001133-850 6180 6180 0 001910-4437c26-2242-796-3732-1133-4392-2120-4141-6694-6525-11668-6525-7011 0-12703 5635-12798 12620 47-3654 3679-6605 7996-6605 350 0 2346 34 4200 1007 1634 858 2490 1894 3086 2921 618 1067 728 2415 728 2952s-271 1333-780 1990z&#39;/%3E%3Cuse fill=&#39;url(%23B)&#39; xlink:href=&#39;%23H&#39;/%3E%3Cuse fill=&#39;url(%23D)&#39; opacity=&#39;.35&#39; xlink:href=&#39;%23H&#39;/%3E%3Cuse fill=&#39;url(%23C)&#39; xlink:href=&#39;%23I&#39;/%3E%3Cuse fill=&#39;url(%23E)&#39; opacity=&#39;.4&#39; xlink:href=&#39;%23I&#39;/%3E%3Cuse fill=&#39;url(%23F)&#39; xlink:href=&#39;%23J&#39;/%3E%3Cuse fill=&#39;url(%23G)&#39; xlink:href=&#39;%23J&#39;/%3E%3C/svg%3E&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
    &lt;span aria-hidden=&quot;true&quot;&gt;
      
        79
      
    &lt;/span&gt;
  &lt;/li&gt;
  
  
          
  
  
    
      
        
          
          
        
      
    
    
  

  
  
  
  
    
  
  
  
    
  

  &lt;li data-support=&quot;yes&quot;&gt;
    &lt;img alt=&quot;Firefox: 85.&quot; src=&quot;data:image/svg+xml,%3Csvg xmlns=&#39;http://www.w3.org/2000/svg&#39; width=&#39;24&#39; height=&#39;24&#39; viewBox=&#39;0 0 512 512&#39;%3E%3Cdefs%3E%3CradialGradient id=&#39;ff-b&#39; cx=&#39;428.5&#39; cy=&#39;55.1&#39; r=&#39;501&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23ffbd4f&#39;/%3E%3Cstop offset=&#39;.2&#39; stop-color=&#39;%23ffac31&#39;/%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23ff9d17&#39;/%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23ff980e&#39;/%3E%3Cstop offset=&#39;.4&#39; stop-color=&#39;%23ff563b&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff3750&#39;/%3E%3Cstop offset=&#39;.7&#39; stop-color=&#39;%23f5156c&#39;/%3E%3Cstop offset=&#39;.8&#39; stop-color=&#39;%23eb0878&#39;/%3E%3Cstop offset=&#39;.9&#39; stop-color=&#39;%23e50080&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-c&#39; cx=&#39;245.4&#39; cy=&#39;259.9&#39; r=&#39;501&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23960e18&#39;/%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23b11927&#39; stop-opacity=&#39;.7&#39;/%3E%3Cstop offset=&#39;.4&#39; stop-color=&#39;%23db293d&#39; stop-opacity=&#39;.3&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23f5334b&#39; stop-opacity=&#39;.1&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff3750&#39; stop-opacity=&#39;0&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-d&#39; cx=&#39;305.8&#39; cy=&#39;-58.6&#39; r=&#39;363&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23fff44f&#39;/%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23ffdc3e&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff9d12&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff980e&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-e&#39; cx=&#39;190&#39; cy=&#39;390.8&#39; r=&#39;238.6&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%233a8ee6&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%235c79f0&#39;/%3E%3Cstop offset=&#39;.7&#39; stop-color=&#39;%239059ff&#39;/%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%23c139e6&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-f&#39; cx=&#39;252.2&#39; cy=&#39;201.3&#39; r=&#39;126.5&#39; gradientTransform=&#39;matrix(1 0 0 1 -48 31)&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.2&#39; stop-color=&#39;%239059ff&#39; stop-opacity=&#39;0&#39;/%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%238c4ff3&#39; stop-opacity=&#39;.1&#39;/%3E%3Cstop offset=&#39;.8&#39; stop-color=&#39;%237716a8&#39; stop-opacity=&#39;.5&#39;/%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%236e008b&#39; stop-opacity=&#39;.6&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-g&#39; cx=&#39;239.1&#39; cy=&#39;34.6&#39; r=&#39;171.6&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%23ffe226&#39;/%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23ffdb27&#39;/%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23ffc82a&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ffa930&#39;/%3E%3Cstop offset=&#39;.7&#39; stop-color=&#39;%23ff7e37&#39;/%3E%3Cstop offset=&#39;.8&#39; stop-color=&#39;%23ff7139&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-h&#39; cx=&#39;374&#39; cy=&#39;-74.3&#39; r=&#39;732.2&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23fff44f&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff980e&#39;/%3E%3Cstop offset=&#39;.6&#39; stop-color=&#39;%23ff5634&#39;/%3E%3Cstop offset=&#39;.7&#39; stop-color=&#39;%23ff3647&#39;/%3E%3Cstop offset=&#39;.9&#39; stop-color=&#39;%23e31587&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-i&#39; cx=&#39;304.6&#39; cy=&#39;7.1&#39; r=&#39;536.4&#39; gradientTransform=&#39;rotate(84 303 4)&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%23fff44f&#39;/%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23ffe847&#39;/%3E%3Cstop offset=&#39;.2&#39; stop-color=&#39;%23ffc830&#39;/%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23ff980e&#39;/%3E%3Cstop offset=&#39;.4&#39; stop-color=&#39;%23ff8b16&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff672a&#39;/%3E%3Cstop offset=&#39;.6&#39; stop-color=&#39;%23ff3647&#39;/%3E%3Cstop offset=&#39;.7&#39; stop-color=&#39;%23e31587&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-j&#39; cx=&#39;235&#39; cy=&#39;98.1&#39; r=&#39;457.1&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23fff44f&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff980e&#39;/%3E%3Cstop offset=&#39;.6&#39; stop-color=&#39;%23ff5634&#39;/%3E%3Cstop offset=&#39;.7&#39; stop-color=&#39;%23ff3647&#39;/%3E%3Cstop offset=&#39;.9&#39; stop-color=&#39;%23e31587&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-k&#39; cx=&#39;355.7&#39; cy=&#39;124.9&#39; r=&#39;500.3&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23fff44f&#39;/%3E%3Cstop offset=&#39;.2&#39; stop-color=&#39;%23ffe141&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ffaf1e&#39;/%3E%3Cstop offset=&#39;.6&#39; stop-color=&#39;%23ff980e&#39;/%3E%3C/radialGradient%3E%3ClinearGradient id=&#39;ff-a&#39; x1=&#39;446.9&#39; y1=&#39;76.8&#39; x2=&#39;47.9&#39; y2=&#39;461.8&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23fff44f&#39;/%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23ffe847&#39;/%3E%3Cstop offset=&#39;.2&#39; stop-color=&#39;%23ffc830&#39;/%3E%3Cstop offset=&#39;.4&#39; stop-color=&#39;%23ff980e&#39;/%3E%3Cstop offset=&#39;.4&#39; stop-color=&#39;%23ff8b16&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff672a&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff3647&#39;/%3E%3Cstop offset=&#39;.7&#39; stop-color=&#39;%23e31587&#39;/%3E%3C/linearGradient%3E%3ClinearGradient id=&#39;ff-l&#39; x1=&#39;442.1&#39; y1=&#39;74.8&#39; x2=&#39;102.6&#39; y2=&#39;414.3&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.2&#39; stop-color=&#39;%23fff44f&#39; stop-opacity=&#39;.8&#39;/%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23fff44f&#39; stop-opacity=&#39;.6&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23fff44f&#39; stop-opacity=&#39;.2&#39;/%3E%3Cstop offset=&#39;.6&#39; stop-color=&#39;%23fff44f&#39; stop-opacity=&#39;0&#39;/%3E%3C/linearGradient%3E%3C/defs%3E%3Cpath d=&#39;M479 166c-11-25-32-52-49-60a249 249 0 0 1 25 73c-27-68-73-95-111-155a255 255 0 0 1-8-14 44 44 0 0 1-4-9 1 1 0 0 0 0-1 1 1 0 0 0-1 0c-60 35-81 101-83 134a120 120 0 0 0-66 25 71 71 0 0 0-6-5 111 111 0 0 1-1-58c-25 11-44 29-58 44-9-12-9-52-8-60l-8 4a175 175 0 0 0-24 21 210 210 0 0 0-22 26 203 203 0 0 0-32 73l-1 2-2 15a229 229 0 0 0-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121zM202 355l3 1-3-1zm55-145zm198-31z&#39; fill=&#39;url(%23ff-a)&#39;/%3E%3Cpath d=&#39;M479 166c-11-25-32-52-49-60 14 26 22 53 25 72v1a207 207 0 0 1-206 279c-113-3-212-87-231-197-3-17 0-26 2-40-2 11-3 14-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121z&#39; fill=&#39;url(%23ff-b)&#39;/%3E%3Cpath d=&#39;M479 166c-11-25-32-52-49-60 14 26 22 53 25 72v1a207 207 0 0 1-206 279c-113-3-212-87-231-197-3-17 0-26 2-40-2 11-3 14-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121z&#39; fill=&#39;url(%23ff-c)&#39;/%3E%3Cpath d=&#39;m362 195 1 1a130 130 0 0 0-22-29C266 92 322 5 331 0c-60 35-81 101-83 134l9-1c45 0 84 25 105 62z&#39; fill=&#39;url(%23ff-d)&#39;/%3E%3Cpath d=&#39;M257 210c-1 6-22 26-29 26-68 0-80 41-80 41 3 35 28 64 57 79l4 2 7 3a107 107 0 0 0 31 6c120 6 143-143 57-186 22-4 45 5 58 14-21-37-60-62-105-62l-9 1a120 120 0 0 0-66 25l17 16c16 16 58 33 58 35z&#39; fill=&#39;url(%23ff-e)&#39;/%3E%3Cpath d=&#39;M257 210c-1 6-22 26-29 26-68 0-80 41-80 41 3 35 28 64 57 79l4 2 7 3a107 107 0 0 0 31 6c120 6 143-143 57-186 22-4 45 5 58 14-21-37-60-62-105-62l-9 1a120 120 0 0 0-66 25l17 16c16 16 58 33 58 35z&#39; fill=&#39;url(%23ff-f)&#39;/%3E%3Cpath d=&#39;m171 151 5 3a111 111 0 0 1-1-58c-25 11-44 29-58 44 1 0 36 0 54 11z&#39; fill=&#39;url(%23ff-g)&#39;/%3E%3Cpath d=&#39;M18 261a242 242 0 0 0 231 197 207 207 0 0 0 206-279c8 56-20 110-64 146-86 71-169 43-186 31l-3-1c-50-24-71-70-67-110-42 0-57-35-57-35s38-28 89-4c46 22 90 4 90 4 0-2-42-19-58-35l-17-16a71 71 0 0 0-6-5l-5-3c-18-11-52-11-54-11-9-12-9-51-8-60l-8 4a175 175 0 0 0-24 21 210 210 0 0 0-22 26 203 203 0 0 0-32 73c0 1-9 38-5 57z&#39; fill=&#39;url(%23ff-h)&#39;/%3E%3Cpath d=&#39;M341 167a130 130 0 0 1 22 29 46 46 0 0 1 4 3c55 50 26 121 24 126 44-36 72-90 64-146-27-68-73-95-111-155a255 255 0 0 1-8-14 44 44 0 0 1-4-9 1 1 0 0 0 0-1 1 1 0 0 0-1 0c-9 5-65 92 10 167z&#39; fill=&#39;url(%23ff-i)&#39;/%3E%3Cpath d=&#39;M367 199a46 46 0 0 0-4-3l-1-1c-13-9-36-18-58-15 86 44 63 193-57 187a107 107 0 0 1-31-6 131 131 0 0 1-11-5c17 12 99 39 186-31 2-5 31-76-24-126z&#39; fill=&#39;url(%23ff-j)&#39;/%3E%3Cpath d=&#39;M148 277s12-41 80-41c7 0 28-20 29-26s-44 18-90-4c-51-24-89 4-89 4s15 35 57 35c-4 40 16 85 67 110l3 1c-29-15-54-44-57-79z&#39; fill=&#39;url(%23ff-k)&#39;/%3E%3Cpath d=&#39;M479 166c-11-25-32-52-49-60a249 249 0 0 1 25 73c-27-68-73-95-111-155a255 255 0 0 1-8-14 44 44 0 0 1-4-9 1 1 0 0 0 0-1 1 1 0 0 0-1 0c-60 35-81 101-83 134l9-1c45 0 84 25 105 62-13-9-36-18-58-14 86 43 63 192-57 186a107 107 0 0 1-31-6 131 131 0 0 1-11-5l-3-1 3 1c-29-15-54-44-57-79 0 0 12-41 80-41 7 0 28-20 29-26 0-2-42-19-58-35l-17-16a71 71 0 0 0-6-5 111 111 0 0 1-1-58c-25 11-44 29-58 44-9-12-9-52-8-60l-8 4a175 175 0 0 0-24 21 210 210 0 0 0-22 26 203 203 0 0 0-32 73l-1 2-2 15a279 279 0 0 0-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121zm-24 13z&#39; fill=&#39;url(%23ff-l)&#39;/%3E%3C/svg%3E&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
    &lt;span aria-hidden=&quot;true&quot;&gt;
      
        85
      
    &lt;/span&gt;
  &lt;/li&gt;
  
  
          
  
  
    
      
        
          
          
        
      
    
    
  

  
  
  
  
    
  
  
  
    
  

  &lt;li data-support=&quot;yes&quot;&gt;
    &lt;img alt=&quot;Safari: 11.1.&quot; src=&quot;data:image/svg+xml,%3Csvg xmlns=&#39;http://www.w3.org/2000/svg&#39; xmlns:xlink=&#39;http://www.w3.org/1999/xlink&#39; width=&#39;24&#39; height=&#39;24&#39; viewBox=&#39;195 190 135 135&#39;%3E%3Cdefs%3E%3ClinearGradient id=&#39;s-a&#39; x1=&#39;132.6&#39; x2=&#39;134.4&#39; y1=&#39;111.7&#39; y2=&#39;-105.3&#39; xlink:href=&#39;%23s-b&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%23d2d2d2&#39; /%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23f2f2f2&#39; /%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%23fff&#39; /%3E%3C/linearGradient%3E%3ClinearGradient id=&#39;s-b&#39; gradientUnits=&#39;userSpaceOnUse&#39; /%3E%3ClinearGradient id=&#39;s-c&#39; x1=&#39;65.4&#39; x2=&#39;67.4&#39; y1=&#39;115.7&#39; y2=&#39;17.1&#39; xlink:href=&#39;%23s-b&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%23005ad5&#39; /%3E%3Cstop offset=&#39;.2&#39; stop-color=&#39;%230875f0&#39; /%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23218cee&#39; /%3E%3Cstop offset=&#39;.6&#39; stop-color=&#39;%2327a5f3&#39; /%3E%3Cstop offset=&#39;.8&#39; stop-color=&#39;%2325aaf2&#39; /%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%2321aaef&#39; /%3E%3C/linearGradient%3E%3ClinearGradient id=&#39;s-d&#39; x1=&#39;158.7&#39; x2=&#39;176.3&#39; y1=&#39;96.7&#39; y2=&#39;79.5&#39; xlink:href=&#39;%23s-b&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%23c72e24&#39; /%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%23fd3b2f&#39; /%3E%3C/linearGradient%3E%3CradialGradient id=&#39;s-i&#39; cx=&#39;-69.9&#39; cy=&#39;69.3&#39; r=&#39;54&#39; gradientTransform=&#39;matrix(.9 -.01 .04 2.72 -9 -120)&#39; xlink:href=&#39;%23s-b&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%2324a5f3&#39; stop-opacity=&#39;0&#39; /%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%231e8ceb&#39; /%3E%3C/radialGradient%3E%3CradialGradient id=&#39;s-j&#39; cx=&#39;109.3&#39; cy=&#39;13.8&#39; r=&#39;93.1&#39; gradientTransform=&#39;matrix(-.02 1.1 -1.04 -.02 137 -115)&#39; xlink:href=&#39;%23s-b&#39;%3E%3Cstop offset=&#39;0&#39; stop-opacity=&#39;0&#39; /%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%235488d6&#39; stop-opacity=&#39;0&#39; /%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%235d96eb&#39; /%3E%3C/radialGradient%3E%3C/defs%3E%3Crect width=&#39;220&#39; height=&#39;220&#39; x=&#39;22&#39; y=&#39;-107&#39; fill=&#39;url(%23s-a)&#39; ry=&#39;49&#39; transform=&#39;matrix(.57 0 0 .57 187 256)&#39; /%3E%3Cg transform=&#39;translate(194 190)&#39;%3E%3Ccircle cx=&#39;67.8&#39; cy=&#39;67.7&#39; fill=&#39;url(%23s-c)&#39; paint-order=&#39;stroke fill markers&#39; r=&#39;54&#39; /%3E%3Ccircle cx=&#39;-69.9&#39; cy=&#39;69.3&#39; fill=&#39;url(%23s-i)&#39; transform=&#39;translate(138 -2)&#39; r=&#39;54&#39; /%3E%3C/g%3E%3Cellipse cx=&#39;120&#39; cy=&#39;14.2&#39; fill=&#39;url(%23s-j)&#39; rx=&#39;93.1&#39; ry=&#39;93.7&#39; transform=&#39;matrix(.58 0 0 .58 192 250)&#39; /%3E%3Cg transform=&#39;matrix(.58 0 0 .57 197 182)&#39;%3E%3Cpath fill=&#39;%23cac7c8&#39; d=&#39;M46 192h1l72-48-7-9-66 57Z&#39; /%3E%3Cpath fill=&#39;%23fbfffc&#39; d=&#39;M46 191v1l66-57-7-9-59 65Z&#39; /%3E%3Cpath fill=&#39;url(%23s-d)&#39; d=&#39;m119 144-7-9 66-57-59 66Z&#39; /%3E%3Cpath fill=&#39;%23fb645c&#39; d=&#39;m105 126 7 9 66-57-1-1-72 49Z&#39; /%3E%3C/g%3E%3Cpath stroke=&#39;%23fff&#39; stroke-linecap=&#39;round&#39; stroke-miterlimit=&#39;1&#39; stroke-width=&#39;1.3&#39; d=&#39;m287 278 3-2m-12-17 8-2m-8-3h4m-4-13 8 2m-8 3h4m-1-13 7 3m-4-11 7 4m-2-11 6 6m0-12 6 7m1-11 4 6m4-10 3 7m5-9 2 7m15-7-1 7m10-5-3 7m11-4-4 7m11-2-5 6m16 7-7 4m10 4-7 3m10 6-8 1m8 16-8-2m5 10-7-3m4 11-7-4m2 11-6-5m0 11-5-6m-2 11-4-7m-4 11-3-8m-6 10-1-8m-16 8 2-8m-10 5 3-7m-11 4 4-7m-11 2 5-6m-8 3 3-3m4 8 2-3m5 8 2-4m6 7 1-4m8 5v-4m8 4v-4m9 3-1-4m9 1-2-4m9 0-2-4m9-2-3-3m8-4-3-2m8-5-4-2m7-6-4-1m5-8h-4m4-8h-4m3-9-4 1m1-9-4 2m-1-9-3 2m-2-9-3 3m-4-8-2 3m-5-8-2 4m-6-6-1 3m-8-5v4m-8-4v4m-9-2 1 3m-9 0 2 3m-9 1 2 3m-9 2 3 3m-8 4 3 2m-8 5 4 2m-7 6 4 1m-4 25 4-1m-2 5 7-3m-6 7 4-2m-2 6 7-4m-13-21h8m41-41v-8m0 99v-8m49-42h-8&#39; transform=&#39;translate(-65 8)&#39; /%3E%3C/svg%3E&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
    &lt;span aria-hidden=&quot;true&quot;&gt;
      
        11.1
      
    &lt;/span&gt;
  &lt;/li&gt;
  
  
        &lt;/ul&gt;
        
        &lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTML/Reference/Attributes/rel/preload&quot;&gt;Source&lt;/a&gt;&lt;/p&gt;
        
      &lt;/div&gt;
    
    
    
  &lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://web.dev/articles/preload-critical-assets&quot;&gt;Preloading&lt;/a&gt; lets you tell the browser about
critical resources that you want to load as soon as possible, before they&#39;re
discovered in HTML. This is especially useful for resources that aren&#39;t readily
discoverable, such as fonts included in stylesheets, background images, or
resources loaded from a script.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;HTML&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;link rel=&quot;preload&quot; as=&quot;image&quot; href=&quot;important.png&quot; fetchpriority=&quot;high&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;aside class=&quot;special&quot;&gt;
  &lt;b&gt;Important:&lt;/b&gt; Preloaded images are presumably high priority and so should include the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;fetchpriority=&quot;high&quot;&lt;/code&gt; attribute as otherwise the preload will get the default priority for the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;as&lt;/code&gt; attribute. For images this is &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;low&lt;/code&gt; by default so the browser can prioritise render-blocking content over images. See &lt;a href=&quot;https://web.dev/articles/fetch-priority&quot;&gt;Optimize resource loading with the Fetch Priority API&lt;/a&gt; for more details.
&lt;/aside&gt;

&lt;h2 id=&quot;imagesrcset-imagesizes&quot; data-text=&quot;imagesrcset and imagesizes&quot; tabindex=&quot;-1&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;imagesrcset&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;imagesizes&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;link&amp;gt;&lt;/code&gt; element uses the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;imagesrcset&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;imagesizes&lt;/code&gt; attributes to
preload responsive images. Use them alongside
 &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;link rel=&quot;preload&quot;&amp;gt;&lt;/code&gt;, with the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;srcset&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;sizes&lt;/code&gt; syntax used in the
 &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;img&amp;gt;&lt;/code&gt; element.&lt;/p&gt;

&lt;p&gt;For example, if you want to preload a responsive image specified with:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;HTML&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt; &amp;lt;img src=&quot;wolf.jpg&quot; srcset=&quot;wolf_400px.jpg 400w, wolf_800px.jpg 800w, wolf_1600px.jpg 1600w&quot; sizes=&quot;50vw&quot; alt=&quot;A rad wolf&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;You can do that by adding the following to your HTML&#39;s &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;head&amp;gt;&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;HTML&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;link rel=&quot;preload&quot; as=&quot;image&quot; href=&quot;wolf.jpg&quot; imagesrcset=&quot;wolf_400px.jpg 400w, wolf_800px.jpg 800w, wolf_1600px.jpg 1600w&quot; imagesizes=&quot;50vw&quot; fetchpriority=&quot;high&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;This initiates a request using the same resource selection logic that
&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;srcset&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;sizes&lt;/code&gt; use.&lt;/p&gt;

&lt;aside class=&quot;warning&quot; id=&quot;http-not-supported&quot;&gt;
  &lt;p&gt;&lt;b&gt;Warning:&lt;/b&gt; Responsive images may not be supported for HTTP header preloads as the viewport is only defined when the document is created. This also means they &lt;a href=&quot;https://developer.chrome.com/docs/web-platform/early-hints#current-limitations&quot;&gt;cannot be used for early preloading in 103 Early Hints&lt;/a&gt;.&lt;/p&gt;
  &lt;p&gt;Some browsers, such as Chrome, do process the preloads with those attributes once the document is defined and after processing the first chunk of HTML. This &lt;a href=&quot;https://github.com/whatwg/html/issues/7873&quot;&gt;has been proposed to the HTML standard&lt;/a&gt;. However, this can still be risky if the viewport is redefined later in the document (for example, a late &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;meta name=&quot;viewport&quot;&amp;gt;&lt;/code&gt; element) as the wrong responsive images may already have started loading.&lt;/p&gt;
  &lt;p&gt;To avoid any issues, it is recommended to use &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;link rel=&quot;preload&quot; href=&quot;...&quot;&amp;gt;&lt;/code&gt; elements for responsive images rather than &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;Link: &amp;lt;...&amp;gt;; rel=&quot;preload&quot;&lt;/code&gt; HTTP response headers.&lt;/p&gt;
&lt;/aside&gt;

&lt;h2 id=&quot;use_cases&quot; data-text=&quot;Use cases&quot; tabindex=&quot;-1&quot;&gt;Use cases&lt;/h2&gt;

&lt;p&gt;The following are some use cases for preloading responsive images.&lt;/p&gt;

&lt;h3 id=&quot;dynamically-injected&quot; data-text=&quot;Preload dynamically-injected responsive images&quot; tabindex=&quot;-1&quot;&gt;Preload dynamically-injected responsive images&lt;/h3&gt;

&lt;p&gt;Imagine you&#39;re dynamically-loading hero images as part of a slideshow, and you
know which image will be displayed first. In that case, you probably want to
show that image as soon as possible, and not wait for the slideshow script to
load it.&lt;/p&gt;

&lt;p&gt;You can inspect this issue on a website with a dynamically-loaded image gallery:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open this &lt;a href=&quot;https://responsive-preload.glitch.me/no_preload.html&quot;&gt;slideshow demo&lt;/a&gt;
in a new tab.&lt;/li&gt;
&lt;li&gt;Press &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;Control+Shift+J&lt;/code&gt; (or &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;Command+Option+J&lt;/code&gt; on Mac) to open DevTools.&lt;/li&gt;
&lt;li&gt;Click the &lt;strong&gt;Network&lt;/strong&gt; tab.&lt;/li&gt;
&lt;li&gt;In the &lt;strong&gt;Throttling&lt;/strong&gt; drop-down list, select &lt;strong&gt;Fast 3G&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Disable the &lt;strong&gt;Disable cache&lt;/strong&gt; checkbox.&lt;/li&gt;
&lt;li&gt;Reload the page.&lt;/li&gt;
&lt;/ol&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-cb8c539cfc089.png&quot; alt=&quot;Chrome DevTools Network panel showing a waterfall with a JPEG resource only starting to download after some JavaScript.&quot; width=&quot;800&quot; height=&quot;481&quot; srcset=&quot;https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-cb8c539cfc089_36.png 36w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-cb8c539cfc089_48.png 48w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-cb8c539cfc089_72.png 72w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-cb8c539cfc089_96.png 96w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-cb8c539cfc089_480.png 480w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-cb8c539cfc089_720.png 720w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-cb8c539cfc089_856.png 856w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-cb8c539cfc089_960.png 960w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-cb8c539cfc089_1440.png 1440w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-cb8c539cfc089_1920.png 1920w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-cb8c539cfc089_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption class=&quot;wd-caption&quot;&gt;Without preloading, the images start loading after the browser has finished running the script. For the first image, that delay is unnecessary.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Using &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;preload&lt;/code&gt; here lets the image start loading ahead of time, so it can be
ready to display when the browser needs to display it.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-cecd20c3666af.png&quot; alt=&quot;Chrome DevTools Network panel showing a waterfall with a JPEG resource downloading in parallel to some JavaScript.&quot; width=&quot;800&quot; height=&quot;481&quot; srcset=&quot;https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-cecd20c3666af_36.png 36w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-cecd20c3666af_48.png 48w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-cecd20c3666af_72.png 72w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-cecd20c3666af_96.png 96w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-cecd20c3666af_480.png 480w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-cecd20c3666af_720.png 720w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-cecd20c3666af_856.png 856w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-cecd20c3666af_960.png 960w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-cecd20c3666af_1440.png 1440w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-cecd20c3666af_1920.png 1920w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-cecd20c3666af_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption class=&quot;wd-caption&quot;&gt;Preloading the first image lets it start loading at the same time as the script.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;To see the difference that preloading makes, inspect the same dynamically-loaded
image gallery but &lt;a href=&quot;https://responsive-preload.glitch.me/preload.html&quot;&gt;with the first image preloaded&lt;/a&gt;
by following the steps from the first example.&lt;/p&gt;
&lt;aside class=&quot;note&quot;&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;span&gt; Another way to avoid delays in image loading is using a markup-based
carousel and having the &lt;a href=&quot;https://hacks.mozilla.org/2017/09/building-the-dom-faster-speculative-parsing-async-defer-and-preload/&quot;&gt;browser&#39;s preloader&lt;/a&gt; pick up
the required resources. However, this approach isn&#39;t always practical, for
example, if you&#39;re reusing an existing component that isn&#39;t markup-based.&lt;/span&gt;&lt;/aside&gt;
&lt;h3 id=&quot;image-set&quot; data-text=&quot;Preload background images using image-set&quot; tabindex=&quot;-1&quot;&gt;Preload background images using image-set&lt;/h3&gt;

&lt;p&gt;If you have different background images for different screen densities, you can
specify them in your CSS with the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;image-set&lt;/code&gt; syntax. The browser can then
choose which one to display based on the screen&#39;s
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Window/devicePixelRatio&quot;&gt;DPR&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;background-image&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;image-set&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;cat.png&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;1x&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;cat-2x.png&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;2x&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;&lt;aside class=&quot;note&quot;&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;span&gt; This syntax ignores this feature&#39;s need for vendor prefixes in both
Chromium and WebKit-based browsers. If you&#39;re planning to use this feature,
consider using &lt;a href=&quot;https://github.com/postcss/autoprefixer&quot;&gt;Autoprefixer&lt;/a&gt;
(&lt;a href=&quot;https://goonlinetools.com/autoprefixer/&quot;&gt;available as an online tool&lt;/a&gt;) to
address that automatically.&lt;/span&gt;&lt;/aside&gt;
&lt;p&gt;The problem with CSS background images is that the browser discovers them
only after it has downloaded and processed all the CSS in the page&#39;s &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;head&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can inspect this issue on an example website with a
&lt;a href=&quot;https://responsive-preload.glitch.me/background_no_preload.html&quot;&gt;responsive background image&lt;/a&gt;.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-f25333cf0e854.png&quot; alt=&quot;Chrome DevTools Network panel showing a waterfall with a JPEG resource only starting to download after some CSS.&quot; width=&quot;800&quot; height=&quot;451&quot; srcset=&quot;https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-f25333cf0e854_36.png 36w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-f25333cf0e854_48.png 48w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-f25333cf0e854_72.png 72w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-f25333cf0e854_96.png 96w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-f25333cf0e854_480.png 480w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-f25333cf0e854_720.png 720w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-f25333cf0e854_856.png 856w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-f25333cf0e854_960.png 960w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-f25333cf0e854_1440.png 1440w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-f25333cf0e854_1920.png 1920w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-f25333cf0e854_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption class=&quot;wd-caption&quot;&gt;In this example, the image download doesn&#39;t start until the CSS is fully downloaded, causing unnecessary lag to the image&#39;s display.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Responsive image preloading lets you load those images faster.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;HTML&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;link rel=&quot;preload&quot; as=&quot;image&quot; imagesrcset=&quot;cat.png 1x, cat-2x.png 2x&quot; fetchpriority=&quot;high&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;Leaving out the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;href&lt;/code&gt; attribute lets you ensure that browsers that don&#39;t
support &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;imagesrcset&lt;/code&gt; on the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;link&amp;gt;&lt;/code&gt; element, but do support &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;image-set&lt;/code&gt; in
CSS download the correct source. However, they won&#39;t benefit from the preload in
this case.&lt;/p&gt;

&lt;p&gt;You can inspect how the previous example behaves with a preloaded responsive
background image in &lt;a href=&quot;https://responsive-preload.glitch.me/background_preload.html&quot;&gt;the responsive background preload demo&lt;/a&gt;.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-6e4a42d42a6ff.png&quot; alt=&quot;Chrome DevTools Network panel showing a waterfall with a JPEG resource downloading in parrallel to some CSS.&quot; width=&quot;800&quot; height=&quot;439&quot; srcset=&quot;https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-6e4a42d42a6ff_36.png 36w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-6e4a42d42a6ff_48.png 48w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-6e4a42d42a6ff_72.png 72w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-6e4a42d42a6ff_96.png 96w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-6e4a42d42a6ff_480.png 480w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-6e4a42d42a6ff_720.png 720w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-6e4a42d42a6ff_856.png 856w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-6e4a42d42a6ff_960.png 960w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-6e4a42d42a6ff_1440.png 1440w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-6e4a42d42a6ff_1920.png 1920w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-chrome-devtoo-6e4a42d42a6ff_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption class=&quot;wd-caption&quot;&gt;Here the image and CSS start downloading at the same time, letting the image load faster.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;practical&quot; data-text=&quot;Practical effects of preloading responsive images&quot; tabindex=&quot;-1&quot;&gt;Practical effects of preloading responsive images&lt;/h2&gt;

&lt;p&gt;Preloading your responsive images can speed them up in theory, but what does it
do in practice?&lt;/p&gt;

&lt;p&gt;To answer that I created two copies of a &lt;a href=&quot;https://github.com/GoogleChromeLabs/sample-pie-shop&quot;&gt;demo PWA shop&lt;/a&gt;:
&lt;a href=&quot;https://20190710t144416-dot-pie-shop-app.appspot.com/apparel&quot;&gt;one that doesn&#39;t preload images&lt;/a&gt;,
and &lt;a href=&quot;https://20190710t132936-dot-pie-shop-app.appspot.com/apparel&quot;&gt;one that preloads some of them&lt;/a&gt;.
Because the site lazy loads images using JavaScript, it&#39;s likely to benefit from
preloading the ones that appear in the initial viewport.&lt;/p&gt;

&lt;p&gt;That produced the following results for
&lt;a href=&quot;https://www.webpagetest.org/result/190710_VM_30b9d4c993a1e60befba17e1261ba1ca/&quot;&gt;no preload&lt;/a&gt;
and for &lt;a href=&quot;https://www.webpagetest.org/result/190710_7B_a99e792121760f81a270b4b9c847797b/&quot;&gt;image preload&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/WPO-Foundation/webpagetest-docs/blob/main/src/getting-started.md#start-render&quot;&gt;Start Render&lt;/a&gt;
stayed the same.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/speed-index&quot;&gt;Speed Index&lt;/a&gt;
improved slightly (273 ms, as images arrive faster don&#39;t take up a huge chunk
of the pixel area).&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/WPO-Foundation/webpagetest/blob/master/docs/Metrics/HeroElements.md&quot;&gt;Last Painted Hero&lt;/a&gt;
improved significantly, by 1.2 seconds.&lt;/li&gt;
&lt;/ul&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/preload-responsive-images/image/screenshot-webpagetest-f-8e3402c523246.png&quot; alt=&quot;WebPageTest filmstrip comparison showing preloaded images are displayed about 1.5 seconds faster.&quot; width=&quot;800&quot; height=&quot;328&quot; srcset=&quot;https://web.dev/static/articles/preload-responsive-images/image/screenshot-webpagetest-f-8e3402c523246_36.png 36w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-webpagetest-f-8e3402c523246_48.png 48w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-webpagetest-f-8e3402c523246_72.png 72w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-webpagetest-f-8e3402c523246_96.png 96w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-webpagetest-f-8e3402c523246_480.png 480w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-webpagetest-f-8e3402c523246_720.png 720w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-webpagetest-f-8e3402c523246_856.png 856w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-webpagetest-f-8e3402c523246_960.png 960w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-webpagetest-f-8e3402c523246_1440.png 1440w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-webpagetest-f-8e3402c523246_1920.png 1920w,https://web.dev/static/articles/preload-responsive-images/image/screenshot-webpagetest-f-8e3402c523246_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption class=&quot;wd-caption&quot;&gt;Images arrive significantly faster when preloaded, greatly improving the user experience.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;picture&quot; data-text=&quot;Preload and &lt;picture&gt;&quot; tabindex=&quot;-1&quot;&gt;Preload and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;picture&amp;gt;&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;The Web Performance Working Group is discussing adding a preload equivalent for
&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;srcset&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;sizes&lt;/code&gt;, but not the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTML/Element/picture&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;picture&amp;gt;&lt;/code&gt;&lt;/a&gt;
element, which handles the &lt;a href=&quot;https://web.dev/articles/codelab-art-direction&quot;&gt;&quot;art direction&quot;&lt;/a&gt;
use case.&lt;/p&gt;

&lt;p&gt;There are still a number of &lt;a href=&quot;https://calendar.perfplanet.com/2018/how-the-sausage-is-made-webperfwg-meeting-summary/&quot;&gt;technical issues to sort out&lt;/a&gt; for preloading &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;picture&amp;gt;&lt;/code&gt;,
but in the meantime, there are workarounds:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;HTML&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;picture&amp;gt;
    &amp;lt;source srcset=&quot;small_cat.jpg&quot; media=&quot;(max-width: 400px)&quot;&amp;gt;
    &amp;lt;source srcset=&quot;medium_cat.jpg&quot; media=&quot;(max-width: 800px)&quot;&amp;gt;
    &amp;lt;img src=&quot;large_cat.jpg&quot;&amp;gt;
&amp;lt;/picture&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; element&#39;s image source selection logic goes over the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;media&lt;/code&gt;
attributes of the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;source&amp;gt;&lt;/code&gt; elements in order, finds the first one that
matches, and uses the attached resource.&lt;/p&gt;

&lt;p&gt;Because responsive preload has no notion of &quot;order&quot; or &quot;first match&quot;, you&#39;ll
need to translate the breakpoints into something like the following:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;HTML&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;link rel=&quot;preload&quot; href=&quot;small_cat.jpg&quot; as=&quot;image&quot; media=&quot;(max-width: 400px)&quot; fetchpriority=&quot;high&quot;&amp;gt;
&amp;lt;link rel=&quot;preload&quot; href=&quot;medium_cat.jpg&quot; as=&quot;image&quot; media=&quot;(min-width: 400.1px) and (max-width: 800px)&quot; fetchpriority=&quot;high&quot;&amp;gt;
&amp;lt;link rel=&quot;preload&quot; href=&quot;large_cat.jpg&quot; as=&quot;image&quot; media=&quot;(min-width: 800.1px)&quot; fetchpriority=&quot;high&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;h3 id=&quot;type&quot; data-text=&quot;Preload and type&quot; tabindex=&quot;-1&quot;&gt;Preload and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;type&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; element also supports matching on the first &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;type&lt;/code&gt;, to let you provide different image formats so the browser can pick the first image format it supports.&lt;/p&gt;

&lt;p&gt;This use case is only partially supported with preload: browsers should only download preloads for supported types. So you can use this to prevent browsers preloading unsupported mimetypes types by including this in the preload:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;HTML&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;link rel=&quot;preload&quot; href=&quot;image.avif&quot; type=&quot;image/avif&quot; as=&quot;image&quot; fetchpriority=&quot;high&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;However, unlike &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; it won&#39;t stop at the first supported type. So if multiple preloads are included for multiple types, then all the images will be preloaded:&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;compare-worse&quot;&gt;Don&#39;t do this&lt;/span&gt; — preload multiple types:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;HTML&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;link rel=&quot;preload&quot; href=&quot;image.avif&quot; type=&quot;image/avif&quot; as=&quot;image&quot; fetchpriority=&quot;high&quot;&amp;gt;
&amp;lt;link rel=&quot;preload&quot; href=&quot;image.jpg&quot; type=&quot;image/jpg&quot; as=&quot;image&quot; fetchpriority=&quot;high&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;&lt;span class=&quot;compare-better&quot;&gt;Instead do this&lt;/span&gt; — preload the most preferred type:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;HTML&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;link rel=&quot;preload&quot; href=&quot;image.avif&quot; type=&quot;image/avif&quot; as=&quot;image&quot; fetchpriority=&quot;high&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;Preloading the newest format (AVIF in this case) acts as as a progressive enhancement, where browsers supporting that type will benefit and other browsers won&#39;t benefit from the preload.&lt;/p&gt;

&lt;p&gt;For sites where the image is quickly discoverable in the HTML, we recommend avoiding preload, and instead having the &lt;a href=&quot;https://web.dev/articles/preload-scanner&quot;&gt;preload scanner&lt;/a&gt; pick up the images from the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;source&amp;gt;&lt;/code&gt; elements instead. This is a best practice anyway, especially when using &lt;a href=&quot;https://web.dev/articles/fetch-priority&quot;&gt;Fetch Priority&lt;/a&gt; for help prioritizing the appropriate image, as it allows the exact image to be preloaded based on browser support. It also removes the risk of a preload becoming outdated from the main markup when images or pages change.&lt;/p&gt;

&lt;h2 id=&quot;lcp&quot; data-text=&quot;Effects on Largest Contentful Paint (LCP)&quot; tabindex=&quot;-1&quot;&gt;Effects on Largest Contentful Paint (LCP)&lt;/h2&gt;

&lt;p&gt;Because images can be &lt;a href=&quot;https://web.dev/articles/lcp#what-elements-are-considered&quot;&gt;Largest Contentful Paint (LCP) candidates&lt;/a&gt;,
preloading them can improve your website&#39;s LCP.&lt;/p&gt;

&lt;p&gt;Regardless of whether the image you&#39;re preloading is responsive, preloads work
best when the image resource isn&#39;t discoverable in the initial markup payload.
You&#39;ll also get more LCP improvement on sites that render markup on the client
side than on sites that send complete markup from the server.&lt;/p&gt;

  

  
</description><link>https://web.dev/articles/preload-responsive-images?hl=en</link><guid isPermaLink="false">https://web.dev/articles/preload-responsive-images?hl=en</guid><pubDate>Fri, 10 Jul 2026 07:00:00 GMT</pubDate></item><item><title>Back/forward cache</title><description>&lt;p&gt;&lt;style&gt;
  .wd-author__links {
    display: flex;
  }
  &lt;/style&gt;

&lt;/p&gt;&lt;p&gt;&lt;/p&gt;

&lt;p&gt;
&lt;/p&gt;&lt;p class=&quot;wd-pubdates&quot; style=&quot;font-size: 90%; border-bottom: 1px solid var(--devsite-ref-palette--grey200);color: var(--devsite-ref-palette--blue-grey700);&quot;&gt;
  Published: May 25, 2023, Last updated: July 2, 2026
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Back/forward cache (or bfcache) is a browser optimization that enables instant
back and forward navigation. It significantly improves the browsing experience,
especially for users with slower networks or devices.&lt;/p&gt;

&lt;p&gt;As web developers, it&#39;s critical to understand how to &lt;a href=&quot;https://web.dev/articles/bfcache?hl=en#optimize&quot;&gt;optimize your pages for bfcache&lt;/a&gt;, so your users can reap the benefits.&lt;/p&gt;

&lt;aside class=&quot;note&quot;&gt;
&lt;p&gt;Prefer video to reading? Check out this &lt;a href=&quot;https://youtu.be/Y2IVv5KnrmI&quot;&gt;DevTools Tips video on debugging bfcache&lt;/a&gt; for a summary of what&#39;s on this page.&lt;/p&gt;
&lt;devsite-video video-id=&quot;Y2IVv5KnrmI&quot;&gt;&lt;/devsite-video&gt;
&lt;/aside&gt;

&lt;h2 id=&quot;browser-compatibility&quot; data-text=&quot;Browser compatibility&quot; tabindex=&quot;-1&quot;&gt;Browser compatibility&lt;/h2&gt;

&lt;p&gt;All major browsers include a bfcache, including Chrome since version 96, &lt;a href=&quot;https://developer.mozilla.org/docs/Mozilla/Firefox/Releases/1.5/Using_Firefox_1.5_caching&quot;&gt;Firefox&lt;/a&gt; and &lt;a href=&quot;https://webkit.org/blog/427/webkit-page-cache-i-the-basics/&quot;&gt;Safari&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;basics&quot; data-text=&quot;bfcache basics&quot; tabindex=&quot;-1&quot;&gt;bfcache basics&lt;/h2&gt;

&lt;p&gt;With back/forward cache (bfcache), instead of destroying a page when the user navigates away, we postpone destruction and pause JS execution. If the user navigates back soon, we make the page visible again and unpause JS execution. This results in a near instant page navigation for the user.&lt;/p&gt;

&lt;p&gt;How many times have you visited a website and clicked a link to go to another page, only to realize it&#39;s not what you wanted and click the back button? In that moment, bfcache can make a big difference in how fast the previous page loads:&lt;/p&gt;

&lt;div class=&quot;table-wrapper&quot;&gt;
  &lt;table data-alignment=&quot;top&quot;&gt;
    &lt;tbody&gt;&lt;tr&gt;
      &lt;td width=&quot;30%&quot;&gt;&lt;strong&gt;&lt;em&gt;Without&lt;/em&gt; bfcache enabled&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;
        A new request is initiated to load the previous page, and, depending on how well that page has been &lt;a href=&quot;https://web.dev/explore/reliable#the-options-in-your-caching-toolbox&quot;&gt; optimized&lt;/a&gt; for repeat visits, the browser might have to re-download, re-parse, and re-execute some (or all) of resources it just downloaded.
      &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;&lt;em&gt;With&lt;/em&gt; bfcache enabled&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;
        Loading the previous page is &lt;em&gt;essentially instant&lt;/em&gt;, because the entire page can be restored from memory, without having to go to the network at all.
      &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;

&lt;p&gt;Check out this video of bfcache in action to understand the speed up it can bring to navigations:&lt;/p&gt;

&lt;figure&gt;
  &lt;devsite-video video-id=&quot;cuPsdRckkF0&quot;&gt;&lt;/devsite-video&gt;
  &lt;figcaption&gt;Using bfcache makes pages load much more quickly during back and forward navigation.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;In the video, the example with bfcache is quite a bit faster than the example without it.&lt;/p&gt;

&lt;p&gt;bfcache not only speeds up navigation, it also reduces data usage, since resources don&#39;t have to be downloaded again.&lt;/p&gt;

&lt;p&gt;Chrome usage data shows that 1 in 10 navigations on desktop and 1 in 5 on mobile are either back or forward. With bfcache enabled, browsers could eliminate the data transfer and time spent loading for billions of web pages every single day!&lt;/p&gt;

&lt;h3 id=&quot;how-bfcache-works&quot; data-text=&quot;How the &amp;quot;cache&amp;quot; works&quot; tabindex=&quot;-1&quot;&gt;How the &quot;cache&quot; works&lt;/h3&gt;

&lt;p&gt;The &quot;cache&quot; used by bfcache is different from the &lt;a href=&quot;https://web.dev/articles/http-cache&quot;&gt;HTTP cache&lt;/a&gt;, which plays its own role in speeding up repeat navigations. bfcache is a snapshot of the entire page in memory, including the JavaScript heap, whereas the HTTP cache contains only the responses for previously made requests. Since it&#39;s very rare for all requests required to load a page to be fulfilled from the HTTP cache, repeat visits using bfcache restores are always faster than even the most well-optimized non-bfcache navigations.&lt;/p&gt;

&lt;p&gt;Freezing a page to potentially re-enable it again later involves some complexity in terms of how best to preserve in-progress code. For example, how do you handle &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;setTimeout()&lt;/code&gt; calls where the timeout is reached while the page is in the bfcache?&lt;/p&gt;

&lt;p&gt;The answer is that browsers pause any pending timers or unresolved promises for pages in bfcache, including almost all pending tasks in the &lt;a href=&quot;https://html.spec.whatwg.org/multipage/webappapis.html#task-queue&quot;&gt;JavaScript task queues&lt;/a&gt;, and resume processing tasks if the page is restored from the bfcache.&lt;/p&gt;

&lt;p&gt;In some cases, such as for timeouts and promises, this is fairly low risk, but in other cases it can lead to confusing or unexpected behavior. For example, if the browser pauses a task that&#39;s required as part of an &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/IDBTransaction&quot;&gt;IndexedDB
transaction&lt;/a&gt;, it can affect other open tabs in the same origin, because the same IndexedDB databases can be accessed by multiple tabs simultaneously. As a result, browsers will generally not attempt to cache pages in the middle of an IndexedDB transaction or while using APIs that might affect other pages.&lt;/p&gt;

&lt;p&gt;For more details on how various API usage affects a page&#39;s bfcache eligibility, see &lt;a href=&quot;https://web.dev/articles/bfcache?hl=en#optimize&quot;&gt;Optimize your pages for bfcache&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;iframes&quot; data-text=&quot;The bfcache and iframes&quot; tabindex=&quot;-1&quot;&gt;The bfcache and iframes&lt;/h3&gt;

&lt;p&gt;If a page contains embedded iframes, then the iframes themselves are not separately eligible for the bfcache. For example, if you navigate to another URL within an iframe, the previous content does not enter bfcache and if you go back, the browser will go &quot;back&quot; within the iframe rather than in the main frame, but the back navigation within the iframe won&#39;t use the bfcache.&lt;/p&gt;

&lt;p&gt;However, when the main frame is restored from the bfcache, embedded iframes will be restored as they were when the page entered the bfcache.&lt;/p&gt;

&lt;p&gt;The main frame can also be blocked from using the bfcache if an embedded iframe uses APIs that block this. The &lt;a href=&quot;https://web.dev/articles/bfcache?hl=en#permissions-policy&quot;&gt;Permissions Policy&lt;/a&gt; set on the main frame or the use of &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTML/Element/iframe#sandbox&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;sandbox&lt;/code&gt; attributes&lt;/a&gt; can be used to avoid this.&lt;/p&gt;

&lt;h3 id=&quot;spa&quot; data-text=&quot;The bfcache and Single Page Apps (SPA)&quot; tabindex=&quot;-1&quot;&gt;The bfcache and Single Page Apps (SPA)&lt;/h3&gt;

&lt;p&gt;Because bfcache works with browser-managed navigations, it doesn&#39;t work for &quot;soft navigations&quot; within a single-page app (SPA). However, bfcache can still help when going back to an SPA rather than doing a full re-initialisation of that app again from the start.&lt;/p&gt;

&lt;h3 id=&quot;observe&quot; data-text=&quot;APIs to observe bfcache&quot; tabindex=&quot;-1&quot;&gt;APIs to observe bfcache&lt;/h3&gt;

&lt;p&gt;Although bfcache is an optimization that browsers do automatically, it&#39;s still important for developers to know when it&#39;s happening so they can &lt;a href=&quot;https://web.dev/articles/bfcache?hl=en#optimize&quot;&gt;optimize their pages for it&lt;/a&gt; and &lt;a href=&quot;https://web.dev/articles/bfcache?hl=en#analytics&quot;&gt;adjust any metrics or performance measurement&lt;/a&gt; accordingly.&lt;/p&gt;

&lt;p&gt;The primary events used to observe bfcache are the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/PageTransitionEvent&quot;&gt;page transition events&lt;/a&gt; &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;pageshow&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;pagehide&lt;/code&gt;, which are supported by &lt;a href=&quot;https://caniuse.com/page-transition-events&quot;&gt;most browsers&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The newer &lt;a href=&quot;https://developer.chrome.com/blog/page-lifecycle-api&quot;&gt;Page Lifecycle&lt;/a&gt; events—&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;freeze&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;resume&lt;/code&gt;—are also dispatched when pages enter or leave bfcache, as well as in some other situations, for example, when a background tab gets frozen to minimize CPU usage. These events are only supported in Chromium-based browsers.&lt;/p&gt;

&lt;h4 id=&quot;observe-page-restored&quot; data-text=&quot;Observe when a page is restored from bfcache&quot; tabindex=&quot;-1&quot;&gt;Observe when a page is restored from bfcache&lt;/h4&gt;

&lt;p&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;pageshow&lt;/code&gt; event fires right after the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;load&lt;/code&gt; event when the page is initially loading and any time the page is restored from bfcache. The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;pageshow&lt;/code&gt; event has a &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/PageTransitionEvent/persisted&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;persisted&lt;/code&gt;&lt;/a&gt; property, which is &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;true&lt;/code&gt; if the page was restored from bfcache and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;false&lt;/code&gt; otherwise. You can use the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;persisted&lt;/code&gt; property to distinguish regular page loads from bfcache restores. For example:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JavaScript&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;pageshow&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&amp;gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;persisted&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;This page was restored from the bfcache.&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;This page was loaded normally.&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;In browsers that support the Page Lifecycle API, the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;resume&lt;/code&gt; event fires when pages are restored from bfcache (immediately before the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;pageshow&lt;/code&gt; event) and when a user revisits a frozen background tab. If you want to update a page&#39;s state after it&#39;s frozen (which includes pages in the bfcache), you can use the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;resume&lt;/code&gt; event, but if you want to measure your site&#39;s bfcache hit rate, you&#39;d need to use the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;pageshow&lt;/code&gt; event. In some cases, you might need to use both.&lt;/p&gt;

&lt;p&gt;For details on bfcache measurement best practices, see &lt;a href=&quot;https://web.dev/articles/bfcache?hl=en#analytics&quot;&gt;How bfcache affects analytics and performance measurement&lt;/a&gt;.&lt;/p&gt;

&lt;h4 id=&quot;observe-page-enters&quot; data-text=&quot;Observe when a page is entering bfcache&quot; tabindex=&quot;-1&quot;&gt;Observe when a page is entering bfcache&lt;/h4&gt;

&lt;p&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;pagehide&lt;/code&gt; event fires either when a page unloads or when the browser tries to put it in the bfcache.&lt;/p&gt;

&lt;p&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;pagehide&lt;/code&gt; event also has a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;persisted&lt;/code&gt; property. If it&#39;s &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;false&lt;/code&gt;, you can be confident a that page isn&#39;t about to enter the bfcache. However, &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;persisted&lt;/code&gt; being &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;true&lt;/code&gt; doesn&#39;t guarantee that a page will be cached. It means the browser &lt;em&gt;intends&lt;/em&gt; to cache the page, but there may be other factors that make it impossible to cache.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JavaScript&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;pagehide&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&amp;gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;persisted&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;This page *might* be entering the bfcache.&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;This page will unload normally and be discarded.&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;Similarly, the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;freeze&lt;/code&gt; event fires immediately after the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;pagehide&lt;/code&gt; event if &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;persisted&lt;/code&gt; is &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;true&lt;/code&gt;, but that only means the browser &lt;em&gt;intends&lt;/em&gt; to cache the page. It might still have to discard it for a number of reasons explained later.&lt;/p&gt;

&lt;h2 id=&quot;optimize&quot; data-text=&quot;Optimize your pages for bfcache&quot; tabindex=&quot;-1&quot;&gt;Optimize your pages for bfcache&lt;/h2&gt;

&lt;p&gt;Not all pages get stored in bfcache, and even when a page does get stored there, it won&#39;t stay there indefinitely. It&#39;s critical that developers understand what makes pages eligible (and ineligible) for bfcache to maximize their cache-hit rates.&lt;/p&gt;

&lt;p&gt;The following sections outline the best practices to make it as likely as possible that the browser can cache your pages.&lt;/p&gt;

&lt;h3 id=&quot;never-use-the-unload-event&quot; data-text=&quot;Never use the unload event&quot; tabindex=&quot;-1&quot;&gt;Never use the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;unload&lt;/code&gt; event&lt;/h3&gt;

&lt;p&gt;The most important way to optimize for bfcache in all browsers is to never use the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;unload&lt;/code&gt; event. Ever!&lt;/p&gt;

&lt;p&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;unload&lt;/code&gt; event is problematic for browsers because it predates bfcache and many pages on the internet operate under the (reasonable) assumption that a page won&#39;t continue to exist after the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;unload&lt;/code&gt; event has fired. This presents a challenge because many of those pages were &lt;em&gt;also&lt;/em&gt; built with the assumption that the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;unload&lt;/code&gt; event would fire any time a user is navigating away, which is no longer true (and &lt;a href=&quot;https://developer.chrome.com/blog/page-lifecycle-api/#the-unload-event&quot;&gt;hasn&#39;t been true for a long time&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;So browsers are faced with a dilemma, they have to choose between something that can improve the user experience—but might also risk breaking the page.&lt;/p&gt;

&lt;p&gt;On desktop, Chrome and Firefox have chosen to make pages ineligible for bfcache if they add an &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;unload&lt;/code&gt; listener, which is less risky but also disqualifies &lt;em&gt;a lot&lt;/em&gt; of pages. Safari will attempt to cache some pages with an &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;unload&lt;/code&gt; event listener, but to reduce potential breakage it won&#39;t run the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;unload&lt;/code&gt; event when a user is navigating away, which makes the event very unreliable.&lt;/p&gt;

&lt;p&gt;On mobile, Chrome and Safari will attempt to cache pages with an &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;unload&lt;/code&gt; event listener since the risk of breakage is lower due to the fact that the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;unload&lt;/code&gt; event has always been extremely unreliable on mobile. Firefox treats pages that use &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;unload&lt;/code&gt; as ineligible for the bfcache, except on iOS, where all browsers use the WebKit rendering engine, and so behave like Safari.&lt;/p&gt;

&lt;p&gt;Instead of using the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;unload&lt;/code&gt; event, use the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;pagehide&lt;/code&gt; event. The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;pagehide&lt;/code&gt; event fires in all cases where the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;unload&lt;/code&gt; event fires, and it &lt;em&gt;also&lt;/em&gt; fires when a page is put in the bfcache.&lt;/p&gt;

&lt;p&gt;In fact, &lt;a href=&quot;https://developer.chrome.com/docs/lighthouse&quot;&gt;Lighthouse&lt;/a&gt; has a &lt;a href=&quot;https://github.com/GoogleChrome/lighthouse/pull/11085&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;no-unload-listeners&lt;/code&gt; audit&lt;/a&gt;, which will warn developers if any JavaScript on their pages (including that from third-party libraries) adds an &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;unload&lt;/code&gt; event listener.&lt;/p&gt;

&lt;aside class=&quot;warning&quot;&gt;&lt;b&gt;Warning: &lt;/b&gt; Never add an &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;unload&lt;/code&gt; event listener! Use the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;pagehide&lt;/code&gt; event instead. Adding an &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;unload&lt;/code&gt; event listener will make your site slower in Firefox, and the code won&#39;t even run most of the time in Chrome and Safari. &lt;/aside&gt;

&lt;p&gt;Due to its unreliability, and the performance impact for bfcache, Chrome is looking to &lt;a href=&quot;https://developer.chrome.com/articles/deprecating-unload&quot;&gt;deprecate the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;unload&lt;/code&gt; event&lt;/a&gt;.&lt;/p&gt;

&lt;h4 id=&quot;permissions-policy&quot; data-text=&quot;Use Permission Policy to prevent unload handlers being used on a page&quot; tabindex=&quot;-1&quot;&gt;Use Permission Policy to prevent unload handlers being used on a page&lt;/h4&gt;

&lt;p&gt;Sites that don&#39;t use &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;unload&lt;/code&gt; event handlers can ensure these are not added by using a &lt;a href=&quot;https://developer.chrome.com/blog/deprecating-unload/#permissions-policy&quot;&gt;Permissions Policy&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;Bash Session&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-go&quot;&gt;Permissions-Policy: unload=()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;This also prevents third parties or extensions from slowing the site down by adding unload handlers and making the site ineligible for the bfcache.&lt;/p&gt;

&lt;h4 id=&quot;beforeunload-caution&quot; data-text=&quot;Only add beforeunload listeners conditionally&quot; tabindex=&quot;-1&quot;&gt;Only add &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;beforeunload&lt;/code&gt; listeners conditionally&lt;/h4&gt;

&lt;p&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;beforeunload&lt;/code&gt; event won&#39;t make your pages ineligible for bfcache in modern browsers&#39; bfcache but previously it did and it is still unreliable, so avoid using it unless absolutely necessary.&lt;/p&gt;

&lt;p&gt;Unlike the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;unload&lt;/code&gt; event, however, there are legitimate uses for
&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;beforeunload&lt;/code&gt;. For example, when you want to warn the user that they have
unsaved changes they&#39;ll lose if they leave the page. In this case, it&#39;s
recommended that you only add &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;beforeunload&lt;/code&gt; listeners when a user has unsaved
changes and then remove them immediately after the unsaved changes are saved.&lt;/p&gt;

&lt;div class=&quot;wd-compare&quot;&gt;
  &lt;div class=&quot;compare-worse&quot;&gt;Don&#39;t&lt;/div&gt;
  &lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JavaScript&quot;&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;beforeunload&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;pageHasUnsavedChanges&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;())&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;preventDefault&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;returnValue&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;Are you sure you want to exit?&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;});&lt;/span&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;span class=&quot;wd-caption&quot;&gt;
This code adds a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;beforeunload&lt;/code&gt; listener unconditionally.
&lt;/span&gt;
&lt;/div&gt;

&lt;div class=&quot;wd-compare&quot;&gt;
  &lt;div class=&quot;compare-better&quot;&gt;Do&lt;/div&gt;
  &lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JavaScript&quot;&gt;&lt;span class=&quot;devsite-syntax-kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;beforeUnloadListener&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;preventDefault&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;returnValue&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;Are you sure you want to exit?&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-c1&quot;&gt;// A function that invokes a callback when the page has unsaved changes.&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-nx&quot;&gt;onPageHasUnsavedChanges&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(()&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;beforeunload&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;beforeUnloadListener&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;});&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-c1&quot;&gt;// A function that invokes a callback when the page&#39;s unsaved changes are resolved.&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-nx&quot;&gt;onAllChangesSaved&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(()&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;removeEventListener&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;beforeunload&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;beforeUnloadListener&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;});&lt;/span&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;span class=&quot;wd-caption&quot;&gt;
This code only adds the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;beforeunload&lt;/code&gt; listener when it&#39;s needed (and
removes it when it&#39;s not).
&lt;/span&gt;
&lt;/div&gt;

&lt;h3 id=&quot;minimize-no-store&quot; data-text=&quot;Minimize use of Cache-Control: no-store&quot; tabindex=&quot;-1&quot;&gt;Minimize use of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;Cache-Control: no-store&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;Cache-Control: no-store&lt;/code&gt; is an HTTP header web servers can set on responses that instructs the browser not to store the response in any HTTP cache. It&#39;s used for resources containing sensitive user information, such as pages behind a login.&lt;/p&gt;

&lt;p&gt;Although bfcache is not an HTTP cache, historically, when &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;Cache-Control: no-store&lt;/code&gt; is set on the page resource itself (as opposed to any subresource), browsers have chosen not to store the page in bfcache so any pages using &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;Cache-Control: no-store&lt;/code&gt; may not be eligible for bfcache. There is &lt;a href=&quot;https://developer.chrome.com/docs/web-platform/bfcache-ccns&quot;&gt;work underway to change this behavior for Chrome&lt;/a&gt; in a privacy-preserving manner.&lt;/p&gt;

&lt;p&gt;Since &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;Cache-Control: no-store&lt;/code&gt; restricts a page&#39;s eligibility for bfcache, it should only be set on pages that contain sensitive information where caching of any sort is never appropriate.&lt;/p&gt;

&lt;p&gt;For pages that need to always serve up-to-date content—and that content does not contain sensitive information—use &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;Cache-Control: no-cache&lt;/code&gt; or &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;Cache-Control: max-age=0&lt;/code&gt;. These directives instruct the browser to revalidate the content before serving it, and they don&#39;t affect a page&#39;s bfcache eligibility.&lt;/p&gt;

&lt;p&gt;Note that when a page is restored from bfcache, it is restored from memory, not from the HTTP cache. As a result, directives like &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;Cache-Control: no-cache&lt;/code&gt; or &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;Cache-Control: max-age=0&lt;/code&gt; are not taken into account, and no revalidation occurs before the content is displayed to the user.&lt;/p&gt;

&lt;p&gt;This is still likely a better user experience, however, as bfcache restores are instant and—since pages don&#39;t stay in the bfcache for very long—it&#39;s unlikely that the content is out of date. However, if your content does change minute-by-minute, you can fetch any updates using the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;pageshow&lt;/code&gt; event, as outlined in the next section.&lt;/p&gt;

&lt;h3 id=&quot;update-data-after-restore&quot; data-text=&quot;Update stale or sensitive data after bfcache restore&quot; tabindex=&quot;-1&quot;&gt;Update stale or sensitive data after bfcache restore&lt;/h3&gt;

&lt;p&gt;If your site keeps user state—especially any sensitive user information—that data needs to be updated or cleared after a page is restored from bfcache.&lt;/p&gt;

&lt;p&gt;For example, if a user navigates to a checkout page and then updates their shopping cart, a back navigation could potentially expose out-of-date information if a stale page is restored from bfcache.&lt;/p&gt;

&lt;p&gt;Another, more critical example is if a user signs out of a site on a public computer and the next user clicks the back button. This could potentially expose private data that the user assumed was cleared when they logged out.&lt;/p&gt;

&lt;p&gt;To avoid situations like this, it&#39;s good to always update the page after a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;pageshow&lt;/code&gt; event if &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;event.persisted&lt;/code&gt; is &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;true&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JavaScript&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;pageshow&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&amp;gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;persisted&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;// Do any checks and updates to the page&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;While ideally you would update the content in place, for some changes you may want to force a full reload. The following code checks for the presence of a site-specific cookie in the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;pageshow&lt;/code&gt; event and reloads if the cookie is not found:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JavaScript&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;pageshow&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&amp;gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;persisted&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &amp;amp;&amp;amp; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;cookie&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;/my-cookie)) {&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;// Force a reload if the user has logged out.&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;location&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;reload&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;A reload has the advantage that will still preserve the history (to allow forward navigations), but a redirect may be more appropriate in some cases.&lt;/p&gt;

&lt;h3 id=&quot;ads-restore&quot; data-text=&quot;Ads and bfcache restore&quot; tabindex=&quot;-1&quot;&gt;Ads and bfcache restore&lt;/h3&gt;

&lt;p&gt;It may be tempting to try to avoid the use of bfcache to serve a new set of ads on each back/forward navigation. However, as well as having a performance impact, it is questionable whether such behavior leads to better ad engagement. Users may have noticed an ad they intended to return to click but by reloading rather than restoring from the bfcache they not be able to. Testing this scenario—ideally with an A/B test—is important before making assumptions.&lt;/p&gt;

&lt;p&gt;For sites that do want to refresh ads on bfcache restore, then refreshing just the ads on the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;pageshow&lt;/code&gt; event when &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;event.persisted&lt;/code&gt; is &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;true&lt;/code&gt; allows this to happen without impacting the page performance. Check with your ad provider but &lt;a href=&quot;https://developers.google.com/publisher-tag/samples/refresh&quot;&gt;here is one example on how to do this with Google Publishing Tag&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;avoid-window-opener&quot; data-text=&quot;Avoid window.opener references&quot; tabindex=&quot;-1&quot;&gt;Avoid &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;window.opener&lt;/code&gt; references&lt;/h3&gt;

&lt;p&gt;In older browsers, if a page was opened using &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Window/open&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;window.open()&lt;/code&gt;&lt;/a&gt; from a link with &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTML/Element/a#target&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;target=_blank&lt;/code&gt;&lt;/a&gt;, without specifying &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTML/Link_types/noopener&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;rel=&quot;noopener&quot;&lt;/code&gt;&lt;/a&gt;, the opening page would have a reference to the window object of the opened page.&lt;/p&gt;

&lt;p&gt;In addition to &lt;a href=&quot;https://mathiasbynens.github.io/rel-noopener/&quot;&gt;being a security risk&lt;/a&gt;, a page with a non-null &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Window/opener&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;window.opener&lt;/code&gt;&lt;/a&gt; reference can&#39;t safely be put into bfcache, because that could break any pages attempting to access it.&lt;/p&gt;

&lt;p&gt;As a result, it&#39;s best to avoid creating &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;window.opener&lt;/code&gt; references. You can do this by using &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;rel=&quot;noopener&quot;&lt;/code&gt; whenever possible (note, this is now the default in all modern browsers). If your site requires opening a window and controlling it through &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Window/postMessage&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;window.postMessage()&lt;/code&gt;&lt;/a&gt; or directly referencing the window object, neither the opened window nor the opener will be eligible for the bfcache.&lt;/p&gt;

&lt;h3 id=&quot;close-open-connections&quot; data-text=&quot;Close open connections before the user navigates away&quot; tabindex=&quot;-1&quot;&gt;Close open connections before the user navigates away&lt;/h3&gt;

&lt;p&gt;As mentioned previously, when a page is held in the bfcache, it pauses all scheduled JavaScript tasks and resumes them when the page is taken out of the cache.&lt;/p&gt;

&lt;p&gt;If these scheduled JavaScript tasks are only accessing DOM APIs—or other APIs isolated to just the current page—then pausing these tasks while the page is not visible to the user is not going to cause any problems.&lt;/p&gt;

&lt;p&gt;However, if these tasks are connected to APIs that are also accessible from other pages in the same origin (for example: IndexedDB, Web Locks, WebSockets) this can be problematic because pausing these tasks may prevent code in other tabs from running.&lt;/p&gt;

&lt;p&gt;As a result, some browsers won&#39;t attempt to put a page in bfcache in the following scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pages with an open &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/IDBOpenDBRequest&quot;&gt;IndexedDB connection&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Pages with in-progress &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Fetch_API&quot;&gt;fetch()&lt;/a&gt; or &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/XMLHttpRequest&quot;&gt;XMLHttpRequest&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Pages with an open &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/WebSocket&quot;&gt;WebSocket&lt;/a&gt; or &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/WebRTC_API&quot;&gt;WebRTC&lt;/a&gt; connection. Chrome (as of 149) and Safari do no block on open WebSockets but other browsers do.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your page is using any of these APIs, we strongly recommend closing connections and removing or disconnecting observers during the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;pagehide&lt;/code&gt; or &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;freeze&lt;/code&gt; event. That allows the browser to safely cache the page without the risk of affecting other open tabs.&lt;/p&gt;

&lt;p&gt;Then, if the page is restored from the bfcache, you can reopen or reconnect to those APIs during the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;pageshow&lt;/code&gt; or &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;resume&lt;/code&gt; event, or have them always automatically reopen using the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;error&lt;/code&gt; or &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;close&lt;/code&gt; events. Ensure you do no open multiple connections if using multiple events.&lt;/p&gt;

&lt;p&gt;The following example shows how to ensure that pages using IndexedDB are eligible for bfcache by closing an open connection in the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;pagehide&lt;/code&gt; event listener:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JavaScript&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-kd&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;dbPromise&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;openDB&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;dbPromise&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;dbPromise&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-ow&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&amp;gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;indexedDB&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;my-db&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;onupgradeneeded&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&amp;gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;createObjectStore&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;keyval&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;onerror&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&amp;gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;onsuccess&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&amp;gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;dbPromise&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-c1&quot;&gt;// Close the connection to the database when the user leaves.&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;pagehide&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&amp;gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;dbPromise&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;dbPromise&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&amp;gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;());&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;dbPromise&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;});&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-c1&quot;&gt;// Open the connection when the page is loaded or restored from bfcache.&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;pageshow&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&amp;gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;openDB&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;h3 id=&quot;test&quot; data-text=&quot;Test to ensure your pages are cacheable&quot; tabindex=&quot;-1&quot;&gt;Test to ensure your pages are cacheable&lt;/h3&gt;

&lt;p&gt;Chrome DevTools can help you test your pages to ensure they&#39;re optimized for bfcache, and identify any issues that might prevent them from being eligible.&lt;/p&gt;

&lt;p&gt;To test a page:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Navigate to the page in Chrome.&lt;/li&gt;
&lt;li&gt;In DevTools, go to &lt;strong&gt;Application&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Back-forward Cache&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click the &lt;strong&gt;Run Test&lt;/strong&gt; button. DevTools then tries to navigate away and back
to determine whether the page can be restored from bfcache.&lt;/li&gt;
&lt;/ol&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/bfcache/image/back-forward-cache-panel-fc27405c6cd76.png&quot; alt=&quot;Back-forward cache panel in DevTools&quot; width=&quot;800&quot; height=&quot;313&quot; srcset=&quot;https://web.dev/static/articles/bfcache/image/back-forward-cache-panel-fc27405c6cd76_36.png 36w,https://web.dev/static/articles/bfcache/image/back-forward-cache-panel-fc27405c6cd76_48.png 48w,https://web.dev/static/articles/bfcache/image/back-forward-cache-panel-fc27405c6cd76_72.png 72w,https://web.dev/static/articles/bfcache/image/back-forward-cache-panel-fc27405c6cd76_96.png 96w,https://web.dev/static/articles/bfcache/image/back-forward-cache-panel-fc27405c6cd76_480.png 480w,https://web.dev/static/articles/bfcache/image/back-forward-cache-panel-fc27405c6cd76_720.png 720w,https://web.dev/static/articles/bfcache/image/back-forward-cache-panel-fc27405c6cd76_856.png 856w,https://web.dev/static/articles/bfcache/image/back-forward-cache-panel-fc27405c6cd76_960.png 960w,https://web.dev/static/articles/bfcache/image/back-forward-cache-panel-fc27405c6cd76_1440.png 1440w,https://web.dev/static/articles/bfcache/image/back-forward-cache-panel-fc27405c6cd76_1920.png 1920w,https://web.dev/static/articles/bfcache/image/back-forward-cache-panel-fc27405c6cd76_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption&gt;The &lt;strong&gt;Back-forward Cache&lt;/strong&gt; panel in DevTools.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;If the test is successful, the panel reports &quot;Restored from back-forward cache&quot;.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/bfcache/image/devtools-reporting-page-1dd392594f6b2.png&quot; alt=&quot;DevTools reporting a page was successfully restored from bfcache&quot; width=&quot;800&quot; height=&quot;313&quot; srcset=&quot;https://web.dev/static/articles/bfcache/image/devtools-reporting-page-1dd392594f6b2_36.png 36w,https://web.dev/static/articles/bfcache/image/devtools-reporting-page-1dd392594f6b2_48.png 48w,https://web.dev/static/articles/bfcache/image/devtools-reporting-page-1dd392594f6b2_72.png 72w,https://web.dev/static/articles/bfcache/image/devtools-reporting-page-1dd392594f6b2_96.png 96w,https://web.dev/static/articles/bfcache/image/devtools-reporting-page-1dd392594f6b2_480.png 480w,https://web.dev/static/articles/bfcache/image/devtools-reporting-page-1dd392594f6b2_720.png 720w,https://web.dev/static/articles/bfcache/image/devtools-reporting-page-1dd392594f6b2_856.png 856w,https://web.dev/static/articles/bfcache/image/devtools-reporting-page-1dd392594f6b2_960.png 960w,https://web.dev/static/articles/bfcache/image/devtools-reporting-page-1dd392594f6b2_1440.png 1440w,https://web.dev/static/articles/bfcache/image/devtools-reporting-page-1dd392594f6b2_1920.png 1920w,https://web.dev/static/articles/bfcache/image/devtools-reporting-page-1dd392594f6b2_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption&gt;A successfully restored page.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;If it&#39;s unsuccessful, the panel indicates the reason why. If the reason is something you can address as a developer, the panel marks it as &lt;strong&gt;Actionable&lt;/strong&gt;.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/bfcache/image/devtools-reporting-failur-e962032a9c1e2.png&quot; alt=&quot;DevTools reporting failure to restore a page from bfcache&quot; width=&quot;800&quot; height=&quot;313&quot; srcset=&quot;https://web.dev/static/articles/bfcache/image/devtools-reporting-failur-e962032a9c1e2_36.png 36w,https://web.dev/static/articles/bfcache/image/devtools-reporting-failur-e962032a9c1e2_48.png 48w,https://web.dev/static/articles/bfcache/image/devtools-reporting-failur-e962032a9c1e2_72.png 72w,https://web.dev/static/articles/bfcache/image/devtools-reporting-failur-e962032a9c1e2_96.png 96w,https://web.dev/static/articles/bfcache/image/devtools-reporting-failur-e962032a9c1e2_480.png 480w,https://web.dev/static/articles/bfcache/image/devtools-reporting-failur-e962032a9c1e2_720.png 720w,https://web.dev/static/articles/bfcache/image/devtools-reporting-failur-e962032a9c1e2_856.png 856w,https://web.dev/static/articles/bfcache/image/devtools-reporting-failur-e962032a9c1e2_960.png 960w,https://web.dev/static/articles/bfcache/image/devtools-reporting-failur-e962032a9c1e2_1440.png 1440w,https://web.dev/static/articles/bfcache/image/devtools-reporting-failur-e962032a9c1e2_1920.png 1920w,https://web.dev/static/articles/bfcache/image/devtools-reporting-failur-e962032a9c1e2_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption&gt;A failed bfcache test with an actionable result.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;In this example, the use of an &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;unload&lt;/code&gt; event listener makes the page &lt;a href=&quot;https://web.dev/articles/bfcache?hl=en#never-use-the-unload-event&quot;&gt;ineligible&lt;/a&gt; for bfcache. You can fix that by switching from &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;unload&lt;/code&gt; to using &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;pagehide&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;wd-compare&quot;&gt;
  &lt;div class=&quot;compare-better&quot;&gt;Do&lt;/div&gt;
  &lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JavaScript&quot;&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;pagehide&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;...);&lt;/span&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;/div&gt;

&lt;div class=&quot;wd-compare&quot;&gt;
  &lt;div class=&quot;compare-worse&quot;&gt;Don&#39;t&lt;/div&gt;
  &lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JavaScript&quot;&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;unload&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;...);&lt;/span&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;/div&gt;

&lt;p&gt;Lighthouse 10.0 also &lt;a href=&quot;https://developer.chrome.com/blog/lighthouse-10-0#bfcache&quot;&gt;added a bfcache audit&lt;/a&gt;, which performs a similar test. For more information, see the &lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/bf-cache&quot;&gt;bfcache audit&#39;s docs&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;analytics&quot; data-text=&quot;How bfcache affects analytics and performance measurement&quot; tabindex=&quot;-1&quot;&gt;How bfcache affects analytics and performance measurement&lt;/h2&gt;

&lt;p&gt;If you use an analytics tool to measure visits to your site, you might notice a decrease in the total number of pageviews reported as Chrome enables bfcache for more users.&lt;/p&gt;

&lt;p&gt;In fact, you&#39;re likely already underreporting pageviews from other browsers that implement bfcache, because many popular analytics libraries don&#39;t measure bfcache restores as new pageviews.&lt;/p&gt;

&lt;p&gt;To include bfcache restores in your pageview count, set listeners for the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;pageshow&lt;/code&gt; event and check the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;persisted&lt;/code&gt; property.&lt;/p&gt;

&lt;p&gt;The following example shows how to do this with Google Analytics. Other analytics tools likely use similar logic:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JavaScript&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;// Send a pageview when the page is first loaded.&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-c1&quot;&gt;// This happens by default just by loading gtag&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-nx&quot;&gt;gtag&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;config&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;TAG_ID&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;pageshow&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&amp;gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;// Send another pageview if the page is restored from bfcache.&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;persisted&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;gtag&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;event&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;page_view&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;h3 id=&quot;measure-bfcache-hit-ratio&quot; data-text=&quot;Measure your bfcache hit ratio&quot; tabindex=&quot;-1&quot;&gt;Measure your bfcache hit ratio&lt;/h3&gt;

&lt;p&gt;You may also want to measure whether the bfcache was used, to help identify pages that are not utilizing the bfcache. This can be done by measuring the navigation type for page loads:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JavaScript&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;// Send a navigation_type when the page is first loaded.&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-c1&quot;&gt;// To do this disable the default pageview so you can manually send it&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-c1&quot;&gt;// supplemented with the additional detail.&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-nx&quot;&gt;gtag&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;config&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;TAG_ID&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;send_page_view&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-nx&quot;&gt;gtag&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;event&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;page_view&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;   &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;navigation_type&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;performance&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;getEntriesByType&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;navigation&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;});&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;pageshow&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&amp;gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;persisted&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;// Send another pageview if the page is restored from bfcache.&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;gtag&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;event&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;page_view&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;navigation_type&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;back_forward_cache&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;Calculate your bfcache hit ratio using the counts for &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;back_forward&lt;/code&gt; navigations and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;back_forward_cache&lt;/code&gt; navigations.&lt;/p&gt;

&lt;p&gt;It is important to realize that there are a number of scenarios, outside of the site owners control, when a Back/Forward navigation won&#39;t use the bfcache, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;when the user quits the browser and starts it again&lt;/li&gt;
&lt;li&gt;when the user duplicates a tab&lt;/li&gt;
&lt;li&gt;when the user closes a tab and reopens it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In some of these cases the original navigation type may be preserved by some browsers and so may show a type of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;back_forward&lt;/code&gt; despite these not being Back/Forward navigations.&lt;/p&gt;

&lt;p&gt;Even without those exclusions the bfcache will be discarded after a period to conserve memory.&lt;/p&gt;

&lt;p&gt;So, website owners shouldn&#39;t be expecting a 100% bfcache hit ratio for all &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;back_forward&lt;/code&gt; navigations. However, measuring their ratio can be useful to identify pages where the page itself is preventing bfcache usage for a high proportion of back and forward navigations.&lt;/p&gt;

&lt;p&gt;The Chrome team has added the &lt;a href=&quot;https://developer.chrome.com/docs/web-platform/bfcache-notrestoredreasons&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;NotRestoredReasons&lt;/code&gt; API&lt;/a&gt; to help expose the reasons why pages don&#39;t use bfcache, so developers can improve their bfcache hit rates. The Chrome team has also &lt;a href=&quot;https://developer.chrome.com/blog/crux-navigation-types&quot;&gt;added navigation types to CrUX&lt;/a&gt; making it possible to see the number of bfcache navigations even without measuring it yourself.&lt;/p&gt;

&lt;h3 id=&quot;performance-measurement&quot; data-text=&quot;Performance measurement&quot; tabindex=&quot;-1&quot;&gt;Performance measurement&lt;/h3&gt;

&lt;p&gt;bfcache can also negatively affect performance metrics collected &lt;a href=&quot;https://web.dev/articles/user-centric-performance-metrics#in_the_field&quot;&gt;in the field&lt;/a&gt;, specifically metrics that measure page load times.&lt;/p&gt;

&lt;p&gt;Since bfcache navigations restore an existing page rather than initiate a new page load, the total number of page loads collected will decrease when bfcache is enabled. What&#39;s critical, though, is that the page loads being replaced by bfcache restores would likely have been some of the fastest page loads in your dataset. This is because back and forward navigations, by definition, are repeat visits, and repeat page loads are generally faster than page loads from first time visitors (due to &lt;a href=&quot;https://web.dev/articles/http-cache&quot;&gt;HTTP caching&lt;/a&gt;, as mentioned earlier).&lt;/p&gt;

&lt;p&gt;The result is fewer fast page loads in your dataset, which will likely skew the distribution slower—despite the fact that the performance experienced by the user has probably improved!&lt;/p&gt;

&lt;p&gt;There are a few ways to deal with this issue. One is to annotate all page load metrics with their respective &lt;a href=&quot;https://www.w3.org/TR/navigation-timing-2/#sec-performance-navigation-types&quot;&gt;navigation type&lt;/a&gt;: &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;navigate&lt;/code&gt;, &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;reload&lt;/code&gt;, &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;back_forward&lt;/code&gt;, or &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;prerender&lt;/code&gt;. This lets you continue to monitor your performance within these navigation types, even if the overall distribution skews negative. We recommend this approach for non-user-centric page load metrics like &lt;a href=&quot;https://web.dev/articles/ttfb&quot;&gt;Time to First Byte (TTFB)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For user-centric metrics like the &lt;a href=&quot;https://web.dev/articles/vitals&quot;&gt;Core Web Vitals&lt;/a&gt;, a better option is to report a value that more accurately represents what the user experiences.&lt;/p&gt;

&lt;aside class=&quot;caution&quot;&gt;&lt;b&gt;Caution: &lt;/b&gt; The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;back_forward&lt;/code&gt; navigation type in the &lt;a href=&quot;https://www.w3.org/TR/navigation-timing-2/#sec-performance-navigation-types&quot;&gt;Navigation Timing API&lt;/a&gt; is not to be confused with bfcache restores. The Navigation Timing API only annotates page loads, whereas bfcache restores are re-using a page loaded from a previous navigation. &lt;/aside&gt;

&lt;h3 id=&quot;core-web-vitals&quot; data-text=&quot;Impact on Core Web Vitals&quot; tabindex=&quot;-1&quot;&gt;Impact on Core Web Vitals&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://web.dev/articles/vitals&quot;&gt;Core Web Vitals&lt;/a&gt; measure the user&#39;s experience of a web page across a variety of dimensions (loading speed, interactivity, visual stability), and since users experience bfcache restores as faster navigations than full page loads, it&#39;s important that the Core Web Vitals metrics reflect this. After all, a user doesn&#39;t care whether or not bfcache was enabled, they just care that the navigation was fast!&lt;/p&gt;

&lt;p&gt;Tools that collect and report on the Core Web Vitals metrics, like the &lt;a href=&quot;https://developer.chrome.com/docs/crux&quot;&gt;Chrome User Experience Report&lt;/a&gt;, treat bfcache restores as separate page visits in their dataset. And while there aren&#39;t dedicated web performance APIs for measuring these metrics after bfcache restores, you can approximate their values using existing web APIs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For &lt;a href=&quot;https://web.dev/articles/lcp&quot;&gt;Largest Contentful Paint (LCP)&lt;/a&gt;, use the delta between the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;pageshow&lt;/code&gt; event&#39;s timestamp and the timestamp of the next painted frame, because all elements in the frame will be painted at the same time. In the case of a bfcache restore, LCP and FCP are the same.&lt;/li&gt;
&lt;li&gt;For &lt;a href=&quot;https://web.dev/articles/inp&quot;&gt;Interaction to Next Paint (INP)&lt;/a&gt;, keep using your existing Performance Observer, but reset the current INP value to 0.&lt;/li&gt;
&lt;li&gt;For &lt;a href=&quot;https://web.dev/articles/cls&quot;&gt;Cumulative Layout Shift (CLS)&lt;/a&gt;, keep using your existing Performance Observer, but reset the current CLS value to 0.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For more details on how bfcache affects each metric, see the individual Core Web Vitals &lt;a href=&quot;https://web.dev/articles/vitals#core_web_vitals&quot;&gt;metric guides pages&lt;/a&gt;. For a specific example of how to implement bfcache versions of these metrics, refer to the &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals/pull/87&quot;&gt;PR adding them to the web-vitals JS library&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals&quot;&gt;web-vitals&lt;/a&gt; JavaScript library &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals/pull/87&quot;&gt;supports bfcache restores&lt;/a&gt; in the metrics it reports.&lt;/p&gt;

&lt;h2 id=&quot;additional_resources&quot; data-text=&quot;Additional Resources&quot; tabindex=&quot;-1&quot;&gt;Additional Resources&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/Firefox/Releases/1.5/Using_Firefox_1.5_caching&quot;&gt;Firefox Caching&lt;/a&gt;
&lt;em&gt;(bfcache in Firefox)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://webkit.org/blog/427/webkit-page-cache-i-the-basics/&quot;&gt;Page Cache&lt;/a&gt;
&lt;em&gt;(bfcache in Safari)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.google.com/document/d/1JtDCN9A_1UBlDuwkjn1HWxdhQ1H2un9K4kyPLgBqJUc/edit?usp=sharing&quot;&gt;Back/forward cache: web exposed behavior&lt;/a&gt;
&lt;em&gt;(bfcache differences across browsers)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://back-forward-cache-tester.glitch.me/?persistent_logs=1&quot;&gt;bfcache tester&lt;/a&gt;
&lt;em&gt;(test how different APIs and events affect bfcache in browsers)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.smashingmagazine.com/2022/05/performance-game-changer-back-forward-cache/&quot;&gt;Performance Game Changer: Browser Back/Forward Cache&lt;/a&gt;
&lt;em&gt;(a case study from Smashing Magazine showing dramatic Core Web Vitals improvements by enabling bfcache)&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

  

  
</description><link>https://web.dev/articles/bfcache?hl=en</link><guid isPermaLink="false">https://web.dev/articles/bfcache?hl=en</guid><pubDate>Thu, 02 Jul 2026 07:00:00 GMT</pubDate></item><item><title>Lazy loading video</title><description>&lt;p&gt;&lt;style&gt;
  .wd-author__links {
    display: flex;
  }
  &lt;/style&gt;

&lt;/p&gt;&lt;p&gt;&lt;/p&gt;

&lt;p&gt;
&lt;/p&gt;&lt;p class=&quot;wd-pubdates&quot; style=&quot;font-size: 90%; border-bottom: 1px solid var(--devsite-ref-palette--grey200);color: var(--devsite-ref-palette--blue-grey700);&quot;&gt;
  Published: August 16, 2019, Last updated: July 2, 2026
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;As with &lt;a href=&quot;https://web.dev/articles/lazy-loading-images&quot;&gt;image elements&lt;/a&gt;, you may also want to lazy-load video. Videos are commonly loaded with the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;video&amp;gt;&lt;/code&gt; element, though for videos hosted on other services such as YouTube they may use &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt;s (in which case check out the article in &lt;a href=&quot;https://web.dev/articles/iframe-lazy-loading&quot;&gt;lazy-loading iframes&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;How to lazy-load &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;video&amp;gt;&lt;/code&gt; depends on the use case, as there are a couple of different solutions.&lt;/p&gt;

&lt;h2 id=&quot;using_the_loading_attribute&quot; data-text=&quot;Using the loading attribute&quot; tabindex=&quot;-1&quot;&gt;Using the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;loading&lt;/code&gt; attribute&lt;/h2&gt;

&lt;p&gt;&lt;devsite-iframe&gt;&lt;iframe src=&quot;https://web.dev/frame/articles/lazy-loading-video/index_e9e8c9d93c8c900dcf9739c5623b534c69a5f80b9c1d0750b00f1c7d3c5511e5.frame&quot; class=&quot;framebox inherit-locale &quot; allow=&quot;clipboard-write https://web-dot-devsite-v2-prod-3p.appspot.com&quot; allowfullscreen=&quot;&quot; is-upgraded=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;&lt;/devsite-iframe&gt;&lt;/p&gt;

&lt;p&gt;Browsers have started to support the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;loading&lt;/code&gt; attribute on media elements (&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;video&amp;gt;&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;audio&amp;gt;&lt;/code&gt;). Where supported this means both the media and any poster image are automatically lazy-loaded just by adding the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;loading=&quot;lazy&quot;&lt;/code&gt; attribute:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;HTML&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;video controls loading=&quot;lazy&quot; preload=&quot;none&quot; poster=&quot;one-does-not-simply-placeholder.jpg&quot;&amp;gt;
  &amp;lt;source src=&quot;one-does-not-simply.webm&quot; type=&quot;video/webm&quot;&amp;gt;
  &amp;lt;source src=&quot;one-does-not-simply.mp4&quot; type=&quot;video/mp4&quot;&amp;gt;
&amp;lt;/video&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;Using &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;loading=&quot;lazy&quot;&lt;/code&gt; will prevent the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;poster&lt;/code&gt; image from loading at all, until in or near the viewport, and then will use the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;preload&lt;/code&gt; attribute to decide how much of the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;video&lt;/code&gt; to load. This also means &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;autoplay&lt;/code&gt; videos won&#39;t autoplay until in or near the viewport. The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;loading=&quot;lazy&quot;&lt;/code&gt; attribute works with &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;poster&lt;/code&gt;, &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;preload&lt;/code&gt;, and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;autoplay&lt;/code&gt; attributes and does not replace the need for them.&lt;/p&gt;

&lt;p&gt;Browsers that don&#39;t support &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;loading&lt;/code&gt; attribute on media elements will ignore this attribute, which will result in the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;poster&lt;/code&gt; image loading immediately, and the video loading based on the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;preload&lt;/code&gt; attribute, which we will cover next.&lt;/p&gt;

&lt;h2 id=&quot;video-no-autoplay&quot; data-text=&quot;Use the preload attribute to lazy-load video that doesn&#39;t autoplay&quot; tabindex=&quot;-1&quot;&gt;Use the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;preload&lt;/code&gt; attribute to lazy-load video that doesn&#39;t autoplay&lt;/h2&gt;

&lt;p&gt;Avoiding autoplaying videos is usually best practice as it leaves the control with the user. In these cases specifying the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTML/Element/video#attr-preload&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;preload&lt;/code&gt; attribute&lt;/a&gt; on the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;video&amp;gt;&lt;/code&gt; element is the best way to avoid loading the whole video until needed:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;HTML&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;video controls loading=&quot;lazy&quot; preload=&quot;none&quot; poster=&quot;one-does-not-simply-placeholder.jpg&quot;&amp;gt;
  &amp;lt;source src=&quot;one-does-not-simply.webm&quot; type=&quot;video/webm&quot;&amp;gt;
  &amp;lt;source src=&quot;one-does-not-simply.mp4&quot; type=&quot;video/mp4&quot;&amp;gt;
&amp;lt;/video&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;The previous example uses a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;preload&lt;/code&gt; attribute with a value of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;none&lt;/code&gt; to prevent browsers from preloading &lt;em&gt;any&lt;/em&gt; video data, even when in the viewport. The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;poster&lt;/code&gt; attribute gives the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;video&amp;gt;&lt;/code&gt; element a placeholder that will occupy the space while the video loads.&lt;/p&gt;

&lt;p&gt;In most browsers &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;preload&lt;/code&gt; defaults to &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;metadata&lt;/code&gt; and a portion of the video is preloaded using the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;Content-Range&lt;/code&gt; header. This can result in more data being downloaded than desired—browsers cannot know what bytes the metadata is stored at, and it may not be stored at the beginning of the file. Therefore, the best chance of avoiding loading the video is with using &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;preload=&quot;none&quot;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This can be further enhanced to preload the metadata when the user hovers the video with an &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;onmouseenter&lt;/code&gt; attribute (or with the equivalent &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;mouseenter&lt;/code&gt; event handler):&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;HTML&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;video controls
  loading=&quot;lazy&quot;
  preload=&quot;none&quot;
  poster=&quot;one-does-not-simply-placeholder.jpg&quot;
  onmouseenter=&quot;event.target.setAttribute(&#39;preload&#39;,&#39;metadata&#39;)&quot;&amp;gt;
  &amp;lt;source src=&quot;one-does-not-simply.webm&quot; type=&quot;video/webm&quot;&amp;gt;
  &amp;lt;source src=&quot;one-does-not-simply.mp4&quot; type=&quot;video/mp4&quot;&amp;gt;
&amp;lt;/video&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;This not only reduces the delay when the user goes to play the video, but also shows the duration of the video as soon as they.&lt;/p&gt;

&lt;p&gt;Videos can qualify as &lt;a href=&quot;https://web.dev/articles/lcp#what-elements-are-considered%22&quot;&gt;LCP candidates&lt;/a&gt;. A &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;poster&lt;/code&gt; image will be quicker to load than the video so where this is an LCP candidate, you should use a poster image, but also &lt;a href=&quot;https://web.dev/articles/preload-critical-assets&quot;&gt;preload it&lt;/a&gt; with a &lt;a href=&quot;https://web.dev/articles/fetch-priority#the_fetchpriority_attribute&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;fetchpriority&lt;/code&gt; attribute value of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&quot;high&quot;&lt;/code&gt;&lt;/a&gt;, since &lt;a href=&quot;https://github.com/whatwg/html/issues/10378&quot;&gt;at the time of writing you cannot specify the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;fetchpriority&lt;/code&gt; for poster attributes&lt;/a&gt;:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;HTML&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;link rel=&quot;preload&quot; href=&quot;one-does-not-simply-placeholder.jpg&quot; as=&quot;image&quot; fetchpriority=&quot;high&quot;&amp;gt;
&amp;lt;video controls preload=&quot;none&quot;
  poster=&quot;one-does-not-simply-placeholder.jpg&quot;
  onmouseenter=&quot;event.target.setAttribute(&#39;preload&#39;,&#39;metadata&#39;)&quot;&amp;gt;
  &amp;lt;source src=&quot;one-does-not-simply.webm&quot; type=&quot;video/webm&quot;&amp;gt;
  &amp;lt;source src=&quot;one-does-not-simply.mp4&quot; type=&quot;video/mp4&quot;&amp;gt;
&amp;lt;/video&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;Videos that are LCP elements shouldn&#39;t use &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;loading=&quot;lazy&quot;&lt;/code&gt;—in the same way that &lt;a href=&quot;https://web.dev/articles/browser-level-image-lazy-loading#eager-load-first-images&quot;&gt;you shouldn&#39;t lazy load LCP images &lt;/a&gt; videos.&lt;/p&gt;

&lt;h2 id=&quot;video-gif-replacement&quot; data-text=&quot;For video acting as an animated GIF replacement&quot; tabindex=&quot;-1&quot;&gt;For video acting as an animated GIF replacement&lt;/h2&gt;

&lt;p&gt;Autoplaying videos are commonly used for GIF-style quick animations. While animated GIFs enjoy wide use, they&#39;re subpar to video equivalents in a number of ways, particularly in file size. Animated GIFs can stretch into the range of several megabytes of data. Videos of similar visual quality tend to be far smaller.&lt;/p&gt;

&lt;p&gt;Using the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;video&amp;gt;&lt;/code&gt; element as a replacement for animated GIF is not as straightforward as the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;img&amp;gt;&lt;/code&gt; element. Animated GIFs have three characteristics:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;They play automatically when loaded.&lt;/li&gt;
&lt;li&gt;They loop continuously (&lt;a href=&quot;https://davidwalsh.name/prevent-gif-loop&quot;&gt;though that&#39;s not always the case&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;They don&#39;t have an audio track.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Achieving this with the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;video&amp;gt;&lt;/code&gt; element looks something like this:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;HTML&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;video autoplay muted loop playsinline&amp;gt;
  &amp;lt;source src=&quot;one-does-not-simply.webm&quot; type=&quot;video/webm&quot;&amp;gt;
  &amp;lt;source src=&quot;one-does-not-simply.mp4&quot; type=&quot;video/mp4&quot;&amp;gt;
&amp;lt;/video&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;loading=&quot;lazy&quot;&lt;/code&gt; attribute avoids loading the video until near the viewport. The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;autoplay&lt;/code&gt;, &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;muted&lt;/code&gt;, and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;loop&lt;/code&gt; attributes are self-explanatory. &lt;a href=&quot;https://webkit.org/blog/6784/new-video-policies-for-ios/&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;playsinline&lt;/code&gt; is necessary for autoplaying to occur in iOS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now you have a serviceable video-as-GIF replacement that works across platforms. But how to go about lazy loading it?&lt;/p&gt;

&lt;p&gt;For browsers that support this the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;loading=&quot;lazy&quot;&lt;/code&gt; is the easiest option:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;HTML&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;video loading=&quot;lazy&quot; autoplay muted loop playsinline&amp;gt;
  &amp;lt;source src=&quot;one-does-not-simply.webm&quot; type=&quot;video/webm&quot;&amp;gt;
  &amp;lt;source src=&quot;one-does-not-simply.mp4&quot; type=&quot;video/mp4&quot;&amp;gt;
&amp;lt;/video&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;To support lazy-loading of video in other browsers you need to use JavaScript and modify your &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;video&amp;gt;&lt;/code&gt; markup accordingly:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;HTML&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;video class=&quot;lazy&quot; preload=&quot;none&quot; controls muted loop playsinline width=&quot;610&quot; height=&quot;254&quot; poster=&quot;one-does-not-simply.jpg&quot;&amp;gt;
  &amp;lt;source src=&quot;one-does-not-simply.webm&quot; type=&quot;video/webm&quot;&amp;gt;
  &amp;lt;source src=&quot;one-does-not-simply.mp4&quot; type=&quot;video/mp4&quot;&amp;gt;
&amp;lt;/video&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;You&#39;ll notice the removal of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;autoplay&lt;/code&gt;, and the addition of the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;class=&quot;lazy&quot;&lt;/code&gt;, &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;preload=&quot;none&quot;&lt;/code&gt; and &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTML/Element/video#attr-poster&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;poster&lt;/code&gt;&lt;/a&gt; attributes, which lets you specify a placeholder to occupy the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;video&amp;gt;&lt;/code&gt; element&#39;s space until the video is lazy-loaded. Then, we use a JavaScript Intersection Observer to re-enabled the autoplaying:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JavaScript&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;DOMContentLoaded&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kd&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;lazyVideos&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;[].&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;querySelectorAll&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;video.lazy&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;));&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;IntersectionObserver&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-ow&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kd&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;lazyVideoObserver&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-ow&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;IntersectionObserver&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;entries&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;observer&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;entries&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;video&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;video&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;isIntersecting&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;          &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;video&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;removeAttribute&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;preload&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;          &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;video&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;removeAttribute&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;controls&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;          &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;video&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;autoplay&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;          &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;video&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;classList&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;lazy&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;          &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;lazyVideoObserver&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;unobserve&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;video&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;});&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;lazyVideos&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;lazyVideo&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;lazyVideoObserver&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;lazyVideo&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;Using this method, the video does not autoplay by default and only when the intersection observer detects the video as entering the viewport. Additionally, the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;controls&lt;/code&gt; attribute handles the cases when the JavaScript fails. Together this means you have a video solution that emulates animated GIF behavior, but doesn&#39;t incur the same intensive data usage as animated GIFs do, and you can lazy-load that content.&lt;/p&gt;

&lt;h2 id=&quot;libraries&quot; data-text=&quot;Lazy loading libraries&quot; tabindex=&quot;-1&quot;&gt;Lazy loading libraries&lt;/h2&gt;

&lt;p&gt;The following libraries can help you to lazy-load video:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/verlok/vanilla-lazyload&quot;&gt;vanilla-lazyload&lt;/a&gt; and &lt;a href=&quot;https://github.com/ApoorvSaxena/lozad.js&quot;&gt;lozad.js&lt;/a&gt; are super lightweight options that use Intersection Observer only. As such, they are highly performant, but will need to be polyfilled before you can use them on older browsers.&lt;/li&gt;
&lt;li&gt;If you need a React-specific lazy loading library, you might consider &lt;a href=&quot;https://github.com/jasonslyvia/react-lazyload&quot;&gt;react-lazyload&lt;/a&gt;. While it doesn&#39;t use Intersection Observer, it &lt;em&gt;does&lt;/em&gt; provide a familiar method of lazy loading images for those accustomed to developing applications with React.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these lazy loading libraries is well documented, with plenty of markup patterns for your various lazy loading endeavors.&lt;/p&gt;

&lt;p&gt;As the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;loading&lt;/code&gt; attribute for &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;video&amp;gt;&lt;/code&gt; becomes available in more browsers we expect JavaScript solutions (including libraries) to be used less in favor of the built-in solutions, except for those that want more control than that allows.&lt;/p&gt;
  

  
</description><link>https://web.dev/articles/lazy-loading-video?hl=en</link><guid isPermaLink="false">https://web.dev/articles/lazy-loading-video?hl=en</guid><pubDate>Thu, 02 Jul 2026 07:00:00 GMT</pubDate></item><item><title>Rendering on the Web</title><description>&lt;p&gt;&lt;style&gt;
  .wd-author__links {
    display: flex;
  }
  &lt;/style&gt;

&lt;/p&gt;&lt;p&gt;&lt;/p&gt;

&lt;p&gt;
&lt;/p&gt;&lt;p class=&quot;wd-pubdates&quot; style=&quot;font-size: 90%; border-bottom: 1px solid var(--devsite-ref-palette--grey200);color: var(--devsite-ref-palette--blue-grey700);&quot;&gt;
  Published: February 6, 2019, Last updated: January 5, 2026
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;One of the core decisions web developers must make is where to implement logic
and rendering in their application. This can be difficult because there are so
many ways to build a website.&lt;/p&gt;

&lt;p&gt;Our understanding of this space is informed by our work in Chrome talking to
large sites over the past few years. Broadly speaking, we encourage developers
to consider server-side rendering or static rendering over a full rehydration
approach.&lt;/p&gt;

&lt;p&gt;To better understand the architectures we&#39;re choosing from when we make this
decision, we need &lt;a href=&quot;https://web.dev/articles/rendering-on-the-web?hl=en#terminology&quot;&gt;consistent terminology&lt;/a&gt; and a shared framework
for each approach. Then, you can better evaluate the tradeoffs of each rendering
approach from the perspective of page performance.&lt;/p&gt;

&lt;h2 id=&quot;terminology&quot; data-text=&quot;Terminology&quot; tabindex=&quot;-1&quot;&gt;Terminology&lt;/h2&gt;

&lt;p&gt;First, we define some terminology we will use.&lt;/p&gt;

&lt;h3 id=&quot;rendering-terminology&quot; data-text=&quot;Rendering&quot; tabindex=&quot;-1&quot;&gt;Rendering&lt;/h3&gt;

&lt;dl&gt;
&lt;dt&gt;Server-side rendering (SSR)&lt;/dt&gt;
&lt;dd&gt;Rendering an app on the server to send HTML, rather than JavaScript, to the client.&lt;/dd&gt;
&lt;dt&gt;Client-side rendering (CSR)&lt;/dt&gt;
&lt;dd&gt;Rendering an app in a browser, using JavaScript to modify the DOM.&lt;/dd&gt;
&lt;dt&gt;Prerendering&lt;/dt&gt;
&lt;dd&gt;Running a client-side application at build time to capture its initial state as static HTML. &lt;em&gt;Note &quot;prerendering&quot; in this sense is different to &lt;a href=&quot;https://developer.chrome.com/docs/web-platform/prerender-pages&quot;&gt;browser prerendering of future navigations&lt;/a&gt;.&lt;/em&gt;&lt;/dd&gt;
&lt;dt&gt;Hydration&lt;/dt&gt;
&lt;dd&gt;Running client-side scripts to add application state and interactivity to
server-rendered HTML. Hydration assumes the DOM doesn&#39;t change.&lt;/dd&gt;
&lt;dt&gt;Rehydration&lt;/dt&gt;
&lt;dd&gt;While often used to mean the same thing as hydration, rehydration implies
regularly updating the DOM with the latest state, including after the initial
hydration.&lt;/dd&gt;
&lt;/dl&gt;

&lt;h3 id=&quot;performance-terminology&quot; data-text=&quot;Performance&quot; tabindex=&quot;-1&quot;&gt;Performance&lt;/h3&gt;

&lt;dl&gt;
&lt;dt&gt;&lt;a href=&quot;https://web.dev/articles/ttfb&quot;&gt;Time to First Byte (TTFB)&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;The time between clicking a link and the first byte of content loading on the
new page.&lt;/dd&gt;
&lt;dt&gt;&lt;a href=&quot;https://web.dev/articles/fcp&quot;&gt;First Contentful Paint (FCP)&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;The time when requested content (article body, etc) becomes visible.&lt;/dd&gt;
&lt;dt&gt;&lt;a href=&quot;https://web.dev/articles/inp&quot;&gt;Interaction to Next Paint (INP)&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;A representative metric that assesses whether a page consistently responds
quickly to user inputs.&lt;/dd&gt;
&lt;dt&gt;&lt;a href=&quot;https://web.dev/articles/tbt&quot;&gt;Total Blocking Time (TBT)&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;A &lt;a href=&quot;https://almanac.httparchive.org/en/2022/performance#inp-and-tbt&quot;&gt;proxy metric for INP&lt;/a&gt;
that calculates how long the main thread was blocked during page load.&lt;/dd&gt;
&lt;/dl&gt;

&lt;h2 id=&quot;server-side&quot; data-text=&quot;Server-side rendering&quot; tabindex=&quot;-1&quot;&gt;Server-side rendering&lt;/h2&gt;

&lt;p&gt;Server-side rendering generates the full HTML for a page on the server in
response to navigation. This avoids additional round trips for data fetching and
templating on the client, because the renderer handles them before the browser
gets a response.&lt;/p&gt;

&lt;p&gt;Server-side rendering generally produces a fast FCP. Running page logic and
rendering on the server lets you avoid sending lots of JavaScript to the client.
This helps to reduce a page&#39;s TTBT, which can also lead to a lower INP, because
the main thread isn&#39;t blocked as often during page load. When the main thread is
blocked less often, user interactions have more opportunities to run sooner.&lt;/p&gt;

&lt;p&gt;This makes sense, because with server-side rendering, you&#39;re really just sending
text and links to the user&#39;s browser. This approach can work well for a variety
of device and network conditions and opens up interesting browser optimizations,
such as streaming document parsing.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/rendering-on-the-web/image/diagram-showing-server-si-4c081d80ab5fe.png&quot; alt=&quot;Diagram
    showing server-side rendering and JavaScript execution affecting FCP and TTI.&quot; width=&quot;800&quot; height=&quot;494&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption&gt;FCP and TTI with server-side rendering.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;With server-side rendering, users are less likely to be left waiting for
CPU-bound JavaScript to run before they can use your site. Even when you can&#39;t
avoid &lt;a href=&quot;https://web.dev/articles/optimizing-content-efficiency-loading-third-party-javascript&quot;&gt;third-party JavaScript&lt;/a&gt;,
using server-side rendering to reduce your own first-party
&lt;a href=&quot;https://medium.com/@addyosmani/the-cost-of-javascript-in-2018-7d8950fbb5d4&quot;&gt;JavaScript costs&lt;/a&gt;
can give you more &lt;a href=&quot;https://medium.com/@addyosmani/start-performance-budgeting-dabde04cf6a3&quot;&gt;budget&lt;/a&gt;
for the rest. However, there is one potential trade-off with this approach:
generating pages on the server takes time, which can increase your page&#39;s TTFB.&lt;/p&gt;

&lt;p&gt;Whether server-side rendering is enough for your application largely depends on
what type of experience you&#39;re building. There&#39;s a long-standing debate over the
correct applications of server-side rendering versus client-side rendering, but
you can always choose to use server-side rendering for some pages and not
others. Some sites have adopted hybrid rendering techniques with success.
For example, &lt;a href=&quot;https://medium.com/dev-channel/a-netflix-web-performance-case-study-c0bcde26a9d9&quot;&gt;Netflix&lt;/a&gt;
server-renders its relatively static landing pages, while
&lt;a href=&quot;https://dev.to/addyosmani/speed-up-next-page-navigations-with-prefetching-4285&quot;&gt;prefetching&lt;/a&gt;
the JavaScript for interaction-heavy pages, giving these heavier client-rendered pages a
better chance of loading quickly.&lt;/p&gt;

&lt;p&gt;With many modern frameworks, libraries, and architectures, you can render the same
application on both the client and the server. You can use these techniques for
server-side rendering. However, architectures where rendering happens both on
the server &lt;em&gt;and&lt;/em&gt; on the client are their own class of solution with very
different performance characteristics and tradeoffs. React users can use
&lt;a href=&quot;https://react.dev/reference/react-dom/server&quot;&gt;server DOM APIs&lt;/a&gt; or solutions
built on them like &lt;a href=&quot;https://nextjs.org/&quot;&gt;Next.js&lt;/a&gt; for server-side rendering. Vue
users can use Vue&#39;s &lt;a href=&quot;https://vuejs.org/guide/scaling-up/ssr.html&quot;&gt;server-side rendering guide&lt;/a&gt; or &lt;a href=&quot;https://nuxtjs.org/&quot;&gt;Nuxt&lt;/a&gt;. Angular has &lt;a href=&quot;https://angular.io/guide/universal&quot;&gt;Universal&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Most popular solutions use some form of hydration, though, so be aware of the
approaches your tool uses.&lt;/p&gt;

&lt;h2 id=&quot;static&quot; data-text=&quot;Static rendering&quot; tabindex=&quot;-1&quot;&gt;Static rendering&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://frontarm.com/james-k-nelson/static-vs-server-rendering/&quot;&gt;Static rendering&lt;/a&gt;
happens at build time. This approach offers a fast FCP, and also a lower TBT and
INP, as long as you limit the amount of client-side JavaScript on your pages. Unlike
server-side rendering, it also achieves a consistently fast TTFB, because the
HTML for a page doesn&#39;t have to be dynamically generated on the server.
Generally, static rendering means producing a separate HTML file for each URL
ahead of time. With HTML responses generated in advance, you can deploy static
renders to multiple CDNs to take advantage of edge caching.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/rendering-on-the-web/image/diagram-showing-static-re-0659f8679c321.png&quot; alt=&quot;Diagram
    showing static rendering and optional JavaScript execution affecting FCP and TTI.&quot; width=&quot;800&quot; height=&quot;613&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption&gt;FCP and TTI with static rendering.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Solutions for static rendering come in all shapes and sizes. Tools like
&lt;a href=&quot;https://www.gatsbyjs.org/&quot;&gt;Gatsby&lt;/a&gt; are designed to make developers feel like
their application is being rendered dynamically, not generated as a build step.
Static site generation tools such as &lt;a href=&quot;https://www.11ty.dev/&quot;&gt;11ty&lt;/a&gt;,
&lt;a href=&quot;https://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt;, and &lt;a href=&quot;https://metalsmith.iooperationalizing-node-js-for-server-side-rendering-c5ba718acfc9speedier-server-side-rendering-in-react-16-with-component-caching-e8aa677929b1/&quot;&gt;Metalsmith&lt;/a&gt;
embrace their static nature, providing a more template-driven approach.&lt;/p&gt;
&lt;aside class=&quot;note&quot;&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;span&gt; Abstractions of popular frameworks such as Next.js or Nuxt offer both
static rendering and server-side rendering. This lets developers opt into static
rendering for pages that qualify, or use server-side rendering for pages that
need to be dynamically generated in response to a request.&lt;/span&gt;&lt;/aside&gt;
&lt;p&gt;One of the downsides to static rendering is that it must generate individual
HTML files for every possible URL. This can be challenging or even infeasible
when you need to predict those URLs ahead of time and for sites with
a large number of unique pages.&lt;/p&gt;

&lt;p&gt;React users might be familiar with Gatsby,
&lt;a href=&quot;https://nextjs.org/learn/excel/static-html-export/&quot;&gt;Next.js static export&lt;/a&gt;, or
&lt;a href=&quot;https://frontarm.com/navi/&quot;&gt;Navi&lt;/a&gt;, all of which make it convenient to create
pages from components. However, static rendering and prerendering behave
differently: statically rendered pages are interactive without needing to
execute much client-side JavaScript, whereas prerendering improves the FCP of a
Single Page Application that must be booted on the client to make pages truly
interactive.
&lt;/p&gt;

&lt;p&gt;If you&#39;re unsure whether a given solution is static rendering or prerendering,
try disabling JavaScript and load the page you want to test. For statically
rendered pages, most interactive features still exist without JavaScript.
Prerendered pages might still have some basic features like links with
JavaScript disabled, but most of the page is inert.&lt;/p&gt;

&lt;p&gt;Another useful test is to use
&lt;a href=&quot;https://developer.chrome.com/docs/devtools/device-mode/#network&quot;&gt;network throttling in Chrome DevTools&lt;/a&gt;
and see how much JavaScript downloads before a page becomes interactive.
Prerendering generally needs more JavaScript to become interactive, and that
JavaScript tends to be more complex than the
&lt;a href=&quot;https://developer.mozilla.org/docs/Glossary/Progressive_Enhancement&quot;&gt;progressive enhancement&lt;/a&gt;
approach used in static rendering.&lt;/p&gt;

&lt;h2 id=&quot;server-side-versus-static&quot; data-text=&quot;Server-side rendering versus static rendering&quot; tabindex=&quot;-1&quot;&gt;Server-side rendering versus static rendering&lt;/h2&gt;

&lt;p&gt;Server-side rendering isn&#39;t the best solution for everything, because its
dynamic nature can have significant compute overhead costs. Many server-side
rendering solutions don&#39;t flush early, delay TTFB, or double the data being sent
(for example, inlined states used by JavaScript on the client). In React,
&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;renderToString()&lt;/code&gt; can be slow because it&#39;s synchronous and single-threaded.
&lt;a href=&quot;https://react.dev/reference/react-dom/server&quot;&gt;Newer React server DOM APIs&lt;/a&gt;
support streaming, which can get the initial part of an HTML response to the
browser sooner while the rest of it is still being generated on the server.&lt;/p&gt;

&lt;p&gt;Getting server-side rendering &quot;right&quot; can involve finding or building a solution
for &lt;a href=&quot;https://medium.com/@reactcomponentcaching/&quot;&gt;component caching&lt;/a&gt;, managing
memory consumption, using &lt;a href=&quot;https://speakerdeck.com/maxnajim/hastening-react-ssr-with-component-memoization-and-templatization&quot;&gt;memoization&lt;/a&gt; techniques,
and other concerns. You&#39;re often processing or rebuilding the same app twice,
once on the client and once on the server. Server-side rendering showing content
sooner doesn&#39;t necessarily give you less work to do. If you have a lot of work
on the client after a server-generated HTML response arrives on the client,
this can still lead to higher TBT and INP for your website.&lt;/p&gt;

&lt;p&gt;Server-side rendering produces HTML on demand for each URL, but it can be slower
than just serving static rendered content. If you can put in the additional
legwork, server-side rendering plus &lt;a href=&quot;https://freecontent.manning.com/caching-in-react/&quot;&gt;HTML caching&lt;/a&gt;
can significantly reduce server render time. The upside to server-side rendering
is the ability to pull more &quot;live&quot; data and respond to a more complete set of
requests than is possible with static rendering. Pages that need personalization
are a concrete example of the type of request that doesn&#39;t work well with static
rendering.&lt;/p&gt;

&lt;p&gt;Server-side rendering can also present interesting decisions when building a
&lt;a href=&quot;https://web.dev/explore/progressive-web-apps&quot;&gt;PWA&lt;/a&gt;. Is it better to use full-page
&lt;a href=&quot;https://developer.chrome.com/docs/workbox/service-worker-overview&quot;&gt;service worker&lt;/a&gt;
caching, or server-render individual pieces of content?&lt;/p&gt;

&lt;h2 id=&quot;client-side&quot; data-text=&quot;Client-side rendering&quot; tabindex=&quot;-1&quot;&gt;Client-side rendering&lt;/h2&gt;

&lt;p&gt;Client-side rendering means rendering pages directly in the browser with
JavaScript. All logic, data fetching, templating, and routing are handled on the
client instead of on the server. The effective outcome is that more data is
passed to the user&#39;s device from the server, and that comes with its own set of
tradeoffs.&lt;/p&gt;

&lt;p&gt;Client-side rendering can be difficult to make and keep fast for mobile devices.
With a little work to keep a &lt;a href=&quot;https://mobile.twitter.com/HenrikJoreteg/status/1039744716210950144&quot;&gt;tight JavaScript budget&lt;/a&gt;
and deliver value in as few &lt;a href=&quot;https://en.wikipedia.org/wiki/Round-trip_delay_time&quot;&gt;round-trips&lt;/a&gt;
as possible, you can get client-side rendering to almost replicate
the performance of pure server-side rendering. You can get the parser to work
for you faster by delivering critical scripts and data using &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;link rel=preload&amp;gt;&lt;/code&gt;
We also recommend considering using patterns like &lt;a href=&quot;https://web.dev/articles/apply-instant-loading-with-prpl&quot;&gt;PRPL&lt;/a&gt;
to ensure that initial and subsequent navigations feel instant.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/rendering-on-the-web/image/diagram-showing-client-si-42200b67e9867.png&quot; alt=&quot;Diagram
    showing client-side rendering affecting FCP and TTI.&quot; width=&quot;800&quot; height=&quot;322&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption&gt;FCP and TTI with client-side rendering.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The primary downside to client-side rendering is that the amount of JavaScript
required tends to grow as an application grows, which can impact a page&#39;s INP.
This becomes especially difficult with the addition of new JavaScript libraries,
polyfills, and third-party code, which compete for processing power and must
often be processed before a page&#39;s content can render.&lt;/p&gt;

&lt;p&gt;Experiences that use client-side rendering and rely on large JavaScript bundles
should consider &lt;a href=&quot;https://web.dev/articles/reduce-javascript-payloads-with-code-splitting&quot;&gt;aggressive code-splitting&lt;/a&gt;
to lower TBT and INP during page load, as well as lazy-loading JavaScript to
serve only what the user needs, when it&#39;s needed. For experiences with little or
no interactivity, server-side rendering can represent a more scalable solution
to these issues.&lt;/p&gt;

&lt;p&gt;For folks building single page applications, identifying core parts of the user
interface shared by most pages lets you apply the
&lt;a href=&quot;https://developer.chrome.com/blog/app-shell&quot;&gt;application shell caching&lt;/a&gt;
technique. Combined with service workers, this can dramatically improve
perceived performance on repeat visits, because the page can load its
application shell HTML and dependencies from &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;CacheStorage&lt;/code&gt; very quickly.&lt;/p&gt;

&lt;h2 id=&quot;rehydration&quot; data-text=&quot;Rehydration combines server-side and client-side rendering&quot; tabindex=&quot;-1&quot;&gt;Rehydration combines server-side and client-side rendering&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://react.dev/reference/react-dom/client/hydrateRoot#hydrating-server-rendered-html&quot;&gt;Hydration&lt;/a&gt;
is an approach that mitigate the tradeoffs between client-side and server-side
rendering by doing both. Navigation requests, like full page loads or
reloads, are handled by a server that renders the application to HTML. Then the
JavaScript and data used for rendering is embedded into the resulting document.
When done carefully, this achieves a fast FCP like server-side rendering, then
&quot;picks up&quot; by rendering again on the client.&lt;/p&gt;

&lt;p&gt;This is an effective solution, but it can have considerable performance
drawbacks.&lt;/p&gt;

&lt;p&gt;The primary downside of server-side rendering with rehydration is that it can
have a significant negative impact on TBT and INP, even if it improves FCP.
Server-side rendered pages can appear to be loaded and interactive, but can&#39;t
actually respond to input until the client-side scripts for components are
executed and event handlers have been attached. On mobile, this can take
minutes, confusing and frustrating the user.&lt;/p&gt;

&lt;h3 id=&quot;rehydration-duplication&quot; data-text=&quot;A rehydration problem: one app for the price of two&quot; tabindex=&quot;-1&quot;&gt;A rehydration problem: one app for the price of two&lt;/h3&gt;

&lt;p&gt;For the client-side JavaScript to accurately take over where the server left off,
without re-requesting all the data the server rendered its HTML with, most
server-side rendering solutions serialize the response from a UI&#39;s data
dependencies as script tags in the document. Because this duplicates a lot of
HTML, rehydration can cause more problems than just delayed interactivity.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/rendering-on-the-web/image/html-document-containing-94fb8c198eab8.png&quot; alt=&quot;&quot; width=&quot;800&quot; height=&quot;447&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption&gt;HTML document containing serialized UI, inlined data and a bundle.js script.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The server is returning a description of the application&#39;s UI in response to a
navigation request, but it&#39;s also returning the source data used to compose that
UI, and a complete copy of the UI&#39;s implementation which then boots up on the
client. The UI doesn&#39;t become interactive until after &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;bundle.js&lt;/code&gt; has finished
loading and executing.&lt;/p&gt;

&lt;p&gt;Performance metrics collected from real websites using server-side rendering and
rehydration indicate that it&#39;s rarely the best option. The most important reason
is its effect on the user experience, when a page looks ready but none of its
interactive features work.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/rendering-on-the-web/image/diagram-showing-client-re-be8ceca54b42e.png&quot; alt=&quot;&quot; width=&quot;800&quot; height=&quot;270&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption&gt;The negative effects of client-side rendering on TTI.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;There&#39;s hope for server-side rendering with rehydration. In the short
term, only using server-side rendering for highly cacheable content can reduce
TTFB, producing similar results to prerendering. Rehydrating
&lt;a href=&quot;https://www.emberjs.com/blog/2017/10/10/glimmer-progress-report.html&quot;&gt;incrementally&lt;/a&gt;,
progressively, or partially might be the key to making this technique more
viable in the future.&lt;/p&gt;

&lt;h2 id=&quot;stream&quot; data-text=&quot;Stream server-side rendering and rehydrate progressively&quot; tabindex=&quot;-1&quot;&gt;Stream server-side rendering and rehydrate progressively&lt;/h2&gt;

&lt;p&gt;Server-side rendering has had a number of developments over the last few years.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://mxstbr.com/thoughts/streaming-ssr&quot;&gt;Streaming server-side rendering&lt;/a&gt;
lets you send HTML in chunks that the browser can progressively render as it&#39;s
received. This can get markup to your users faster, speeding up your FCP. In
React, streams being asynchronous in &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;renderToPipeableStream()&lt;/code&gt;, compared to
synchronous &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;renderToString()&lt;/code&gt;, means backpressure is handled well.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.patterns.dev/react/progressive-hydration/&quot;&gt;Progressive rehydration&lt;/a&gt;
is also worth considering
(&lt;a href=&quot;https://github.com/facebook/react/pull/14717&quot;&gt;React has implemented it&lt;/a&gt;). With this
approach, individual pieces of a server-rendered application are &quot;booted up&quot;
over time, instead of the current common approach of initializing the entire
application at once. This can help reduce the amount of JavaScript needed to
make pages interactive, because it lets you defer client-side upgrading of
low-priority parts of the page to prevent it from blocking the main thread,
letting user interactions happen sooner after the user initiates them.&lt;/p&gt;

&lt;p&gt;Progressive rehydration can also help you avoid one of the most common
server-side rendering rehydration pitfalls: a server-rendered DOM tree gets
destroyed and then immediately rebuilt, most often because the initial
synchronous client-side render required data that wasn&#39;t quite ready, often a
&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;Promise&lt;/code&gt; that hasn&#39;t resolved yet.&lt;/p&gt;

&lt;h3 id=&quot;partial-rehydration&quot; data-text=&quot;Partial rehydration&quot; tabindex=&quot;-1&quot;&gt;Partial rehydration&lt;/h3&gt;

&lt;p&gt;Partial rehydration has proven difficult to implement. This approach is an
extension of progressive rehydration that analyzes individual pieces of the page
(components, views, or trees) and identifies the pieces with little
interactivity or no reactivity. For each of these mostly-static parts, the
corresponding JavaScript code is then transformed into inert references and
decorative features, reducing their client-side footprint to nearly zero.&lt;/p&gt;

&lt;p&gt;The partial rehydration approach comes with its own issues and compromises. It
poses some interesting challenges for caching, and client-side navigation means
we can&#39;t assume that server-rendered HTML for inert parts of the application are
available without a full page load.&lt;/p&gt;

&lt;h3 id=&quot;trisomorphic-rendering&quot; data-text=&quot;Trisomorphic rendering&quot; tabindex=&quot;-1&quot;&gt;Trisomorphic rendering&lt;/h3&gt;

&lt;p&gt;If &lt;a href=&quot;https://developer.chrome.com/docs/workbox/service-worker-overview&quot;&gt;service workers&lt;/a&gt;
are an option for you, consider &lt;em&gt;trisomorphic&lt;/em&gt; rendering. This technique
lets you use streaming server-side rendering for initial or non-JavaScript
navigations, and then have your service worker take on rendering of HTML for
navigations after it has been installed. This can keep cached components and
templates up to date and enable SPA-style navigations for rendering new views in
the same session. This approach works best when you can share the same
templating and routing code between the server, client page, and service worker.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/rendering-on-the-web/image/diagram-trisomorphic-rendering.png&quot; alt=&quot;&quot; width=&quot;800&quot; height=&quot;504&quot; srcset=&quot;https://web.dev/static/articles/rendering-on-the-web/image/diagram-trisomorphic-rendering_36.png 36w,https://web.dev/static/articles/rendering-on-the-web/image/diagram-trisomorphic-rendering_48.png 48w,https://web.dev/static/articles/rendering-on-the-web/image/diagram-trisomorphic-rendering_72.png 72w,https://web.dev/static/articles/rendering-on-the-web/image/diagram-trisomorphic-rendering_96.png 96w,https://web.dev/static/articles/rendering-on-the-web/image/diagram-trisomorphic-rendering_480.png 480w,https://web.dev/static/articles/rendering-on-the-web/image/diagram-trisomorphic-rendering_720.png 720w,https://web.dev/static/articles/rendering-on-the-web/image/diagram-trisomorphic-rendering_856.png 856w,https://web.dev/static/articles/rendering-on-the-web/image/diagram-trisomorphic-rendering_960.png 960w,https://web.dev/static/articles/rendering-on-the-web/image/diagram-trisomorphic-rendering_1440.png 1440w,https://web.dev/static/articles/rendering-on-the-web/image/diagram-trisomorphic-rendering_1920.png 1920w,https://web.dev/static/articles/rendering-on-the-web/image/diagram-trisomorphic-rendering_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption&gt;Trisomorphic rendering, demonstrating a browser and service worker communicating with the server.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;seo&quot; data-text=&quot;SEO considerations&quot; tabindex=&quot;-1&quot;&gt;SEO considerations&lt;/h2&gt;

&lt;p&gt;When choosing a web rendering strategy, teams often consider the impact of SEO.
Server-side rendering is a popular choice for delivering a &quot;complete looking&quot;
experience that crawlers can interpret. Crawlers &lt;a href=&quot;https://web.dev/articles/how-search-works&quot;&gt;can understand JavaScript&lt;/a&gt;,
but there are often &lt;a href=&quot;https://developers.google.com/search/docs/guides/rendering&quot;&gt;limitations&lt;/a&gt;
to how they render. Client-side rendering can work, but often needs additional
testing and overhead. More recently,
&lt;a href=&quot;https://developers.google.com/search/docs/advanced/javascript/dynamic-rendering&quot;&gt;dynamic rendering&lt;/a&gt;
has also become an option worth considering if your architecture depends heavily
on client-side JavaScript.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot; data-text=&quot;Conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;When deciding on an approach to rendering, measure and understand what your
bottlenecks are. Consider whether static rendering or server-side rendering can
get you most of the way there. It&#39;s fine to mostly ship HTML with minimal
JavaScript to get an experience interactive. Here&#39;s a handy infographic showing
the server-client spectrum:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/rendering-on-the-web/image/app-rendering-types-examples.png&quot; alt=&quot;&quot; width=&quot;800&quot; height=&quot;538&quot; srcset=&quot;https://web.dev/static/articles/rendering-on-the-web/image/app-rendering-types-examples_36.png 36w,https://web.dev/static/articles/rendering-on-the-web/image/app-rendering-types-examples_48.png 48w,https://web.dev/static/articles/rendering-on-the-web/image/app-rendering-types-examples_72.png 72w,https://web.dev/static/articles/rendering-on-the-web/image/app-rendering-types-examples_96.png 96w,https://web.dev/static/articles/rendering-on-the-web/image/app-rendering-types-examples_480.png 480w,https://web.dev/static/articles/rendering-on-the-web/image/app-rendering-types-examples_720.png 720w,https://web.dev/static/articles/rendering-on-the-web/image/app-rendering-types-examples_856.png 856w,https://web.dev/static/articles/rendering-on-the-web/image/app-rendering-types-examples_960.png 960w,https://web.dev/static/articles/rendering-on-the-web/image/app-rendering-types-examples_1440.png 1440w,https://web.dev/static/articles/rendering-on-the-web/image/app-rendering-types-examples_1920.png 1920w,https://web.dev/static/articles/rendering-on-the-web/image/app-rendering-types-examples_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption&gt;Rendering options and their tradeoffs.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;credits&quot; data-text=&quot;Credits&quot; tabindex=&quot;-1&quot;&gt;Credits&lt;/h2&gt;

&lt;p&gt;Thanks to everyone for their reviews and inspiration:&lt;/p&gt;

&lt;p&gt;Jeffrey Posnick, Houssein Djirdeh, Shubhie Panicker,
Chris Harrelson, and Sebastian Markbåge.&lt;/p&gt;

  

  
</description><link>https://web.dev/articles/rendering-on-the-web?hl=en</link><guid isPermaLink="false">https://web.dev/articles/rendering-on-the-web?hl=en</guid><pubDate>Mon, 05 Jan 2026 08:00:00 GMT</pubDate></item><item><title>Why is CrUX data different from my RUM data?</title><description>&lt;p&gt;Learn about reasons why RUM data can show different Core Web Vitals numbers from CrUX.&lt;/p&gt;

&lt;p&gt;&lt;style&gt;
  .wd-author__links {
    display: flex;
  }
  &lt;/style&gt;

&lt;/p&gt;&lt;p&gt;&lt;/p&gt;

&lt;p&gt;
&lt;/p&gt;&lt;p class=&quot;wd-pubdates&quot; style=&quot;font-size: 90%; border-bottom: 1px solid var(--devsite-ref-palette--grey200);color: var(--devsite-ref-palette--blue-grey700);&quot;&gt;
  Published: August 15, 2022, Last updated: December 17, 2025
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://developer.chrome.com/docs/crux&quot;&gt;Chrome User Experience Report (CrUX)&lt;/a&gt; provides user experience metrics for how real-world Chrome users experience popular destinations on the web. This data is automatically collected by Chrome from users who have opted in, and is made available based on the &lt;a href=&quot;https://developer.chrome.com/docs/crux/methodology#eligibility&quot;&gt;CrUX eligibility criteria&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;CrUX data is therefore available for millions of websites. Many site owners have not had access to field data before, and CrUX has enabled many sites to see the value of this for the first time. As a public dataset, CrUX can also be used for competitive analysis and benchmarking of user experience metrics.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Real_user_monitoring&quot;&gt;Real User Monitoring (RUM)&lt;/a&gt; is similar to CrUX, but instead of Chrome automatically collecting user-experience metrics, code is included on the websites to do this collection and feed it back to a RUM provider or analytics solution for further analysis.&lt;/p&gt;

&lt;p&gt;With both solutions measuring user experience metrics, it is natural to assume that they should be equivalent. It can be confusing when we see differences. This guide will explain why that can happen, and offers suggestions for what to do when the numbers don&#39;t align.&lt;/p&gt;

&lt;h2 id=&quot;benefits_of_supplementing_crux_with_a_rum_solution&quot; data-text=&quot;Benefits of supplementing CrUX with a RUM solution&quot; tabindex=&quot;-1&quot;&gt;Benefits of supplementing CrUX with a RUM solution&lt;/h2&gt;

&lt;p&gt;CrUX is a great tool for a consistent view across sites and—as &lt;a href=&quot;https://developer.chrome.com/docs/crux&quot;&gt;the Google dataset for the Core Web Vitals program&lt;/a&gt;—sites will likely want to keep an eye on what it is showing. The aim of CrUX is to provide a statistically relevant overview of millions of websites for cross comparison.&lt;/p&gt;

&lt;p&gt;However, for a deeper dive in investigating &lt;em&gt;why&lt;/em&gt; data is showing the numbers it is, investing in a full RUM solution to supplement CrUX can give you access to more detailed information than can be made available in a publicly queryable dataset. This can help you explain and improve your metrics in a number of ways.&lt;/p&gt;

&lt;h3 id=&quot;deeper_analysis_to_investigate_issues&quot; data-text=&quot;Deeper analysis to investigate issues&quot; tabindex=&quot;-1&quot;&gt;Deeper analysis to investigate issues&lt;/h3&gt;

&lt;p&gt;CrUX can often be used to point out if you have a problem on your site, but not necessarily exactly where on your site the issue lies nor why. RUM solutions—whether home-grown through &lt;a href=&quot;https://web.dev/articles/debug-performance-in-the-field&quot;&gt;the likes of the web-vitals library&lt;/a&gt; or some of the many commercial products—can help bridge that gap.&lt;/p&gt;

&lt;p&gt;Using a RUM solution gives you access to much more fine-grained data for all your pages, and on all browsers. It also lets you segment and analyze this data in ways CrUX does not, enabling you to drill down and investigate problem areas of the site. Are they affected by a particular segment of users? Or users who take certain actions? Exactly when did the problem start? These are questions that are much easier to answer with the additional data that a RUM tool can provide.&lt;/p&gt;

&lt;h3 id=&quot;correlate_with_other_business_metrics&quot; data-text=&quot;Correlate with other business metrics&quot; tabindex=&quot;-1&quot;&gt;Correlate with other business metrics&lt;/h3&gt;

&lt;p&gt;RUM also lets you compare your web performance metrics directly with any business metrics, showing the value in investing in performance, and what other performance work to prioritize. We have numerous &lt;a href=&quot;https://web.dev/case-studies&quot;&gt;case studies&lt;/a&gt; with businesses doing this correlation, such as &lt;a href=&quot;https://web.dev/case-studies/farfetch#step_1_defining_measuring_and_monitoring_metrics&quot;&gt;Farfetch&lt;/a&gt; or &lt;a href=&quot;https://web.dev/case-studies/economic-times-cwv&quot;&gt;The Economic Times&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;collect_other_performance_data&quot; data-text=&quot;Collect other performance data&quot; tabindex=&quot;-1&quot;&gt;Collect other performance data&lt;/h3&gt;

&lt;p&gt;A RUM solution allows collection of other custom metrics, tied directly to your specific business. One of the more well-known examples is Twitter&#39;s &quot;&lt;a href=&quot;https://blog.twitter.com/engineering/en_us/a/2012/improving-performance-on-twittercom#:%7E:text=The%20most%20important%20metric%20we,Tweet%20on%20each%20page&#39;s%20timeline.&quot;&gt;Time to first Tweet&lt;/a&gt;&quot; metric. These site-specific measures can then be correlated with Core Web Vital improvements and business metrics.&lt;/p&gt;

&lt;h2 id=&quot;differences_between_two_sets_of_field_data&quot; data-text=&quot;Differences between two sets of field data&quot; tabindex=&quot;-1&quot;&gt;Differences between two sets of field data&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;A man with a watch knows what time it is. A man with two watches is never sure.&lt;/p&gt;
  
  &lt;cite&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Segal%27s_law&quot;&gt;Segal&#39;s Law&lt;/a&gt;&lt;/cite&gt;
&lt;/blockquote&gt;

&lt;p&gt;Whenever you have two sources of data, it can often be confusing and frustrating as to why they differ. In much the same way as it&#39;s important to understand the difference between &lt;a href=&quot;https://web.dev/articles/lab-and-field-data-differences&quot;&gt;lab and field metrics&lt;/a&gt;, there can also be differences between two sources of &lt;em&gt;field&lt;/em&gt; data. While the data would be the same in an ideal world, there are many reasons why they can differ.&lt;/p&gt;

&lt;h3 id=&quot;lab_data_versus_field_data&quot; data-text=&quot;Lab data versus field data&quot; tabindex=&quot;-1&quot;&gt;Lab data versus field data&lt;/h3&gt;

&lt;p&gt;The first thing to check is whether you are looking at lab (synthetic) metrics or field (RUM) metrics. While it&#39;s natural to assume RUM products only look at field data, many offer a lab component as well.&lt;/p&gt;

&lt;p&gt;Lab data is incredibly useful precisely because of the fixed conditions it measures under. It can be used to monitor unexpected changes or regressions in a production environment without the noise of a changing field population. However, lab data may not be representative of the real user experience, so field metrics can show quite different results.&lt;/p&gt;

&lt;h3 id=&quot;populations&quot; data-text=&quot;Populations&quot; tabindex=&quot;-1&quot;&gt;Populations&lt;/h3&gt;

&lt;p&gt;The datasets used by CrUX and RUM solutions may be different due to differences in which page visits are being measured depending on which browsers, users, sites, and devices are being compared.&lt;/p&gt;

&lt;h4 id=&quot;included_browsers&quot; data-text=&quot;Included browsers&quot; tabindex=&quot;-1&quot;&gt;Included browsers&lt;/h4&gt;

&lt;p&gt;The Chrome User Experience Report, as its name suggests, is Chrome-only. While there are many Chromium-based browsers (Edge, Opera, and Brave to name a few) that shares a lot of the Chrome codebase, only Chrome users feed data into CrUX. This restriction also means Chrome users on iOS are not included, since it uses the underlying Webkit browser engine. Android WebViews also don&#39;t count as &quot;Chrome&quot;, so data from these users is not included—though &lt;a href=&quot;https://developer.chrome.com/docs/android/custom-tabs&quot;&gt;Chrome Custom Tabs&lt;/a&gt; are included.&lt;/p&gt;

&lt;p&gt;While Chrome is one of the world&#39;s most popular browsers—and therefore would likely give a broad representation of your site&#39;s performance in most cases—measuring just that browser is by no means a measure of all your users. This may explain one main difference between RUM and CrUX. This is particularly true for performance techniques that rely on APIs or image formats only available in Chrome, for example.&lt;/p&gt;

&lt;p&gt;The lack of iOS data can also lead to bias. For example, &lt;a href=&quot;https://infrequently.org/2021/03/the-performance-inequality-gap/&quot;&gt;as iOS users are typically on more performant devices&lt;/a&gt; or visiting from more countries with better network infrastructure, including them can lead to high overall performance metrics. On the other hand, &lt;em&gt;excluding&lt;/em&gt; them—as CrUX does—can lead to data that is skewed to the lower end of site visitors (&lt;a href=&quot;https://www.smashingmagazine.com/2021/12/core-web-vitals-case-study-smashing-magazine/&quot;&gt;example case study&lt;/a&gt;). Android users typically cover a wider range of devices, device capabilities, and markets.&lt;/p&gt;

&lt;p&gt;RUM solutions can get data for non-Chrome browsers, and in particular from Chromium-based browsers that often have the same metrics (such as Core Web Vitals) built-in. Non-Chromium-based browsers are also measured by RUM solutions, but may have a more limited set of metrics. For example, &lt;a href=&quot;https://web.dev/articles/cls&quot;&gt;Cumulative Layout Shift (CLS)&lt;/a&gt; is only available in Chromium-based browsers. Some other paint-based metrics like LCP, INP, and even &lt;a href=&quot;https://web.dev/articles/fcp&quot;&gt;First Contentful Paint (FCP)&lt;/a&gt; can be measured quite differently (see later).&lt;/p&gt;

&lt;h4 id=&quot;opted-in_users&quot; data-text=&quot;Opted-in users&quot; tabindex=&quot;-1&quot;&gt;Opted-in users&lt;/h4&gt;

&lt;p&gt;As well as being limited to Chrome users, CrUX is further restricted by only measuring &lt;a href=&quot;https://developer.chrome.com/docs/crux/methodology#user-eligibility&quot;&gt;a subset of Chrome users&lt;/a&gt; who have opted in to share CrUX data when the browser was installed.&lt;/p&gt;

&lt;p&gt;RUM providers also only look at a subset of users, usually due to cookie banner prompts—asking users to opt into RUM data collection—or tracking blockers. This can adversely affect some initial page loads if the confirmation is not given until the second or subsequent page, when some of the site assets have already been cached from previous pages. If this happens frequently, the metrics may appear more favorable in RUM than they actually are if slower initial page loads are excluded in a sufficient number of cases.&lt;/p&gt;

&lt;h4 id=&quot;included_sites&quot; data-text=&quot;Included sites&quot; tabindex=&quot;-1&quot;&gt;Included sites&lt;/h4&gt;

&lt;p&gt;CrUX is only intended to report on public websites, so there are other &lt;a href=&quot;https://developer.chrome.com/docs/crux/methodology#eligibility&quot;&gt;eligibility criteria&lt;/a&gt; that may result in data not being logged in CrUX. Most notable of these criteria is that the website must be publicly discoverable and sufficiently popular to ensure a minimal sample size from which to draw meaningful conclusions. In most cases, this will result in no data being available in CrUX. This is less of a confusing difference compared to the data being available but different, but explains why that happens.&lt;/p&gt;

&lt;p&gt;However, if specific pages of a site are marked as indexable but others are not, then you may only see a subset of URLs in CrUX. If the origin is publicly discoverable, then all page views within that origin will be included in the origin-level data, but URL-level data may not be available.&lt;/p&gt;

&lt;h4 id=&quot;devices&quot; data-text=&quot;Devices&quot; tabindex=&quot;-1&quot;&gt;Devices&lt;/h4&gt;

&lt;p&gt;CrUX segments data by mobile, desktop, and tablet - though many tools concentrate on the first two and may not expose tablet data, or may include it within mobile or desktop. The performance characteristics on mobile versus desktop can be quite different—both in terms of the content delivered, and in the capabilities of the devices viewing them.&lt;/p&gt;

&lt;p&gt;RUM data will allow segmenting traffic similarly, but often shows consolidated data by default. RUM may only allow segmenting by device type (for example, mobile) or browser (for example, Chrome), but not both to only see mobile Chrome traffic. When comparing to CrUX data, ensure you are comparing like-for-like by filtering by device type &lt;em&gt;and&lt;/em&gt; the Chrome browser.&lt;/p&gt;

&lt;h4 id=&quot;sampling&quot; data-text=&quot;Sampling&quot; tabindex=&quot;-1&quot;&gt;Sampling&lt;/h4&gt;

&lt;p&gt;RUM solutions typically allow adjusting the sampling rate of opted-in visitors where data is collected. This can be used to reduce the volume of data needed to be analyzed, and to reduce costs of commercial RUM services. If that sample size is too small and not representative of the wider population, then the resulting metrics will also be similarly skewed. Discuss with your RUM provider the appropriate sampling size for your site.&lt;/p&gt;

&lt;h3 id=&quot;aggregation_of_data&quot; data-text=&quot;Aggregation of data&quot; tabindex=&quot;-1&quot;&gt;Aggregation of data&lt;/h3&gt;

&lt;p&gt;Field data by its very nature will include many, many data points of the same metrics compared to lab data, which will give a single value. If this data is aggregated differently for reporting, it can lead to another reason for differences between CrUX and RUM.&lt;/p&gt;

&lt;h4 id=&quot;timespan&quot; data-text=&quot;Timespan&quot; tabindex=&quot;-1&quot;&gt;Timespan&lt;/h4&gt;

&lt;p&gt;CrUX data is based on a 28 day sliding window of traffic, and it is not possible to change this timeframe—though the &lt;a href=&quot;https://developer.chrome.com/docs/crux/bigquery&quot;&gt;CrUX BigQuery data&lt;/a&gt; is stored for each month, allowing you to see previous months, and the &lt;a href=&quot;https://developer.chrome.com/docs/crux/history-api&quot;&gt;CrUX History API&lt;/a&gt; also gives historical data over a weekly period. Both still provide data based on the 28 day sliding window.&lt;/p&gt;

&lt;p&gt;RUM data typically allows much greater granularity to allow seeing the impact of changes much sooner. When choosing smaller periods, though, RUM data can be unduly impacted by fluctuations in website traffic and visitors. When comparing RUM data to CrUX data, always ensure you are looking at performance over 28 days. Once you are satisfied the data is similar, you can look at other time frames to drill into the RUM data.&lt;/p&gt;

&lt;h4 id=&quot;aggregation_of_statistics&quot; data-text=&quot;Aggregation of statistics&quot; tabindex=&quot;-1&quot;&gt;Aggregation of statistics&lt;/h4&gt;

&lt;p&gt;CrUX metrics are measured at the 75th percentile—that is, looking at the value that 75% of page views achieved. There will be extremes in field data and removing the worst 25% experiences, it is intended to give a value that the majority of visitors can reasonably be expected to achieve.&lt;/p&gt;

&lt;p&gt;RUM products often give a wider number of options of how to aggregate the metrics, including 75th percentile, median and other percentiles. If comparing RUM values to CrUX data, it is necessary to ensure you are looking at 75th percentile data to compare like-for-like.&lt;/p&gt;

&lt;p&gt;The histogram data in CrUX includes all available data—not just 75th percentile—and shows the number of page views in each rating, but the aggregate score will be based on 75th percentile. This CrUX data is surfaced in tools like &lt;a href=&quot;https://pagespeed.web.dev/&quot;&gt;PageSpeed Insights&lt;/a&gt;:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/crux-and-rum-differences/image/pagespeed-insights-screen-b135a0cfa5e39.png&quot; alt=&quot;PageSpeed Insights screenshot showing histograms of LCP rating page loads&quot; width=&quot;632&quot; height=&quot;428&quot; srcset=&quot;https://web.dev/static/articles/crux-and-rum-differences/image/pagespeed-insights-screen-b135a0cfa5e39_36.png 36w,https://web.dev/static/articles/crux-and-rum-differences/image/pagespeed-insights-screen-b135a0cfa5e39_48.png 48w,https://web.dev/static/articles/crux-and-rum-differences/image/pagespeed-insights-screen-b135a0cfa5e39_72.png 72w,https://web.dev/static/articles/crux-and-rum-differences/image/pagespeed-insights-screen-b135a0cfa5e39_96.png 96w,https://web.dev/static/articles/crux-and-rum-differences/image/pagespeed-insights-screen-b135a0cfa5e39_480.png 480w,https://web.dev/static/articles/crux-and-rum-differences/image/pagespeed-insights-screen-b135a0cfa5e39_720.png 720w,https://web.dev/static/articles/crux-and-rum-differences/image/pagespeed-insights-screen-b135a0cfa5e39_856.png 856w,https://web.dev/static/articles/crux-and-rum-differences/image/pagespeed-insights-screen-b135a0cfa5e39_960.png 960w,https://web.dev/static/articles/crux-and-rum-differences/image/pagespeed-insights-screen-b135a0cfa5e39_1440.png 1440w,https://web.dev/static/articles/crux-and-rum-differences/image/pagespeed-insights-screen-b135a0cfa5e39_1920.png 1920w,https://web.dev/static/articles/crux-and-rum-differences/image/pagespeed-insights-screen-b135a0cfa5e39_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption&gt;PageSpeed Insights shows CrUX 75th percentile and histogram data&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&quot;differences_in_metrics&quot; data-text=&quot;Differences in metrics&quot; tabindex=&quot;-1&quot;&gt;Differences in metrics&lt;/h3&gt;

&lt;p&gt;There are many metrics used to measure web performance, so when comparing two different sets of data, it&#39;s important to understand what metrics are being measured, and how those metrics are being used.&lt;/p&gt;

&lt;h4 id=&quot;metrics_measured&quot; data-text=&quot;Metrics measured&quot; tabindex=&quot;-1&quot;&gt;Metrics measured&lt;/h4&gt;

&lt;p&gt;CrUX data is the Google dataset of the &lt;a href=&quot;https://web.dev/articles/vitals&quot;&gt;Core Web Vitals&lt;/a&gt; initiative and primarily measures these metrics (&lt;a href=&quot;https://web.dev/articles/lcp&quot;&gt;LCP&lt;/a&gt;, &lt;a href=&quot;https://web.dev/articles/cls&quot;&gt;CLS&lt;/a&gt;, and &lt;a href=&quot;https://web.dev/articles/inp&quot;&gt;INP&lt;/a&gt;), with &lt;a href=&quot;https://developer.chrome.com/docs/crux/methodology/metrics&quot;&gt;a few additional metrics&lt;/a&gt; to complement these.&lt;/p&gt;

&lt;p&gt;RUM tools usually include these Core Web Vitals, but often include many other metrics too. Some RUM providers also measure the user experience using their own combination of all these metrics perhaps to give a &quot;happiness index&quot; or such. When comparing RUM data to CrUX, ensure you are comparing like-for-like.&lt;/p&gt;

&lt;p&gt;Tools that assess Core Web Vitals pass or fail status should consider a page passing if it meets the recommended targets at the 75th percentile for all Core Web Vitals. If INP is not present for pages with no interactions, then only LCP and CLS need to pass.&lt;/p&gt;

&lt;h4 id=&quot;metric_differences_across_browsers&quot; data-text=&quot;Metric differences across browsers&quot; tabindex=&quot;-1&quot;&gt;Metric differences across browsers&lt;/h4&gt;

&lt;p&gt;CrUX only measures in Chrome browsers, and you can refer to the &lt;a href=&quot;https://chromium.googlesource.com/chromium/src/+/main/docs/speed/metrics_changelog/README.md&quot;&gt;Web Vitals Changelogs&lt;/a&gt; to see how these change with each version of Chrome.&lt;/p&gt;

&lt;p&gt;RUM solutions, however, will measure from a wider variety of browsers. Chromium-based browsers (Edge, Opera, and so on) will likely be similar to Chrome, unless Chrome is implementing new changes as noted in the Changelog.&lt;/p&gt;

&lt;p&gt;For non-Chromium browsers, the differences can be more pronounced. paint based metrics like LCP, INP and FCP, for example, are available in Safari and Firefox, but &lt;a href=&quot;https://web.dev/blog/lcp-and-inp-are-now-baseline-newly-available#are_there_any_differences_in_the_implementations&quot;&gt;is measured in a different way&lt;/a&gt;. This can lead to significant variances in the times reported. As stated previously, if you are wanting to compare RUM to CrUX, it is best to filter on just Chrome users to allow for a like-for-like comparison.&lt;/p&gt;

&lt;h4 id=&quot;metrics_timing&quot; data-text=&quot;Metrics timing&quot; tabindex=&quot;-1&quot;&gt;Metrics timing&lt;/h4&gt;

&lt;p&gt;Core Web Vitals metrics are provided by web browser APIs, but that does not mean there is not a potential for differences of values reported using them. Exactly &lt;em&gt;when&lt;/em&gt; the metric measurement is taken—at page load or throughout the full page life cycle—can lead to differences. RUM tools may not always measure metrics in the same way—even if using the same names—and the same browser APIs to get the data, which can be confusing.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://web.dev/articles/lcp&quot;&gt;Largest Contentful Paint (LCP)&lt;/a&gt; is a page-load metric. A number of LCP elements can be reported by the Web API if larger elements are loaded later after the initial render. The final LCP element is when the page finishes loading or the user interacts with the page. Therefore differences can arise if the LCP element is reported earlier than those two events.&lt;/p&gt;

&lt;p&gt;Additionally, in field data, the LCP element can be different depending on how the page is loaded. For a default page load showing the top of the page content, the LCP element will depend primarily on the screen size. However, if the page is opened with an anchor link further down the document, or similarly opened with a deep-link into a Single Page App (SPA)—more on that later—then the LCP element can be different.&lt;/p&gt;

&lt;p&gt;Don&#39;t assume that the LCP timings provided in either CrUX nor RUM are based on the same element as lab tools. While CrUX will give you the overall LCP value per page or origin, RUM can segment this further to identify individual LCP problem sessions.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://web.dev/articles/cls&quot;&gt;Cumulative Layout Shift (CLS)&lt;/a&gt; is measured &lt;a href=&quot;https://web.dev/articles/cls#what_is_cls&quot;&gt;throughout the life of the page&lt;/a&gt;, so the initial page-load CLS may not be representative of pages that cause greater shifts later after the page has loaded and the user has interacted with it. Taking the CLS value only after the page load—as many RUM products do—will therefore give a different result than taking the CLS value after the user finishes with the page.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://web.dev/articles/inp&quot;&gt;Interaction to Next Paint (INP)&lt;/a&gt; responsiveness metric requires an input to be measured, and observes all click, tap, and keyboard interactions throughout the life of the page, in a similar fashion to CLS, and so the reported value of INP may be very different if measured after the user has made a number of interactions on the page.&lt;/p&gt;

&lt;p&gt;CrUX will follow the &lt;a href=&quot;https://web.dev/articles/vitals&quot;&gt;Core Web Vitals documentation&lt;/a&gt; and measure these through the full lifetime of the page. Many RUM providers choose instead to measure these metrics either after page load, or at some other time (for example, when a key call-to-action is clicked) for various reasons.&lt;/p&gt;

&lt;p&gt;Getting an understanding from your RUM provider as to when Core Web Vitals are measured is important when seeing unexplained variances between the two data sources.&lt;/p&gt;

&lt;h4 id=&quot;single-page_applications&quot; data-text=&quot;Single-page applications&quot; tabindex=&quot;-1&quot;&gt;Single-page applications&lt;/h4&gt;

&lt;p&gt;Single-page applications (SPA) work by updating the content on the current page, rather than performing actual page navigations at the browser level. This means the browser does not see these as page navigations, despite users experiencing them as such. The &lt;a href=&quot;https://web.dev/articles/vitals-spa-faq&quot;&gt;Core Web Vitals APIs provided by the browser won&#39;t take these into consideration&lt;/a&gt;, and therefore CrUX does not support these page navigations. Work is underway to resolve this issue—see the &lt;a href=&quot;https://developer.chrome.com/docs/web-platform/soft-navigations&quot;&gt;Measuring soft navigations&lt;/a&gt; post for more information.&lt;/p&gt;

&lt;p&gt;Some RUM providers do attempt to detect &quot;soft navigations&quot; in SPAs, but if they&#39;re also attributing Core Web Vitals metrics to those &quot;soft navigations&quot; it will lead to differences with CrUX since the underlying APIs don&#39;t support this for many of the metrics.&lt;/p&gt;

&lt;h3 id=&quot;crux_and_web_api_differences&quot; data-text=&quot;CrUX and Web API differences&quot; tabindex=&quot;-1&quot;&gt;CrUX and Web API differences&lt;/h3&gt;

&lt;p&gt;As well as the differences in &lt;em&gt;which&lt;/em&gt; page views are measured, and &lt;em&gt;what&lt;/em&gt; is measured, there are a few other, more complicated, scenarios to be aware of that can lead to differences in CrUX and RUM data. Some of these are due to limitations of the Web APIs used to measure the metrics, and some are where the results returned by the API need to be treated differently for certain scenarios. The Core Web Vitals documentation lists these differences for &lt;a href=&quot;https://web.dev/articles/lcp#differences_between_the_metric_and_the_api&quot;&gt;LCP&lt;/a&gt; and &lt;a href=&quot;https://web.dev/articles/cls#differences_between_the_metric_and_the_api&quot;&gt;CLS&lt;/a&gt; but the main differences are also noted in the following sections.&lt;/p&gt;

&lt;h4 id=&quot;backforward_cache&quot; data-text=&quot;Back/forward cache&quot; tabindex=&quot;-1&quot;&gt;Back/forward cache&lt;/h4&gt;

&lt;p&gt;CrUX considers &lt;a href=&quot;https://web.dev/articles/bfcache&quot;&gt;Back/forward cache&lt;/a&gt; (or bfcache) restores as page navigations even though they don&#39;t result in a conventional page load. As the Web APIs don&#39;t treat these as a page load, RUM solutions need to &lt;a href=&quot;https://web.dev/articles/bfcache#how_bfcache_affects_analytics_and_performance_measurement&quot;&gt;take extra steps&lt;/a&gt; for these pages to be counted if they want to match CrUX. These are considerably faster page loads that can result in overall better performance being reported for a site, so not including them can result in worse overall page performance metrics. Refer to your RUM solution to understand if they handle bfcache restored pages.&lt;/p&gt;

&lt;h4 id=&quot;iframes&quot; data-text=&quot;Iframes&quot; tabindex=&quot;-1&quot;&gt;Iframes&lt;/h4&gt;

&lt;p&gt;For security and privacy reasons, top-level pages don&#39;t have access to content within iframes (not even same-origin iframes). This means performance metrics for content in those can only be measured by the iframe itself, and not through the Web APIs on the framing page. If the iframe content includes the LCP element, or content that impacts the CLS or INP experienced by the user, this won&#39;t be available to RUM solutions (&lt;a href=&quot;https://github.com/GoogleChrome/web-vitals#limitations&quot;&gt;including the Google web-vitals JavaScript library&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;CrUX however, being measured by the Chrome browser itself rather than JavaScript on the page, does not have these limitations and so does measure metrics within iframes when reporting Core Web Vitals. This more accurately reflects what the user experiences, but can be another reason for differences for sites making use of iframes.&lt;/p&gt;

&lt;p&gt;One concrete example of how this can lead to differences between LCP data in CrUX and RUM is embedded &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;video&amp;gt;&lt;/code&gt;. An playsinline autoplaying &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;video&amp;gt;&lt;/code&gt; element&#39;s first painted frame can count as an LCP candidate, but embeds for popular video streaming services may place these elements in an &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt;. CrUX can account for this, as it can access &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; contents, but RUM solutions cannot.&lt;/p&gt;

&lt;h4 id=&quot;cross-origin_resources&quot; data-text=&quot;Cross-origin resources&quot; tabindex=&quot;-1&quot;&gt;Cross-origin resources&lt;/h4&gt;

&lt;p&gt;LCP media served from other domains may not give render time in the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/PerformanceObserver&quot;&gt;PerformanceObserver API&lt;/a&gt;—unless the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Headers/Timing-Allow-Origin&quot;&gt;Timing-Allow-Origin header&lt;/a&gt; (TAO) is provided—due to browser security restrictions to reduce timing attacks. This &lt;a href=&quot;https://web.dev/articles/lcp#load-render-time&quot;&gt;falls back to the load time of the resource&lt;/a&gt; but this may be quite different from when the content was actually painted.&lt;/p&gt;

&lt;p&gt;This can lead to the seemingly impossible situation where LCP is reported by web APIs as earlier than FCP. This is not the case but only appears so due to this security restriction.&lt;/p&gt;

&lt;p&gt;This was &lt;a href=&quot;https://github.com/w3c/paint-timing/issues/104&quot;&gt;resolved in late 2024&lt;/a&gt; and a slightly coarsened render time &lt;a href=&quot;https://chromestatus.com/feature/5128261284397056&quot;&gt;is available from Chrome 133&lt;/a&gt; even when &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;Timing-Allow-Origin&lt;/code&gt; is not provided.&lt;/p&gt;

&lt;p&gt;Again, CrUX does report the render time data for Core Web Vitals. Sites are advised to limit cross-origin content that impacts the Core Web Vitals metrics and to enable TAO where possible if they want to measure this more accurately. Other cross-origin resources may be subject to similar restrictions.&lt;/p&gt;

&lt;h4 id=&quot;background_tabs&quot; data-text=&quot;Background tabs&quot; tabindex=&quot;-1&quot;&gt;Background tabs&lt;/h4&gt;

&lt;p&gt;When a page is not opened in a background tab they will still emit metrics using Web APIs. However, these are not reported by CrUX since they give timings that are inconsistent with the user experience. RUM solutions should also consider ignoring these, or at least explaining how these page views are treated.&lt;/p&gt;

&lt;h2 id=&quot;so_what_can_we_do_about_it&quot; data-text=&quot;So what can we do about it?&quot; tabindex=&quot;-1&quot;&gt;So what can we do about it?&lt;/h2&gt;

&lt;p&gt;We have shown why there may be differences between CrUX and RUM data, either due to differences in the methodology each uses or due to which users and page views are included or excluded. Ideally, both sets of data will still be representative of your site performance to be useful, but the reasons given should outline why it is very unlikely to get the exact same numbers in each.&lt;/p&gt;

&lt;p&gt;Where differences are slight (for example reporting a LCP of 2.0 seconds versus 2.2 seconds) both datasets will be useful and can usually be considered to be roughly in sync.&lt;/p&gt;

&lt;p&gt;When pronounced differences make you question the accuracy of the data, you should try to understand those differences. Can the RUM data be filtered to be more closely aligned with CrUX (looking only at Chrome users, for desktop or mobile, with 75th percentile values over 28 days) to reduce these differences?&lt;/p&gt;

&lt;p&gt;If so—and you can get the data to match more closely—then you should still ask why you are seeing these differences in the overall data and what this means. Are non-Chrome users skewing your metrics in a positive or negative way? Does this give you more insights into where you have performance issues that you can prioritize?&lt;/p&gt;

&lt;p&gt;If your non-Chrome users are getting different results then you can use this valuable insight that RUM has given you to optimize differently. For example, certain APIs are not available on certain browsers, but you can consider alternatives for unsupported browsers to also improve their experiences. Or you can give &lt;a href=&quot;https://www.smashingmagazine.com/2022/03/signals-customizing-website-user-experience&quot;&gt;a different, but more performant, experience&lt;/a&gt; to users on constrained devices or networks. CrUX is limited to Chrome data, but you should consider all your site visitors&#39; experiences to help prioritize improvements. RUM data can fill that gap.&lt;/p&gt;

&lt;p&gt;Once you understand the reasons for any differences, both tools can be incredibly useful to understand the user experiences of your website, and to help improve this even if the numbers are not identical. Use your RUM data to complement CrUX data and allow you to dig into what CrUX is telling you at a high level by segmenting your traffic to help you identify if it&#39;s particular areas of your site or user base that need attention.&lt;/p&gt;

&lt;p&gt;Looking at the trends to see your improvements are having the expected positive impacts is often more important than having each number match exactly between the two data sources. As mentioned previously, RUM lets you look at different time frames to get an advance look at what your 28 day CrUX scores will be—though looking at too short time frames can lead to noisy data, hence why CrUX uses 28 days.&lt;/p&gt;

&lt;p&gt;Often there is no &quot;right&quot; or &quot;wrong&quot; answer in these different metrics - they are just a different lens on your users and how they are experiencing your site. As long as you understand why these differences happen, and what that can do to drive your decision making, that is what is more important to better serve your site visitors.&lt;/p&gt;

&lt;h2 id=&quot;acknowledgements&quot; data-text=&quot;Acknowledgements&quot; tabindex=&quot;-1&quot;&gt;Acknowledgements&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Thumbnail image by &lt;a href=&quot;https://unsplash.com/@slelham&quot;&gt;Steven Lelham&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/atSaEOeE8Nk&quot;&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

  

  
</description><link>https://web.dev/articles/crux-and-rum-differences?hl=en</link><guid isPermaLink="false">https://web.dev/articles/crux-and-rum-differences?hl=en</guid><pubDate>Wed, 17 Dec 2025 08:00:00 GMT</pubDate></item><item><title>Responsive and fluid typography with Baseline CSS features</title><description>&lt;p&gt;&lt;style&gt;
  .wd-author__links {
    display: flex;
  }
  &lt;/style&gt;

&lt;/p&gt;

&lt;p class=&quot;wd-pubdates&quot; style=&quot;font-size: 90%; border-bottom: 1px solid var(--devsite-ref-palette--grey200);color: var(--devsite-ref-palette--blue-grey700);&quot;&gt;
  Published: December 16, 2025
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Responsive_web_design&quot;&gt;Responsive web design&lt;/a&gt; is an approach to building websites that look good and function well across a range of different browsers, viewport dimensions, devices, and user preferences. When applied to typography, the central concern is often adjusting &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;font-size&lt;/code&gt; based on the browser width—which can also have implications for spacing values like &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;line-height&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;margin&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;As designers, it makes sense to think about what space is available in the browser, and adjust your typography accordingly. It&#39;s also important to remember that different users will have different &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;font-size&lt;/code&gt; needs across a range of devices, depending on personal circumstances outside your reach or awareness. So it&#39;s dangerous to do anything that would take away user control of the final result. There are two primary inputs that people can use to impact font sizing while they browse the web:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Providing a default &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;font-size&lt;/code&gt; preference across all websites.&lt;/li&gt;
&lt;li&gt;Zooming in or out on a site-by-site basis.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal of this demo is to make typography responsive to both the browser&#39;s viewport size, and also the user inputs. But it&#39;s important to understand that &lt;em&gt;the more your typography responds to the viewport, the less it will respond to user preferences.&lt;/em&gt; If you are going to implement responsive typography, it&#39;s important to do so carefully, and test that the results are still accessible.&lt;/p&gt;

&lt;div class=&quot;wd-embed&quot; style=&quot;height: 640px; width: 100%&quot;&gt;
   &lt;iframe allow=&quot;camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;&quot; loading=&quot;lazy&quot; src=&quot;https://codepen.io/web-dot-dev/embed/dPMEarE?height=640&amp;amp;theme-id=light&amp;amp;default-tab=result&amp;amp;editable=true&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot; title=&quot;Pen dPMEarE by web-dot-dev on Codepen&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
   &lt;/iframe&gt;
&lt;/div&gt;
&lt;aside class=&quot;note&quot;&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;span&gt; This demo is also &lt;a href=&quot;https://github.com/GoogleChromeLabs/baseline-demos/tree/main/src/responsive-type&quot;&gt;available on GitHub&lt;/a&gt;.&lt;/span&gt;&lt;/aside&gt;
&lt;h2 id=&quot;negotiate_a_base_font-size_based_on_user_preferences&quot; data-text=&quot;Negotiate a base font-size based on user preferences&quot; tabindex=&quot;-1&quot;&gt;Negotiate a base &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;font-size&lt;/code&gt; based on user preferences&lt;/h2&gt;

&lt;p&gt;The first step for defining any typography online is to negotiate an initial font size based on the user&#39;s &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;font-size&lt;/code&gt; preference. This will be used for the majority of the text on the page, and as a basis for other font sizes like headings. The simplest option here is to give the user full control by using &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1em&lt;/code&gt;, without any adjustments. When you don&#39;t set any other &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;font-size&lt;/code&gt; value, &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1em&lt;/code&gt; refers to the user preference. On the other end of the spectrum, setting a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;font-size&lt;/code&gt; in pixels or other absolute units (and even viewport-relative units) will override the user entirely, which should be avoided.&lt;/p&gt;

&lt;p&gt;However, different use cases call for different typography. An article might be easier to read in large text, while data-heavy sites might call for a more compact design with smaller text. In either case, you might want to suggest a default that fits the design, while still allowing the user to adjust the result based on their unique situation.&lt;/p&gt;

&lt;h3 id=&quot;option_one_calculate_a_multiplier_based_on_assumptions&quot; data-text=&quot;Option one: calculate a multiplier based on assumptions&quot; tabindex=&quot;-1&quot;&gt;Option one: calculate a multiplier based on assumptions&lt;/h3&gt;

&lt;p&gt;One common compromise is to define an adjusted &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;font-size&lt;/code&gt; in &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;em&lt;/code&gt; or &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;%&lt;/code&gt; units, relative to the user default &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;font-size&lt;/code&gt;. Generally, this approach starts with the assumption that browsers provide a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;16px&lt;/code&gt; default, and most users will leave that default in place. If you think a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;20px&lt;/code&gt; font size will work better for your site, then a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;font-size&lt;/code&gt; of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1.25em&lt;/code&gt; or &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;125%&lt;/code&gt; will usually give the result you want:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-c&quot;&gt;/* 20px preferred, 16px expected: 20/16 = 1.25 */&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;1.25&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;em&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;You could also use a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;calc()&lt;/code&gt; function here to show the math, but you still have to know what the equation is—the target size, divided by the expected size, multiplied by &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1em&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;em&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;Users with a larger or smaller preference will have some ability to impact the result, since your default is now relative to theirs—1.25 times their preference in this case. But, it can be strange if both you and the user are both requesting &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;20px&lt;/code&gt; defaults, and the result is &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;25px&lt;/code&gt;—their adjusted default times 1.25 again—a size that no one asked for.&lt;/p&gt;

&lt;h3 id=&quot;option_two_let_clamp_do_the_work&quot; data-text=&quot;Option two: let clamp() do the work&quot; tabindex=&quot;-1&quot;&gt;Option two: let &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;clamp()&lt;/code&gt; do the work&lt;/h3&gt;

&lt;p&gt;A more nuanced approach involves CSS comparison functions, without any math! Rather than assuming 1em is equal to &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;16px&lt;/code&gt;, and doing unreliable conversions from &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;px&lt;/code&gt; to &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;em&lt;/code&gt;, you can think of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1em&lt;/code&gt; as a variable that refers to the user preference. No matter what pixel value &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1em&lt;/code&gt; represents, a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;font-size&lt;/code&gt; of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;max(1em, 20px)&lt;/code&gt; will always return the larger of your design preference (&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;20px&lt;/code&gt;) and the user&#39;s preference (&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1em&lt;/code&gt;). This allows the user to choose larger, but not smaller font sizes.&lt;/p&gt;

&lt;p&gt;By switching to a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;clamp()&lt;/code&gt; function, you can allow the user to scale both directions, when their preferred size gets too far from your chosen default. For example, a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;font-size&lt;/code&gt; of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;clamp(1em, 20px, 1.25em)&lt;/code&gt; will default to &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;20px&lt;/code&gt; as long as that is larger than the user default, but not more than &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;125%&lt;/code&gt; of their default.&lt;/p&gt;

&lt;p&gt;This allows your design to take priority when it is &lt;em&gt;close&lt;/em&gt; to the user preference, but the user still has priority when their preference is outside the specified range. There&#39;s no conversion math involved, no assumptions about the user preference size, and no multiplication of designer and user values.&lt;/p&gt;

&lt;p&gt;By setting this as the root &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;font-size&lt;/code&gt; on the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;html&lt;/code&gt; element, you can now reference &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1rem&lt;/code&gt; anywhere on the site, as the negotiated base size.&lt;/p&gt;

&lt;h2 id=&quot;add_responsiveness&quot; data-text=&quot;Add responsiveness&quot; tabindex=&quot;-1&quot;&gt;Add responsiveness&lt;/h2&gt;

&lt;p&gt;To make this &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;font-size&lt;/code&gt; responsive to the viewport, one option would be to add media query (or container query) breakpoints. For example, you could change the clamped value depending on the screen size:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;clamp&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;em&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--base-font-size&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;px&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;),&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;1.25&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;em&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;@media&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;(width&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &amp;gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;30em)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--base-font-size&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;18&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;px&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;media&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &amp;gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;45em&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;--base-font-size&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;20px&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-err&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;The other option is adding viewport or container units to the static base value:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;clamp&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;em&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;px&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;0.25&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;vw&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;1.25&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;em&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;vw&lt;/code&gt; (viewport width) or &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;vi&lt;/code&gt; (viewport inline size) units represent 1% of the total viewport—the part of the browser that renders your site. Similarly, the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;cqw&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;cqi&lt;/code&gt; units represent 1% of an inline-size container on the page. Check out the &lt;a href=&quot;https://web.dev/articles/baseline-in-action-container-queries&quot;&gt;container queries and units demo&lt;/a&gt; for more details.&lt;/p&gt;

&lt;p&gt;This approach is often referred to as &lt;em&gt;fluid typography&lt;/em&gt;, since the change in &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;font-size&lt;/code&gt; is constant over a range of viewport widths, rather than jumping from one value to another at media or container breakpoints. However, don&#39;t get distracted by the smoothness of the transition—that distinction is generally only visible in testing, if you smoothly adjust the window size. That effect is rarely (if ever) seen by users. While users may regularly change the size of the browser—or the zoom level—they would have to make those adjustments in a slow and fluid manner to notice the difference between a breakpoint and a viewport unit. It only impacts the transition, not the result after resizing.&lt;/p&gt;

&lt;p&gt;The main advantage of fluid font sizing is that it removes any need to calculate or specify breakpoints manually, providing an interpolated result at any given size. You only set the starting point (&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;16px&lt;/code&gt;) and the rate of change (&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;0.25vw&lt;/code&gt; will provide a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;0.25px&lt;/code&gt; increase in &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;font-size&lt;/code&gt; for every &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;100px&lt;/code&gt; increase in the viewport), and possibly min and max values. When the viewport is &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1000px&lt;/code&gt; wide, the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;font-size&lt;/code&gt; will be &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;16px + 2.5px&lt;/code&gt; or &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;18.5px&lt;/code&gt;—but that calculation is handled entirely by the browser. This is the approach used in the demo, using &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;cqi&lt;/code&gt; units to show container-based responsiveness. When used on the root (&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;html&lt;/code&gt;) element, where there is no defined container, &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;cqi&lt;/code&gt; units still refer to the viewport size.&lt;/p&gt;

&lt;p&gt;If you prefer to think in terms of the specified &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;font-size&lt;/code&gt; at a given viewport size, consider using the more direct media query approach, which is a bit more clear. Things get complicated when you try to calculate viewport units based on intended breakpoints. Many people do this by copy and pasting values from third-party tools, but the resulting code is much more difficult to understand or change directly. In general with CSS, the best option is the one that most clearly expresses your intentions.&lt;/p&gt;

&lt;h2 id=&quot;warning_viewport_changes_dont_always_mean_the_same_thing&quot; data-text=&quot;Warning: Viewport changes don&#39;t always mean the same thing!&quot; tabindex=&quot;-1&quot;&gt;Warning: Viewport changes don&#39;t always mean the same thing!&lt;/h2&gt;

&lt;p&gt;While media queries and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;vi&lt;/code&gt; units are applied in different ways, both approaches are based on the same measurement of the viewport. If the viewport is &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;600px&lt;/code&gt; wide, then &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;100vw&lt;/code&gt; will be equal to &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;600px&lt;/code&gt;, and styles inside the (&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;width &amp;gt; 500px&lt;/code&gt;) media query will be applied.&lt;/p&gt;

&lt;p&gt;But what does it mean for the viewport to be &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;600px&lt;/code&gt; wide? In reality, a pixel is not one fixed size with a single meaning in all situations. While it feels natural that a viewport with fewer pixels across is on a smaller screen (like a phone), or in a narrow browser window, that&#39;s not a reliable assumption. In fact, &lt;em&gt;zooming in and making the browser window smaller&lt;/em&gt; will both have the same impact on the measured viewport width. One action (zooming) changes the size of a pixel, while the other (resizing) changes the size of the browser itself—but &lt;em&gt;both change the number of pixels across the width of the browser.&lt;/em&gt; What we get from the viewport measurement is a &lt;em&gt;relationship&lt;/em&gt; between the current pixel size and the current browser window.&lt;/p&gt;

&lt;p&gt;For the user, zooming and resizing each serve a very different purpose. A user that changes the zoom level is trying to make the contents of the page larger or smaller, but a user that resizes the browser is only managing the space across different screens. Even though the user intent is different, the result on CSS measurements is the same. As the window gets smaller, or the pixel gets larger, there are fewer pixels across the browser&#39;s width.&lt;/p&gt;

&lt;p&gt;That disconnect makes responsive typography unreliable. If your text is set to resize based only on a viewport or container, then the user zoom will have no effect!&lt;/p&gt;

&lt;div class=&quot;wd-embed&quot; style=&quot;height: 640px; width: 100%&quot;&gt;
   &lt;iframe allow=&quot;camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;&quot; loading=&quot;lazy&quot; src=&quot;https://codepen.io/web-dot-dev/embed/zxqQbaV?height=640&amp;amp;theme-id=light&amp;amp;default-tab=result&amp;amp;editable=true&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot; title=&quot;Pen zxqQbaV by web-dot-dev on Codepen&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
   &lt;/iframe&gt;
&lt;/div&gt;

&lt;p&gt;Changing the value of the viewport-relative unit to &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1vw&lt;/code&gt; or &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;100vw&lt;/code&gt; will change the exact relationship between the font size and viewport. A &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1vw&lt;/code&gt; font will grow &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1px&lt;/code&gt; for every &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;100px&lt;/code&gt; of viewport size, while a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;100vw&lt;/code&gt; font will be exactly the same size as the viewport. You can change that value to make the font grow more slowly or more quickly in relation to the browser. But any viewport-relative value will remain constant as the user zooms in or out—entirely unresponsive to user controls.&lt;/p&gt;

&lt;p&gt;Similarly, neither &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1vw&lt;/code&gt; nor &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;100vw&lt;/code&gt; accounts for the user default &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;font-size&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Using viewport or container-relative units on their own for &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;font-size&lt;/code&gt; is always hostile to the user. When a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;font-size&lt;/code&gt; is fully responsive to its container, it cannot also be responsive to user defaults or adjustments. Even with the best intentions and safeguards, taking that final &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;font-size&lt;/code&gt; control away from users should be avoided. It&#39;s not only a bad user experience, but can also break accessibility guidance that is often required by law. Specifically, &lt;a href=&quot;https://www.w3.org/WAI/WCAG22/quickref/?versions=2.1#resize-text&quot;&gt;section 1.4.4 of the Web Content Accessibility Guidelines&lt;/a&gt; requires that &quot;text can be resized without assistive technology up to 200 percent.&quot;&lt;/p&gt;

&lt;h3 id=&quot;how_to_ensure_font-size_values_are_responsive_to_zoom&quot; data-text=&quot;How to ensure font-size values are responsive to zoom&quot; tabindex=&quot;-1&quot;&gt;How to ensure &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;font-size&lt;/code&gt; values are responsive to zoom&lt;/h3&gt;

&lt;p&gt;To ensure that a viewport-relative &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;font-size&lt;/code&gt; is responsive to zoom, the viewport-relative value has to be applied as an adjustment to some other value. That&#39;s possible in CSS using the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;calc()&lt;/code&gt; function or any other math function that accepts calculations—such as &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;min()&lt;/code&gt;, &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;max()&lt;/code&gt;, and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;clamp()&lt;/code&gt;. A &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;font-size&lt;/code&gt; of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;calc(16px + 1vw)&lt;/code&gt; is based on both the viewport size, and also the current (zoom-relative) size of a pixel. While the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;vw&lt;/code&gt; unit won&#39;t be impacted by zoom, the base value will be.&lt;/p&gt;

&lt;p&gt;The result is a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;font-size&lt;/code&gt; that responds to both the viewport size and the user&#39;s zoom settings. If the user zooms to &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;200%&lt;/code&gt;, the base value will render twice as large (&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;32px&lt;/code&gt;), while the responsive value remains unchanged. A &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1000px&lt;/code&gt; viewport would initially give you a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;font-size&lt;/code&gt; of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;16px + 10px = 26px&lt;/code&gt;, but at &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;200%&lt;/code&gt; zoom, the font size would only grow to &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;42px&lt;/code&gt;, just over &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;160%&lt;/code&gt;. That may not seem like an extreme issue, but the more your &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;font-size&lt;/code&gt; is based on the viewport, the less effective zoom becomes.&lt;/p&gt;

&lt;p&gt;On small screens, the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;font-size&lt;/code&gt; will come primarily from the base pixel value, and will respond well to zoom. But on larger screens, the viewport-sizing becomes a larger fraction of the rendered font size, making zoom less effective. This becomes particularly dangerous at the point where 500% zoom (the max in most browsers) can no longer provide the 200% increase in font-size required by WCAG 1.4.4—but even before that point, it can be frustrating to have zoom become ineffective.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/baseline-in-action-fluid-type/image/fig-1.png&quot; width=&quot;800&quot; height=&quot;530&quot; alt=&quot;A graph showing font size and zoom effectiveness versus viewport width. The font size, calculated as `calc(17px + 2.5vw)`, increases linearly with viewport width. The 500% zoom line, representing the maximum possible zoom, shows that zoom becomes less effective as viewport width increases, failing to provide a 200% font size increase beyond a viewport width of 2040px.&quot; srcset=&quot;https://web.dev/static/articles/baseline-in-action-fluid-type/image/fig-1_36.png 36w,https://web.dev/static/articles/baseline-in-action-fluid-type/image/fig-1_48.png 48w,https://web.dev/static/articles/baseline-in-action-fluid-type/image/fig-1_72.png 72w,https://web.dev/static/articles/baseline-in-action-fluid-type/image/fig-1_96.png 96w,https://web.dev/static/articles/baseline-in-action-fluid-type/image/fig-1_480.png 480w,https://web.dev/static/articles/baseline-in-action-fluid-type/image/fig-1_720.png 720w,https://web.dev/static/articles/baseline-in-action-fluid-type/image/fig-1_856.png 856w,https://web.dev/static/articles/baseline-in-action-fluid-type/image/fig-1_960.png 960w,https://web.dev/static/articles/baseline-in-action-fluid-type/image/fig-1_1440.png 1440w,https://web.dev/static/articles/baseline-in-action-fluid-type/image/fig-1_1920.png 1920w,https://web.dev/static/articles/baseline-in-action-fluid-type/image/fig-1_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption&gt;
    The horizontal axis represents viewport size, from &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;0&lt;/code&gt; to &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;2600px&lt;/code&gt; wide. The vertical axis for &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;font-size&lt;/code&gt; is also in pixels, showing the result of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;calc(17px + 2.5vw)&lt;/code&gt;. The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;500%&lt;/code&gt; zoom line uses the same viewport-width horizontal axis, but treats the vertical axis as a percentage.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;At the left edge of the graph (&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;0&lt;/code&gt; viewport width), &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;500%&lt;/code&gt; zoom is fully effective. However, that effectiveness drops away quickly as the browser size increases, and (unzoomable) viewport units become a larger factor in the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;font-size&lt;/code&gt;. When the browser is &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;2040px&lt;/code&gt; wide, the maximum &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;500%&lt;/code&gt; zoom is only able to achieve a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;200%&lt;/code&gt; increase in font-size. Beyond that point, &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;200%&lt;/code&gt; effective font-zoom is no longer possible.&lt;/p&gt;

&lt;p&gt;By moving this calculation into a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;clamp()&lt;/code&gt; function, with min and max values, you can enforce boundaries that ensure zoomable text. According to &lt;a href=&quot;https://www.smashingmagazine.com/2023/11/addressing-accessibility-concerns-fluid-type/&quot;&gt;Maxwell Barvian&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If the maximum font size is less than or equal to 2.5 times the minimum font size, then the text will always pass WCAG SC 1.4.4, at least on all modern browsers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Since &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;@media&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;@container&lt;/code&gt; queries are based on the same measurements as &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;vw&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;cqw&lt;/code&gt; units, the same logic applies when using a breakpoint to change the font size. When the size increase is too dramatic, zoom becomes ineffective. You can experiment with how these values interact in the following visualization:&lt;/p&gt;

&lt;div class=&quot;wd-embed&quot; style=&quot;height: 640px; width: 100%&quot;&gt;
   &lt;iframe allow=&quot;camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;&quot; loading=&quot;lazy&quot; src=&quot;https://codepen.io/web-dot-dev/embed/KwzLYBK?height=640&amp;amp;theme-id=light&amp;amp;default-tab=result&amp;amp;editable=true&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot; title=&quot;Pen KwzLYBK by web-dot-dev on Codepen&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
   &lt;/iframe&gt;
&lt;/div&gt;

&lt;h3 id=&quot;how_to_ensure_font-size_values_are_responsive_to_user_defaults&quot; data-text=&quot;How to ensure font-size values are responsive to user defaults&quot; tabindex=&quot;-1&quot;&gt;How to ensure &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;font-size&lt;/code&gt; values are responsive to user defaults&lt;/h3&gt;

&lt;p&gt;But &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;calc(16px + 1vw)&lt;/code&gt; is still not responsive to user default font settings. To achieve that, you can set a base—or min and max values—using &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;em&lt;/code&gt; or &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;rem&lt;/code&gt; units rather than &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;px&lt;/code&gt;. Putting it all together, you get a familiar result that matches the linked demo:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;clamp&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;em&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;17&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;px&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;0.24&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;vw&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;1.125&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;em&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;Note that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The minimum and maximum both use &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;em&lt;/code&gt; units, which are based on the user preference (and responsive to zoom).&lt;/li&gt;
&lt;li&gt;The additional &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;vw&lt;/code&gt; value is kept minimal, so that zoom isn&#39;t too heavily impacted.&lt;/li&gt;
&lt;li&gt;The maximum size (&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1.125em&lt;/code&gt;) is well under 2.5 times the minimum (&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1em&lt;/code&gt;), ensuring that an effective &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;font-size&lt;/code&gt; value of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;200%&lt;/code&gt; is always possible.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;typographic_scales_with_pow&quot; data-text=&quot;Typographic scales with pow()&quot; tabindex=&quot;-1&quot;&gt;Typographic scales with &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;pow()&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;Most designs use more than one font size! A &lt;em&gt;typographic scale&lt;/em&gt; describes the relationship between multiple font sizes. This can be expressed as a &lt;em&gt;base size&lt;/em&gt; and a series of multipliers to calculate the other sizes. CSS provides a built-in typographic scale relative to the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;medium&lt;/code&gt; keyword, which refers to the user&#39;s font size preference, or a default of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;16px&lt;/code&gt;. The full &lt;a href=&quot;https://drafts.csswg.org/css-fonts/#absolute-size-mapping&quot;&gt;keyword scale&lt;/a&gt; is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;xx-small&lt;/code&gt;: 3/5 (0.6)&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;x-small&lt;/code&gt;: 3/4 (0.75)&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;small&lt;/code&gt;: 8/9 (0.89)&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;medium&lt;/code&gt;: 1 (the base size that others are multiplied against)&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;large&lt;/code&gt;: 6/5 (1.2)&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;x-large&lt;/code&gt;: 3/2 (1.5)&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;xx-large&lt;/code&gt;: 2/1 (2)&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;xxx-large&lt;/code&gt;: 3/1 (3)&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;wd-embed&quot; style=&quot;height: 640px; width: 100%&quot;&gt;
   &lt;iframe allow=&quot;camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;&quot; loading=&quot;lazy&quot; src=&quot;https://codepen.io/web-dot-dev/embed/pvymBJB?height=640&amp;amp;theme-id=light&amp;amp;default-tab=result&amp;amp;editable=true&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot; title=&quot;Pen pvymBJB by web-dot-dev on Codepen&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
   &lt;/iframe&gt;
&lt;/div&gt;

&lt;p&gt;This scale is relative to the user default rather than the root &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;font-size&lt;/code&gt;, so it doesn&#39;t work as well once you change the root &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;font-size&lt;/code&gt; of your site. Most authors end up re-creating a similar type scale with custom properties—sometimes using the same t-shirt size names, and sometimes preferring a series of steps up and down a mathematical scale. There are many third-party tools for generating these scales based on common ratios, mostly borrowed from a western musical scale:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-c&quot;&gt;/* musical ratios */&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--minor-second&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--major-second&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--minor-third&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--major-third&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--perfect-fourth&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--augmented-fourth&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;sqrt&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--perfect-fifth&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--major-sixth&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-c&quot;&gt;/* the golden ratio*/&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--golden-ratio&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;sqrt&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;But you don&#39;t need outside tools to create your own scale in CSS—the new &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;pow()&lt;/code&gt; function can generate your scale for you—with &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1rem&lt;/code&gt; as your own base size!&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-c&quot;&gt;/* choose a ratio */&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--scale&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;1.2&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-c&quot;&gt;/* generate the scale using pow() */&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--xx-small&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;rem&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;pow&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--scale&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;),&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;-0.5&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--x-small&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;rem&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;pow&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--scale&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;),&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;-0.25&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--small&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;rem&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;pow&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--scale&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;),&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;-0.125&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--medium&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;rem&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--large&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;rem&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;pow&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--scale&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;),&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--x-large&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;rem&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;pow&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--scale&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;),&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--xx-large&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;rem&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;pow&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--scale&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;),&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--xxx-large&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;rem&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;pow&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--scale&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;),&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;));&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-c&quot;&gt;/* change the ratio for different viewport sizes */&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;@media&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;(width&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &amp;gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;50em)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--scale&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--perfect-fourth&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-err&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;&lt;devsite-iframe&gt;&lt;iframe src=&quot;https://web.dev/frame/articles/baseline-in-action-fluid-type/index_b65360db44ad1561e7dea8a7afd7557bf5464bfac6cb7494b3e6f67b0b4a08a8.frame&quot; class=&quot;framebox inherit-locale &quot; allow=&quot;clipboard-write https://web-dot-devsite-v2-prod-3p.appspot.com&quot; allowfullscreen=&quot;&quot; is-upgraded=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;&lt;/devsite-iframe&gt;&lt;/p&gt;

&lt;p&gt;You don&#39;t have to use whole steps to keep the scale consistent. In fact, the common &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;12pt&lt;/code&gt; typography scale uses roughly 5 fractions per step. While the large sizes here use whole steps in the scale, the small sizes use fractions to scale at a slower rate.&lt;/p&gt;

&lt;div class=&quot;wd-embed&quot; style=&quot;height: 640px; width: 100%&quot;&gt;
   &lt;iframe allow=&quot;camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;&quot; loading=&quot;lazy&quot; src=&quot;https://codepen.io/web-dot-dev/embed/ogxROxZ?height=640&amp;amp;theme-id=light&amp;amp;default-tab=result&amp;amp;editable=true&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot; title=&quot;Pen ogxROxZ by web-dot-dev on Codepen&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
   &lt;/iframe&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;https://www.w3.org/TR/css-mixins-1/&quot;&gt;CSS mixins and functions&lt;/a&gt; make it possible to condense that logic even more, while other built-in tools like &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;progress()&lt;/code&gt; make it easier to create scales that fluidly adjust from one value to another. But those features are out of scope for this demo.&lt;/p&gt;

&lt;p&gt;&lt;devsite-iframe&gt;&lt;iframe src=&quot;https://web.dev/frame/articles/baseline-in-action-fluid-type/index_83dc4266c182b0477a2fdf3166ad6eba3c53946a7ce2b731fd9713a011c72033.frame&quot; class=&quot;framebox inherit-locale &quot; allow=&quot;clipboard-write https://web-dot-devsite-v2-prod-3p.appspot.com&quot; allowfullscreen=&quot;&quot; is-upgraded=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;&lt;/devsite-iframe&gt;
&lt;devsite-iframe&gt;&lt;iframe src=&quot;https://web.dev/frame/articles/baseline-in-action-fluid-type/index_1f717cfee1b2a699e31e3433830ab56a5f09a88e041f5fa1ed8aba687a96affc.frame&quot; class=&quot;framebox inherit-locale &quot; allow=&quot;clipboard-write https://web-dot-devsite-v2-prod-3p.appspot.com&quot; allowfullscreen=&quot;&quot; is-upgraded=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;&lt;/devsite-iframe&gt;&lt;/p&gt;

&lt;h2 id=&quot;respond_to_the_size_of_in-page_containers&quot; data-text=&quot;Respond to the size of in-page containers&quot; tabindex=&quot;-1&quot;&gt;Respond to the size of in-page containers&lt;/h2&gt;

&lt;p&gt;You can make all these calculations work in container queries by using the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;cqi&lt;/code&gt; unit in place of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;vw&lt;/code&gt; or &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;vi&lt;/code&gt;, but it also helps to leave the user&#39;s &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;font-size&lt;/code&gt; in place on the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;html&lt;/code&gt; element, so that every type-setting container can refer back to that user preference as &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1rem&lt;/code&gt;. In the demo, you&#39;ll find that the entire type scale is applied to the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;body&lt;/code&gt; rather than the root &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;html&lt;/code&gt; element for global type, and then reset based on the container size for every element with the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;type-set&lt;/code&gt; attribute.&lt;/p&gt;

&lt;p&gt;This is always a trade-off with container-relative font sizes. You achieve more fluid font sizing for each element in context, but at the expense of a page-wide consistency. Which one is more important to you will depend on the specifics of your use case. And remember that fluid typography itself is a trade-off, making user controls like zoom less effective!&lt;/p&gt;

&lt;p&gt;While responsive typography and typographic scales are great tools for designers, there&#39;s no need to make things more complicated, if you don&#39;t need to. The user default and built-in type scale are a great option as well! But if you do choose responsive (or fluid) typography, make sure to test how the results behave in relation to different user defaults and zoom settings. Enjoy!&lt;/p&gt;
  

  
</description><link>https://web.dev/articles/baseline-in-action-fluid-type?hl=en</link><guid isPermaLink="false">https://web.dev/articles/baseline-in-action-fluid-type?hl=en</guid><pubDate>Tue, 16 Dec 2025 08:00:00 GMT</pubDate></item><item><title>Ten modern layouts in one line of CSS</title><description>&lt;p&gt;This post highlights a few powerful lines of CSS that do some serious heavy lifting and help you build robust modern layouts.&lt;/p&gt;

&lt;p&gt;&lt;style&gt;
  .wd-author__links {
    display: flex;
  }
  &lt;/style&gt;

&lt;/p&gt;&lt;p&gt;&lt;/p&gt;

&lt;p&gt;
&lt;/p&gt;&lt;p class=&quot;wd-pubdates&quot; style=&quot;font-size: 90%; border-bottom: 1px solid var(--devsite-ref-palette--grey200);color: var(--devsite-ref-palette--blue-grey700);&quot;&gt;
  Published: July 7, 2020, Last updated: December 15, 2025
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;devsite-video video-id=&quot;qm0IfG1GyZU&quot;&gt;&lt;/devsite-video&gt;&lt;/p&gt;

&lt;p&gt;Modern CSS layouts enable developers to write really meaningful and robust styling rules with just a few keystrokes. The talk above and this subsequent post examine 10 powerful lines of CSS that do some serious heavy lifting.&lt;/p&gt;
&lt;aside class=&quot;note&quot;&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;span&gt; All ten features mentioned in the article are &lt;a href=&quot;https://web.dev/baseline&quot;&gt;Baseline Widely available&lt;/a&gt;.&lt;/span&gt;&lt;/aside&gt;
&lt;div class=&quot;wd-embed&quot; style=&quot;height: 480px;&quot;&gt;
  &lt;iframe loading=&quot;lazy&quot; src=&quot;https://1linelayouts.com/&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot; title=&quot;1linelayouts&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;p&gt;To follow along or play with these demos on your own, check out the site embed above, or visit &lt;a href=&quot;https://1linelayouts.com/&quot;&gt;1linelayouts.com&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;01_super_centered_place-items_center&quot; data-text=&quot;01. Super Centered: place-items: center&quot; tabindex=&quot;-1&quot;&gt;01. Super Centered: &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;place-items: center&lt;/code&gt;&lt;/h2&gt;

&lt;figure&gt;
  &lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;
    &lt;source src=&quot;https://web.dev/static/articles/one-line-layouts/video/01-place-items-center.mp4&quot;&gt;
  &lt;/video&gt;
&lt;/figure&gt;

&lt;p&gt;For the  first &#39;single-line&#39; layout, let&#39;s solve the biggest mystery in all of the CSS land: centering things. I want you to know that it&#39;s easier than you think with &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/place-items&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;place-items: center&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;First specify &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;grid&lt;/code&gt; as the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;display&lt;/code&gt; method, and then write &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;place-items: center&lt;/code&gt; on the same element. &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;place-items&lt;/code&gt; is a shorthand to set both &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;align-items&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;justify-items&lt;/code&gt; at once. By setting it to &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;center&lt;/code&gt;, both &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;align-items&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;justify-items&lt;/code&gt; are set to  &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;center&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;GAS&quot; syntax-guessed=&quot;&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-na&quot;&gt;.parent&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nl&quot;&gt;display:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;grid&lt;/span&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nl&quot;&gt;place-items:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;center&lt;/span&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-err&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;This enables the content to be perfectly centered within the parent, regardless of intrinsic size.&lt;/p&gt;

&lt;h2 id=&quot;02_the_deconstructed_pancake_flex_grow_shrink_basewidth&quot; data-text=&quot;02. The Deconstructed Pancake: flex: &lt;grow&gt; &lt;shrink&gt; &lt;baseWidth&gt;&quot; tabindex=&quot;-1&quot;&gt;02. The Deconstructed Pancake: &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;flex: &amp;lt;grow&amp;gt; &amp;lt;shrink&amp;gt; &amp;lt;baseWidth&amp;gt;&lt;/code&gt;&lt;/h2&gt;

&lt;figure&gt;
  &lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;
    &lt;source src=&quot;https://web.dev/static/articles/one-line-layouts/video/02-deconstructed-pancake-1.mp4&quot;&gt;
  &lt;/video&gt;
&lt;/figure&gt;

&lt;p&gt;Next we have the deconstructed pancake! This is a common layout for marketing sites, for example, which may have a row of 3 items, usually with an image, title, and then some text, describing some features of a product. On mobile, we&#39;ll want those to stack nicely, and expand as we increase the screen size. &lt;/p&gt;

&lt;p&gt;By using Flexbox for this effect, you won&#39;t need media queries to adjust the placement of these elements when the screen resizes.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/flex&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;flex&lt;/code&gt;&lt;/a&gt; shorthand stands for: &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;flex: &amp;lt;flex-grow&amp;gt; &amp;lt;flex-shrink&amp;gt; &amp;lt;flex-basis&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Because of this, if you want your boxes to fill out to their &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;flex-basis&amp;gt;&lt;/code&gt; size, shrink on smaller sizes, but not &lt;em&gt;stretch&lt;/em&gt; to fill any additional space, write: &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;flex: 0 1 &amp;lt;flex-basis&amp;gt;&lt;/code&gt;. In this case, your &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;flex-basis&amp;gt;&lt;/code&gt; is &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;150px&lt;/code&gt; so it looks like:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;GAS&quot; syntax-guessed=&quot;&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-na&quot;&gt;.parent&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nl&quot;&gt;display:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;flex&lt;/span&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-err&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-na&quot;&gt;.child&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nl&quot;&gt;flex:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;150&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;px&lt;/span&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-err&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;If you &lt;em&gt;do&lt;/em&gt; want the boxes to stretch and fill the space as they wrap to the next line, set the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;flex-grow&amp;gt;&lt;/code&gt; to &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1&lt;/code&gt;, so it would look like:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;GAS&quot; syntax-guessed=&quot;&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-na&quot;&gt;.parent&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nl&quot;&gt;display:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;flex&lt;/span&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-err&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-na&quot;&gt;.child&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nl&quot;&gt;flex:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;150&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;px&lt;/span&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-err&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;figure&gt;
  &lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;
    &lt;source src=&quot;https://web.dev/static/articles/one-line-layouts/video/02-deconstructed-pancake-2.mp4&quot;&gt;
  &lt;/video&gt;
&lt;/figure&gt;

&lt;p&gt;Now, as you increase or decrease the screen size,  these flex items both shrink and grow.&lt;/p&gt;

&lt;h2 id=&quot;03_sidebar_says_grid-template-columns_minmaxmin_max_…&quot; data-text=&quot;03. Sidebar Says: grid-template-columns: minmax(&lt;min&gt;, &lt;max&gt;) …)&quot; tabindex=&quot;-1&quot;&gt;03. Sidebar Says: &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;grid-template-columns: minmax(&amp;lt;min&amp;gt;, &amp;lt;max&amp;gt;) …)&lt;/code&gt;&lt;/h2&gt;

&lt;figure&gt;
  &lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;
    &lt;source src=&quot;https://web.dev/static/articles/one-line-layouts/video/03-sidebar-says.mp4&quot;&gt;
  &lt;/video&gt;
&lt;/figure&gt;

&lt;p&gt;This demo takes advantage of the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/minmax&quot;&gt;minmax&lt;/a&gt; function for grid layouts. What we&#39;re doing here is setting the minimum sidebar size to be &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;150px&lt;/code&gt;, but on larger screens, letting that stretch out to &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;25%&lt;/code&gt;. The sidebar will always take up &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;25%&lt;/code&gt; of its parent&#39;s horizontal space until that &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;25%&lt;/code&gt; becomes smaller than &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;150px&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Add this as a value of grid-template-columns with the following value:
&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;minmax(150px, 25%) 1fr&lt;/code&gt;. The item in the first column (the sidebar in this case) gets a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;minmax&lt;/code&gt; of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;150px&lt;/code&gt; at &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;25%&lt;/code&gt;, and the second item (the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;main&lt;/code&gt; section here) takes up the rest of the space as a single &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1fr&lt;/code&gt; track.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;GAS&quot; syntax-guessed=&quot;&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-na&quot;&gt;.parent&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nl&quot;&gt;display:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;grid&lt;/span&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nl&quot;&gt;grid-template-columns:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;minmax&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;150&lt;/span&gt;&lt;span class=&quot;devsite-syntax-no&quot;&gt;px&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;25&lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-no&quot;&gt;fr&lt;/span&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-err&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;h2 id=&quot;04_pancake_stack_grid-template-rows_auto_1fr_auto&quot; data-text=&quot;04. Pancake Stack: grid-template-rows: auto 1fr auto&quot; tabindex=&quot;-1&quot;&gt;04. Pancake Stack: &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;grid-template-rows: auto 1fr auto&lt;/code&gt;&lt;/h2&gt;

&lt;figure&gt;
  &lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;
    &lt;source src=&quot;https://web.dev/static/articles/one-line-layouts/video/04-pancake-stack.mp4&quot;&gt;
  &lt;/video&gt;
&lt;/figure&gt;

&lt;p&gt;Unlike the Deconstructed Pancake, this example does not wrap its children when the screen size changes. Commonly referred to as a &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/Layout_cookbook/Sticky_footers&quot;&gt;sticky footer&lt;/a&gt;, this layout is often used for both websites and apps, across mobile applications (the footer is commonly a toolbar), and websites (single page applications often use this global layout).&lt;/p&gt;

&lt;p&gt;Adding &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;display: grid&lt;/code&gt; to the component will give you a single column grid, however the main area will only be as tall as the content with the footer below it.&lt;/p&gt;

&lt;p&gt;To make the footer stick to the bottom,  add: &lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;GAS&quot; syntax-guessed=&quot;&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-na&quot;&gt;.parent&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nl&quot;&gt;display:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;grid&lt;/span&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nl&quot;&gt;grid-template-rows:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;auto&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-no&quot;&gt;fr&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-no&quot;&gt;auto&lt;/span&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-err&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;This sets the header and footer content to automatically take the size of its children, and applies the remaining space (&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1fr&lt;/code&gt;) to the main area, while the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;auto&lt;/code&gt; sized row will take the size of the minimum content of its children, so as that content increases in size, the row itself will grow to adjust.&lt;/p&gt;

&lt;h2 id=&quot;05_classic_holy_grail_layout_grid-template_auto_1fr_auto_auto_1fr_auto&quot; data-text=&quot;05. Classic Holy Grail Layout: grid-template: auto 1fr auto / auto 1fr auto&quot; tabindex=&quot;-1&quot;&gt;05. Classic Holy Grail Layout: &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;grid-template: auto 1fr auto / auto 1fr auto&lt;/code&gt;&lt;/h2&gt;

&lt;figure&gt;
  &lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;
    &lt;source src=&quot;https://web.dev/static/articles/one-line-layouts/video/05-holy-grail.mp4&quot;&gt;
  &lt;/video&gt;
&lt;/figure&gt;

&lt;p&gt;For this classic holy grail layout, there is a header, footer, left sidebar, right sidebar, and main content. It&#39;s similar to the previous layout, but now with sidebars!&lt;/p&gt;

&lt;p&gt;To write this entire grid using a single line of code, use the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;grid-template&lt;/code&gt; property. This enables you to set both the rows and columns at the same time.&lt;/p&gt;

&lt;p&gt;The property and value pair is: &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;grid-template: auto 1fr auto / auto 1fr auto&lt;/code&gt;. The slash in between the first and second space-separated lists is the break between rows and columns.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;GAS&quot; syntax-guessed=&quot;&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-na&quot;&gt;.parent&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nl&quot;&gt;display:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;grid&lt;/span&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nl&quot;&gt;grid-template:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;auto&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-no&quot;&gt;fr&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-no&quot;&gt;auto&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-no&quot;&gt;auto&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-no&quot;&gt;fr&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-no&quot;&gt;auto&lt;/span&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-err&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;As in the last example, where the header and footer had auto-sized content, here the left and right sidebar are automatically sized based on their children&#39;s intrinsic size. However, this time it is horizontal size (width) instead of vertical (height).&lt;/p&gt;

&lt;h2 id=&quot;06_12-span_grid_grid-template-columns_repeat12_1fr&quot; data-text=&quot;06. 12-Span Grid: grid-template-columns: repeat(12, 1fr)&quot; tabindex=&quot;-1&quot;&gt;06. 12-Span Grid: &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;grid-template-columns: repeat(12, 1fr)&lt;/code&gt;&lt;/h2&gt;

&lt;figure&gt;
  &lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;
    &lt;source src=&quot;https://web.dev/static/articles/one-line-layouts/video/06-12-span-1.mp4&quot;&gt;
  &lt;/video&gt;
&lt;/figure&gt;

&lt;p&gt;Next we have another classic: the 12-span grid. You can quickly write grids in CSS with the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;repeat()&lt;/code&gt; function. Using: &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;repeat(12, 1fr);&lt;/code&gt; for the grid template columns gives you 12 columns each of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1fr&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;GAS&quot; syntax-guessed=&quot;&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-na&quot;&gt;.parent&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nl&quot;&gt;display:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;grid&lt;/span&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nl&quot;&gt;grid-template-columns:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-na&quot;&gt;rep&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;eat&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-no&quot;&gt;fr&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-err&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-na&quot;&gt;.child-span-12&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nl&quot;&gt;grid-column:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;13&lt;/span&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-err&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;Now you have a 12 column track grid, we can place our children on the grid. One way to do this would be to place them using grid lines. For example, &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;grid-column: 1 / 13&lt;/code&gt; would span all the way from the first line to the last (13th) and span 12 columns. &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;grid-column: 1 / 5;&lt;/code&gt; would span the first four.&lt;/p&gt;

&lt;figure&gt;
  &lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;
    &lt;source src=&quot;https://web.dev/static/articles/one-line-layouts/video/06-12-span-2.mp4&quot;&gt;
  &lt;/video&gt;
&lt;/figure&gt;

&lt;p&gt;Another way to write this is by using the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;span&lt;/code&gt; keyword. With &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;span&lt;/code&gt;, you set the starting line and then how many columns to span into from that starting point. In this case, &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;grid-column: 1 / span 12&lt;/code&gt; would be equivalent to &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;grid-column: 1 / 13&lt;/code&gt;, and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;grid-column: 2 / span 6&lt;/code&gt; would be equivalent to &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;grid-column: 2 / 8&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;GAS&quot; syntax-guessed=&quot;&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-na&quot;&gt;.child-span-12&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nl&quot;&gt;grid-column:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;span&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-err&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;h2 id=&quot;07_ram_repeat_auto_minmax_grid-template-columnsauto-fit_minmaxbase_1fr&quot; data-text=&quot;07. RAM (Repeat, Auto, MinMax): grid-template-columns(auto-fit, minmax(&lt;base&gt;, 1fr))&quot; tabindex=&quot;-1&quot;&gt;07. RAM (Repeat, Auto, MinMax): &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;grid-template-columns(auto-fit, minmax(&amp;lt;base&amp;gt;, 1fr))&lt;/code&gt;&lt;/h2&gt;

&lt;figure&gt;
  &lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;
    &lt;source src=&quot;https://web.dev/static/articles/one-line-layouts/video/07-ram-1.mp4&quot;&gt;
  &lt;/video&gt;
&lt;/figure&gt;

&lt;p&gt;For this seventh example, combine some of the concepts you&#39;ve already learned about to create a responsive layout with automatically-placed and flexible children. Pretty neat. The key terms to remember here are &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;repeat&lt;/code&gt;, &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;auto-(fit|fill)&lt;/code&gt;, and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;minmax()&#39;&lt;/code&gt;, which you remember by the acronym RAM.&lt;/p&gt;

&lt;p&gt;All together, it looks like:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;GAS&quot; syntax-guessed=&quot;&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-na&quot;&gt;.parent&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nl&quot;&gt;display:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;grid&lt;/span&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nl&quot;&gt;grid-template-columns:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-na&quot;&gt;rep&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;eat&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-no&quot;&gt;auto-fit&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-no&quot;&gt;minmax&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;150&lt;/span&gt;&lt;span class=&quot;devsite-syntax-no&quot;&gt;px&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-no&quot;&gt;fr&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-err&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;You are using repeat again, but this time, using the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;auto-fit&lt;/code&gt; keyword instead of an explicit numeric value. This enables auto-placement of these child elements. These children also have a base minimum value of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;150px&lt;/code&gt; with a maximum value &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1fr&lt;/code&gt;, meaning on smaller screens, they will take up the full &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1fr&lt;/code&gt; width, and as they reach &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;150px&lt;/code&gt; wide each, they will start to flow onto the same line.&lt;/p&gt;

&lt;p&gt;With &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;auto-fit&lt;/code&gt;, the boxes will stretch as their horizontal size exceeds 150px to fill the entire remaining space. However, if you change this to &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;auto-fill&lt;/code&gt;, they will not stretch when their base size in the minmax function is exceeded:&lt;/p&gt;

&lt;figure&gt;
  &lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;
    &lt;source src=&quot;https://web.dev/static/articles/one-line-layouts/video/07-ram-2.mp4&quot;&gt;
  &lt;/video&gt;
&lt;/figure&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;GAS&quot; syntax-guessed=&quot;&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-na&quot;&gt;.parent&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nl&quot;&gt;display:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;grid&lt;/span&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nl&quot;&gt;grid-template-columns:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-na&quot;&gt;rep&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;eat&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-no&quot;&gt;auto-fill&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-no&quot;&gt;minmax&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;150&lt;/span&gt;&lt;span class=&quot;devsite-syntax-no&quot;&gt;px&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-no&quot;&gt;fr&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-err&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;h2 id=&quot;08_line_up_justify-content_space-between&quot; data-text=&quot;08. Line Up: justify-content: space-between&quot; tabindex=&quot;-1&quot;&gt;08. Line Up: &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;justify-content: space-between&lt;/code&gt;&lt;/h2&gt;

&lt;figure&gt;
  &lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;
    &lt;source src=&quot;https://web.dev/static/articles/one-line-layouts/video/08-lineup.mp4&quot;&gt;
  &lt;/video&gt;
&lt;/figure&gt;

&lt;p&gt;For the next layout, the main point to demonstrate here is &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;justify-content: space-between&lt;/code&gt;, which places the first and last child elements at the edges of their bounding box, with the remaining space evenly distributed between the elements. For these cards, they are placed in a Flexbox display mode, with the direction being set to column using &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;flex-direction: column&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This places the title, description, and image block in a vertical column inside of the parent card. Then, applying &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;justify-content: space-between&lt;/code&gt; anchors the first (title) and last (image block) elements to the edges of the flexbox, and the descriptive text in between those gets placed with equal spacing to each endpoint.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;GAS&quot; syntax-guessed=&quot;&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-na&quot;&gt;.parent&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nl&quot;&gt;display:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;flex&lt;/span&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nl&quot;&gt;flex-direction:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;column&lt;/span&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nl&quot;&gt;justify-content:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;space-between&lt;/span&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-err&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;h2 id=&quot;09_clamping_my_style_clampmin_actual_max&quot; data-text=&quot;09. Clamping My Style: clamp(&lt;min&gt;, &lt;actual&gt;, &lt;max&gt;)&quot; tabindex=&quot;-1&quot;&gt;09. Clamping My Style: &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;clamp(&amp;lt;min&amp;gt;, &amp;lt;actual&amp;gt;, &amp;lt;max&amp;gt;)&lt;/code&gt;&lt;/h2&gt;

&lt;figure&gt;
  &lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;
    &lt;source src=&quot;https://web.dev/static/articles/one-line-layouts/video/09-clamping.mp4&quot;&gt;
  &lt;/video&gt;
&lt;/figure&gt;

&lt;p&gt;Here&#39;s where we get into some techniques that have really exciting implications for layouts and responsive UI design. In this demo, you are setting the width using clamp like so: &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;width: clamp(&amp;lt;min&amp;gt;, &amp;lt;actual&amp;gt;, &amp;lt;max&amp;gt;)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This sets an absolute min and max size, and an actual size. With values, that can look like:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;GAS&quot; syntax-guessed=&quot;&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-na&quot;&gt;.parent&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nl&quot;&gt;width:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;clamp&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;23&lt;/span&gt;&lt;span class=&quot;devsite-syntax-no&quot;&gt;ch&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;devsite-syntax-no&quot;&gt;ch&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-err&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;The minimum size here is &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;23ch&lt;/code&gt; or 23 character units, and the maximum size is &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;46ch&lt;/code&gt;, 46 characters. &lt;a href=&quot;https://meyerweb.com/eric/thoughts/2018/06/28/what-is-the-css-ch-unit/&quot;&gt;Character width units&lt;/a&gt; are based on the font size of the element (specifically the width of the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;0&lt;/code&gt; glyph). The &#39;actual&#39; size is 50%, which represents 50% of this element&#39;s parent width.&lt;/p&gt;

&lt;p&gt;What the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;clamp()&lt;/code&gt; function is doing here is enabling this element to retain a 50% width &lt;em&gt;until&lt;/em&gt; 50% is either greater than &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;46ch&lt;/code&gt; (on wider viewports), or smaller than &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;23ch&lt;/code&gt; (on smaller viewports). You can see that as I stretch and shrink the parent size, the width of this card increases to its clamped maximum point and decreases to its clamped minimum. It then stays centered in the parent since we&#39;ve applied additional properties to center it. This enables more legible layouts, as the text won&#39;t be too wide (above &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;46ch&lt;/code&gt;) or too squished and narrow (less than &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;23ch&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;This is also a great way to implement responsive typography. For example, you could write: &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;font-size: clamp(1.5rem, 20vw, 3rem)&lt;/code&gt;. In this case, the font-size of a headline would always stay clamped between &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1.5rem&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;3rem&lt;/code&gt; but would grow and shrink based on the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;20vw&lt;/code&gt; actual value to fit the width of of the viewport.&lt;/p&gt;

&lt;p&gt;This is a great technique to ensure legibility with a minimum and maximum size value.&lt;/p&gt;

&lt;h2 id=&quot;10_respect_for_aspect_aspect-ratio_width_height&quot; data-text=&quot;10. Respect for Aspect: aspect-ratio: &lt;width&gt; / &lt;height&gt;&quot; tabindex=&quot;-1&quot;&gt;10. Respect for Aspect: &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;aspect-ratio: &amp;lt;width&amp;gt; / &amp;lt;height&amp;gt;&lt;/code&gt;&lt;/h2&gt;

&lt;figure&gt;
  &lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;
    &lt;source src=&quot;https://web.dev/static/articles/one-line-layouts/video/10-aspectratio.mp4&quot;&gt;
  &lt;/video&gt;
&lt;/figure&gt;

&lt;p&gt;And finally, this last layout solves a frequently met problem of maintaining the aspect ratio of an image.&lt;/p&gt;

&lt;p&gt;With the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;aspect-ratio&lt;/code&gt; property, as I resize the card, the green visual block maintains this 16 x 9 aspect ratio. We are Respecting the Aspect Ratio with &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;aspect-ratio: 16 / 9&lt;/code&gt;. &lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;GAS&quot; syntax-guessed=&quot;&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-na&quot;&gt;.video&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nl&quot;&gt;aspect-ratio:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-err&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;To maintain a 16 x 9 aspect ratio without this property, you&#39;d need to use a &lt;a href=&quot;https://css-tricks.com/aspect-ratio-boxes/&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;padding-top&lt;/code&gt; hack&lt;/a&gt; and give it a padding of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;56.25%&lt;/code&gt; to set a top-to-width ratio. We will soon have a property for this to avoid the hack and the need to calculate the percentage. You can make a square with &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1 / 1&lt;/code&gt; ratio, a 2 to 1 ratio with &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;2 / 1&lt;/code&gt;, and really just anything you need for this image to scale with a set size ratio.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;GAS&quot; syntax-guessed=&quot;&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-na&quot;&gt;.square&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nl&quot;&gt;aspect-ratio:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-err&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;This feature is a good one to know about as it resolves a lot of developer strife that I have faced many times myself, especially when it comes to video and iframes.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot; data-text=&quot;Conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Thank you for following this journey through 10 powerful lines of CSS. To learn more, watch &lt;a href=&quot;https://youtu.be/qm0IfG1GyZU&quot;&gt;the full video&lt;/a&gt;, and try out &lt;a href=&quot;https://1linelayouts.glitch.me/&quot;&gt;the demos&lt;/a&gt; yourself.&lt;/p&gt;

  

  
</description><link>https://web.dev/articles/one-line-layouts?hl=en</link><guid isPermaLink="false">https://web.dev/articles/one-line-layouts?hl=en</guid><pubDate>Mon, 15 Dec 2025 08:00:00 GMT</pubDate></item><item><title>Color themes with Baseline CSS features</title><description>&lt;p&gt;&lt;style&gt;
  .wd-author__links {
    display: flex;
  }
  &lt;/style&gt;

&lt;/p&gt;

&lt;p class=&quot;wd-pubdates&quot; style=&quot;font-size: 90%; border-bottom: 1px solid var(--devsite-ref-palette--grey200);color: var(--devsite-ref-palette--blue-grey700);&quot;&gt;
  Published: December 11, 2025
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;

&lt;div class=&quot;wd-embed&quot; style=&quot;height: 640px; width: 100%&quot;&gt;
   &lt;iframe allow=&quot;camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;&quot; loading=&quot;lazy&quot; src=&quot;https://codepen.io/web-dot-dev/embed/OPNdOeY?height=640&amp;amp;theme-id=light&amp;amp;default-tab=result&amp;amp;editable=true&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot; title=&quot;Pen XJXEOEg by web-dot-dev on Codepen&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
   &lt;/iframe&gt;
&lt;/div&gt;
&lt;aside class=&quot;note&quot;&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;span&gt; This demo is also &lt;a href=&quot;https://github.com/GoogleChromeLabs/baseline-demos/tree/main/src/color-theme&quot;&gt;available on GitHub&lt;/a&gt;.&lt;/span&gt;&lt;/aside&gt;
&lt;p&gt;So, you have a site that you want to build or redesign. Maybe you have a few core colors in mind, and you&#39;re thinking about how to quickly implement a theme based on those colors.&lt;/p&gt;

&lt;p&gt;You&#39;ll need your primary color, but also colors for actions, hover states, errors, and colors for other user interface needs. Then what about light and dark mode options? Suddenly there&#39;s lots of colors you&#39;ll need, and it can feel overwhelming.&lt;/p&gt;

&lt;p&gt;The good news is that when it comes to building a palette relative to the color tokens that define your site and switching between color modes, Baseline features can do a lot of the heavy lifting for you. You can explore some of these techniques in the featured demo, a color themed playlist on the fictional Baseline Radio site.&lt;/p&gt;

&lt;h2 id=&quot;build_a_base_with_relative_colors&quot; data-text=&quot;Build a base with relative colors&quot; tabindex=&quot;-1&quot;&gt;Build a base with relative colors&lt;/h2&gt;

&lt;p&gt;If you have an idea for a primary color for your theme, with some basic color theory and the CSS &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/CSS_colors/Relative_colors&quot;&gt;relative color syntax&lt;/a&gt;, you can quickly start generating a palette of colors to use in your theme.&lt;/p&gt;

&lt;p&gt;Say your base color is a shade of teal, which you can first define in your preferred color format. Then you can use any color function to create new colors relative to your base color:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--base-color&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;oklch&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;43.7&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;0.075&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;224&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;--base-color&lt;/code&gt; custom property is made using the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/Reference/Values/color_value/oklch&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;oklch()&lt;/code&gt;&lt;/a&gt; color function. OkLCh is the cylindrical form of the Oklab color space, and defines values for three channels: &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;L&lt;/code&gt; (lightness), &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;C&lt;/code&gt; (chroma), &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;H&lt;/code&gt; (hue), plus an optional alpha channel to control transparency.&lt;/p&gt;

&lt;p&gt;&lt;devsite-iframe&gt;&lt;iframe src=&quot;https://web.dev/frame/articles/baseline-in-action-color-theme/index_192bfe4cf67662b12d9ab47c0bf0bc9377db78174687f956836b4acc6d7d006c.frame&quot; class=&quot;framebox inherit-locale &quot; allow=&quot;clipboard-write https://web-dot-devsite-v2-prod-3p.appspot.com&quot; allowfullscreen=&quot;&quot; is-upgraded=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;&lt;/devsite-iframe&gt;&lt;/p&gt;

&lt;p&gt;OkLCh is a good format for this type of color manipulation, as it&#39;s designed to provide perceptual uniformity. For example, if you adjust just the hue of a color, the resulting color should have a similar perceived lightness and chroma to the original color. This is especially useful in avoiding unexpected contrast issues.&lt;/p&gt;

&lt;p&gt;Keeping the same lightness and chroma from your &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;--base-color&lt;/code&gt;, you could adjust the hue by 120 degrees in both directions for a triadic palette.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-c&quot;&gt;/* ... */&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--triadic-color-primary&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;oklch&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--base-color&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;120&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--triadic-color-secondary&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;oklch&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--base-color&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;120&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;As shown here, the relative color syntax uses a color function that references an origin color (&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;--base-color&lt;/code&gt; in this example) with the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;from&lt;/code&gt; keyword, and adjusts the color space&#39;s respective channels based on the chosen output color, which in this case will also be OkLCh.&lt;/p&gt;

&lt;p&gt;The resulting output gives you a dark pink for the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;--accent-color&lt;/code&gt;, and a shade of gold to use for the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;--highlight-color&lt;/code&gt;, both with the same lightness and chroma as the original &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;--base-color&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-c&quot;&gt;/* ... */&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--accent-color&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--triadic-color-primary&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--highlight-color&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--triadic-color-secondary&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;&lt;devsite-iframe&gt;&lt;iframe src=&quot;https://web.dev/frame/articles/baseline-in-action-color-theme/index_330d6ee70726677e6753067030ea3d872a6b0feee28395fb89eeb81677893be3.frame&quot; class=&quot;framebox inherit-locale &quot; allow=&quot;clipboard-write https://web-dot-devsite-v2-prod-3p.appspot.com&quot; allowfullscreen=&quot;&quot; is-upgraded=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;&lt;/devsite-iframe&gt;&lt;/p&gt;
&lt;aside class=&quot;note&quot;&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;span&gt; In this demo, both the origin color (&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;--base-color&lt;/code&gt;) and the output colors are in the OkLCh color space. You could define your origin color in any format, though, and the output color will be in whichever color space you use for the manipulation.&lt;/span&gt;&lt;/aside&gt;&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-c&quot;&gt;/* Input color in the rgb color space*/&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--base-color&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;teal&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-w&quot;&gt;     &lt;/span&gt;&lt;span class=&quot;devsite-syntax-c&quot;&gt;/* Output color in oklch. Computes to oklch(0.543123 0.0927099 314.769) */&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;     &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--triadic-color-primary&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;oklch&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--base-color&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;120&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;div class=&quot;wd-embed&quot; style=&quot;height: 384px; width: 100%&quot;&gt;
   &lt;iframe allow=&quot;camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;&quot; loading=&quot;lazy&quot; src=&quot;https://codepen.io/web-dot-dev/embed/NPNoXmp?height=384&amp;amp;theme-id=light&amp;amp;default-tab=result&amp;amp;editable=true&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot; title=&quot;Pen XJXEOEg by web-dot-dev on Codepen&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
   &lt;/iframe&gt;
&lt;/div&gt;

&lt;p&gt;A complementary color would add 180 degrees to the hue angle.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-c&quot;&gt;/* ... */&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--complement-color&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;oklch&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--base-color&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;180&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--border-highlight&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--complement-color&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;For a hover state in your UI, you might want to output a lighter version of a particular color. This would mean increasing the value of the lightness channel. For an active state, you might want to add transparency by adjusting the alpha channel, or darkening it by decreasing the value of the lightness channel.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-c&quot;&gt;/* Darken the --base-color by 15% */&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--base-color-darkened&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;oklch&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--base-color&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;0.85&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-c&quot;&gt;/* Assign this color a meaningful variable name */&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--action-color&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--base-color-darkened&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-c&quot;&gt;/* Lighten the --action-color by 15% */&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--action-color-light&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;oklch&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--action-color&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;1.15&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-c&quot;&gt;/* Darken the --action-color by 10% */&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--action-color-dark&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;oklch&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--action-color&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;0.9&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;Here, we are deriving the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;--action-color&lt;/code&gt; from the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;--base-color&lt;/code&gt;, and using it for buttons and links. The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;--action-color&lt;/code&gt; has two variants—lighter and darker—that would still apply even if the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;--action-color&lt;/code&gt; was changed to be relative to another color different from the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;--base-color&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can adjust channels by using a math function like &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;calc()&lt;/code&gt; or replacing the channel entirely with a new value. Unchanged channels are represented by their respective letters (for example, &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;l&lt;/code&gt; for an unchanged lightness value).&lt;/p&gt;

&lt;h2 id=&quot;mix_colors_with_color-mix&quot; data-text=&quot;Mix colors with color-mix()&quot; tabindex=&quot;-1&quot;&gt;Mix colors with &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;color-mix()&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;For other color variants, you could take a similar approach and adjust other channels of the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;--base-color&lt;/code&gt; custom property. Or use &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;color-mix()&lt;/code&gt; to add hints of the base color to other aspects of your design.&lt;/p&gt;

&lt;p&gt;&lt;devsite-iframe&gt;&lt;iframe src=&quot;https://web.dev/frame/articles/baseline-in-action-color-theme/index_0ecfc23fade74112d405fcaf57766dfdee6e8b72e854940abd3501b3de841b6e.frame&quot; class=&quot;framebox inherit-locale &quot; allow=&quot;clipboard-write https://web-dot-devsite-v2-prod-3p.appspot.com&quot; allowfullscreen=&quot;&quot; is-upgraded=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;&lt;/devsite-iframe&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;--border-color&lt;/code&gt; is a mix of the base color and the named color &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;grey&lt;/code&gt;, interpolated in the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;oklab&lt;/code&gt; color space. When used as a color interpolation method, this provides &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/Reference/Values/color_value/color-mix#choosing_a_color_space&quot;&gt;perceptually uniform&lt;/a&gt; results.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--base-mix-grey-50&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;color-mix&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;oklab&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--base-color&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;),&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;grey&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--border-color&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--base-mix-grey-&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;By default, this would be 50% of each color, but you can make either color more or less prominent by adjusting its percentage weight.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--background-mix-base-80&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;color-mix&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;oklab&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--background-color&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--base-color&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--surface-light&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--background-mix-base-&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;An alternative to adding more color to an element would be to adjust its chroma channel using the relative color syntax. The border of the text inputs in the contact form have a slightly more colorful border when in focus.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;data-input&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;*=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--focus-ring&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;transparent&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-c&quot;&gt;/* ... */&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&amp;amp;&lt;span class=&quot;devsite-syntax-err&quot;&gt;:focus&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--focus-ring&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;oklch&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--border-color&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;0.1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-err&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;h2 id=&quot;opt_in_to_light_and_dark_modes&quot; data-text=&quot;Opt in to light and dark modes&quot; tabindex=&quot;-1&quot;&gt;Opt in to light and dark modes&lt;/h2&gt;

&lt;p&gt;Once you have a set of colors to work with, you&#39;ll want an efficient way to apply different colors for light and dark modes.&lt;/p&gt;

&lt;h3 id=&quot;signal_support_for_light_and_dark_themes_with_the_color-scheme_property&quot; data-text=&quot;Signal support for light and dark themes with the color-scheme property&quot; tabindex=&quot;-1&quot;&gt;Signal support for light and dark themes with the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;color-scheme&lt;/code&gt; property&lt;/h3&gt;

&lt;p&gt;You can instantly tell the browser that your site can be viewed in &quot;light&quot;, &quot;dark&quot;, or both modes with the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;color-scheme&lt;/code&gt; property. This property tells the browser in which color schemes an element can be comfortably rendered.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;   &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;color-scheme&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;light&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;dark&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;&lt;devsite-iframe&gt;&lt;iframe src=&quot;https://web.dev/frame/articles/baseline-in-action-color-theme/index_ce3e28a5ebafe9facab42afe50f0503ec8b898d37db9104f95258e7ccab40309.frame&quot; class=&quot;framebox inherit-locale &quot; allow=&quot;clipboard-write https://web-dot-devsite-v2-prod-3p.appspot.com&quot; allowfullscreen=&quot;&quot; is-upgraded=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;&lt;/devsite-iframe&gt;&lt;/p&gt;

&lt;p&gt;Setting &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;color-scheme: light dark&lt;/code&gt; on the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;:root&lt;/code&gt; pseudo-element or the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;html&lt;/code&gt; element:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tells the browser that your page supports being viewed in light or dark mode.&lt;/li&gt;
&lt;li&gt;Changes the default colors of the browser user interface to match the respective operating system setting.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To give user agents earlier notice that your page supports light and dark modes, you can also signal support for color scheme switching by adding a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; element in the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;head&amp;gt;&lt;/code&gt; of the document.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;HTML&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;head&amp;gt;
  &amp;lt;!-- ... --&amp;gt;
   &amp;lt;meta name=&quot;color-scheme&quot; content=&quot;light dark&quot;&amp;gt;
&amp;lt;/head&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;&lt;aside class=&quot;note&quot;&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;span&gt; Read more about &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;color-scheme&lt;/code&gt; and different color modes in the &lt;a href=&quot;https://web.dev/learn/design/media-features#setting_the_color_scheme_for_ui_elements&quot;&gt;media features module&lt;/a&gt; of &lt;a href=&quot;https://web.dev/learn/design&quot;&gt;Learn Responsive Design&lt;/a&gt;.&lt;/span&gt;&lt;/aside&gt;
&lt;h3 id=&quot;set_light_and_dark_variants_with_the_light-dark_function&quot; data-text=&quot;Set &amp;quot;light&amp;quot; and &amp;quot;dark&amp;quot; variants with the light-dark() function&quot; tabindex=&quot;-1&quot;&gt;Set &quot;light&quot; and &quot;dark&quot; variants with the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;light-dark()&lt;/code&gt; function&lt;/h3&gt;

&lt;p&gt;As an author, you might be used to setting a page&#39;s colors with a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;prefers-color-scheme&lt;/code&gt; &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;@media&lt;/code&gt; query.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;media&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;prefers-color-scheme&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;light&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--background-color&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;oklch&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;95.5&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;162&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--text-color&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;black&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;media&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;prefers-color-scheme&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;dark&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--background-color&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;oklch&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;22.635&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;0.01351&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;291.83&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--text-color&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;white&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;This works great for colors and styles controlled by the &lt;em&gt;author&lt;/em&gt;, but as mentioned in the previous section, you would still need &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;color-scheme&lt;/code&gt; to update the colors of the browser UI.&lt;/p&gt;

&lt;p&gt;Changing the page&#39;s colors with a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;prefers-color-scheme&lt;/code&gt; query also means some code duplication since you have to define colors for each mode separately.&lt;/p&gt;

&lt;p&gt;With &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;color-scheme&lt;/code&gt; set on the overall page (or specific elements) though, you can use the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;light-dark()&lt;/code&gt; function to set colors for each mode in one line of code.&lt;/p&gt;

&lt;p&gt;&lt;devsite-iframe&gt;&lt;iframe src=&quot;https://web.dev/frame/articles/baseline-in-action-color-theme/index_af7a8d4fff2f9da511b7cc9c27bdd838fc825d6bca9d34fe9ad825568c312b92.frame&quot; class=&quot;framebox inherit-locale &quot; allow=&quot;clipboard-write https://web-dot-devsite-v2-prod-3p.appspot.com&quot; allowfullscreen=&quot;&quot; is-upgraded=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;&lt;/devsite-iframe&gt;&lt;/p&gt;

&lt;p&gt;The function accepts two colors. The first is used when the color scheme is set to &quot;light&quot;, and the second for when the color scheme is set to &quot;dark&quot;.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;color-scheme&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;light&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;dark&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-c&quot;&gt;/* Color custom property values for both light and dark modes */&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--base-color&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;light-dark&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;oklch&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;43.7&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;0.075&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;224&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;),&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;oklch&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;89.2&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;0.069&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;224&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--background-color&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;light-dark&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;oklch&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;95.5&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;162&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;),&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;oklch&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;22.635&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;0.01351&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;291.83&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--accent-color&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;oklch&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--base-color&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;120&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--active-color&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;light-dark&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--action-color-light&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;),&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--action-color-dark&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-c&quot;&gt;/* ... */&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;As with any custom property, the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;light-dark()&lt;/code&gt; settings for your colors can be set globally or within specific components and then used elsewhere as needed.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-c&quot;&gt;/* custom property usage */&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-nt&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--background-color&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-c&quot;&gt;/* ... */&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nd&quot;&gt;any-link&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-c&quot;&gt;/* ... */&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;text-decoration-color&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--accent-color&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;h2 id=&quot;give_users_control_with_built-in_theme_switcher&quot; data-text=&quot;Give users control with built-in theme switcher&quot; tabindex=&quot;-1&quot;&gt;Give users control with built-in theme switcher&lt;/h2&gt;

&lt;p&gt;It&#39;s great to have a theme that adapts to a user&#39;s default system or browser color preferences, but you can take it a step further and give viewers of your site the ability to override these default color preferences.&lt;/p&gt;

&lt;p&gt;If you build a theme toggle which updates the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;data-scheme&lt;/code&gt; attribute on the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;html&amp;gt;&lt;/code&gt; element you can use the same attribute to change the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;color-scheme&lt;/code&gt; with CSS.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;color-scheme&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;light&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;dark&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&amp;amp;&lt;span class=&quot;devsite-syntax-err&quot;&gt;[data-scheme=&quot;light&quot;]&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;color-scheme&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;light&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&amp;amp;&lt;span class=&quot;devsite-syntax-o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;data-scheme&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;dark&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;color-scheme&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;dark&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&amp;amp;&lt;span class=&quot;devsite-syntax-o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;data-scheme&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;green&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--base-color-light&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;oklch&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;48.052&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;0.11875&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;151.945&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--base-color-dark&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;oklch&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;92.124&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;0.13356&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;151.558&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;color-scheme&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;light&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;dark&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;   &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-err&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;data-scheme=&quot;light&quot;&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;data-scheme=&quot;dark&quot;&lt;/code&gt; show the page only in their respective color modes. &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;data-scheme=&quot;green&quot;&lt;/code&gt; can be viewed in either mode and also changes the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;--base-color&lt;/code&gt; to a shade of green, which gives you an entirely new palette since most other colors are based on the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;--base-color&lt;/code&gt;.&lt;/p&gt;
&lt;aside class=&quot;note&quot;&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;span&gt; Read more about toggling themes in the web.dev article &quot;Building a theme switch component&quot;](/articles/building/a-theme-switch-component) on web.dev&lt;/span&gt;&lt;/aside&gt;
&lt;h2 id=&quot;register_custom_properties_with_property&quot; data-text=&quot;Register custom properties with @property&quot; tabindex=&quot;-1&quot;&gt;Register custom properties with &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;@property&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;So far, colors in the demo have been set as standard custom properties. You can also register properties with the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;@property&lt;/code&gt; rule to tap into the benefits that come with type checking.&lt;/p&gt;

&lt;p&gt;Since the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;--base-color&lt;/code&gt; is used as the base of so many of the other colors in the interface, it could be good to make sure it&#39;s always a color and has a fallback value.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;property&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;--base-color-light&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;syntax&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;&amp;lt;color&amp;gt;&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;inherits&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;initial-value&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;oklch&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;43&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nc&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nc&quot;&gt;075&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;224&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;property&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;--base-color-dark&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;syntax&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;&amp;lt;color&amp;gt;&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;inherits&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;initial-value&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;oklch&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;89&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nc&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nc&quot;&gt;069&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;224&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-nt&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--base-color&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;light-dark&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--base-color-light&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;),&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--base-color-dark&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;This way, if the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;--base-color&lt;/code&gt; is inadvertently changed to an invalid value, it would always fallback to its &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;initial-value&lt;/code&gt; set with the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;@property&lt;/code&gt; rule.&lt;/p&gt;

&lt;p&gt;&lt;devsite-iframe&gt;&lt;iframe src=&quot;https://web.dev/frame/articles/baseline-in-action-color-theme/index_bf9288640d1d3bbfb2c4ec7366d0357fd2f4e5cab541aca8e31ab7421389adf4.frame&quot; class=&quot;framebox inherit-locale &quot; allow=&quot;clipboard-write https://web-dot-devsite-v2-prod-3p.appspot.com&quot; allowfullscreen=&quot;&quot; is-upgraded=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;&lt;/devsite-iframe&gt;&lt;/p&gt;

&lt;p&gt;Registering certain properties this way also allows for smoothly animating colors in a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;linear-gradient()&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nc&quot;&gt;main-heading&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;linear-gradient&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;oklch&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;90&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;deg&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--text-color&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;oklch&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--base-color&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--header-hue&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)));&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;background-clip&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;transparent&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;animation&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;header-hue-switch&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;ease-in-out&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;infinite&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;alternate&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;.main-heading&lt;/code&gt; has a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;linear-gradient()&lt;/code&gt; background that&#39;s shown through the transparent text with the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;background-clip&lt;/code&gt; property.&lt;/p&gt;

&lt;p&gt;A portion of the text shows a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;hue&lt;/code&gt; that, using the relative color syntax, animates from a channel value of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;26.67&lt;/code&gt; to &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;277&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;keyframes&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;header-hue-switch&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--header-hue&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;26.67&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--header-hue&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;277&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;With a registered &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;--header-hue&lt;/code&gt; custom property, this animation is able to happen smoothly since the browser knows that this custom property is a number.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;property&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;--header-hue&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;syntax&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;&amp;lt;number&amp;gt;&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;inherits&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;initial-value&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;With an unregistered custom property, the browser wouldn&#39;t know the data type of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;--header-hue&lt;/code&gt;, so the transition to a number would be a &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/Guides/Animations/Animatable_properties#discrete&quot;&gt;discrete animation&lt;/a&gt;, which would jump between states without gradual interpolation.&lt;/p&gt;

&lt;h2 id=&quot;wrap_up&quot; data-text=&quot;Wrap up&quot; tabindex=&quot;-1&quot;&gt;Wrap up&lt;/h2&gt;

&lt;p&gt;New Baseline tools can help you quickly build an adjustable color palette and make creating color variables a more efficient process. You&#39;ll still have to agonize over the endless color options and combinations yourself, though.&lt;/p&gt;

&lt;p&gt;Creating your palette dynamically like this gives you flexibility. If you need to change the base color for branding, you can just update the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;--base-color&lt;/code&gt;, and the rest of the theme will flow from that. Or, if you add music playback capabilities, you could decide that you want to dynamically change the base color to match the currently playing song.&lt;/p&gt;

&lt;h2 id=&quot;credits&quot; data-text=&quot;Credits&quot; tabindex=&quot;-1&quot;&gt;Credits&lt;/h2&gt;

&lt;p&gt;Theme switcher logic adapted from Adam Argyle&#39;s &lt;a href=&quot;https://web.dev/articles/building/a-theme-switch-component&quot;&gt;Theme switch component&lt;/a&gt;.&lt;/p&gt;
  

  
</description><link>https://web.dev/articles/baseline-in-action-color-theme?hl=en</link><guid isPermaLink="false">https://web.dev/articles/baseline-in-action-color-theme?hl=en</guid><pubDate>Thu, 11 Dec 2025 08:00:00 GMT</pubDate></item><item><title>Optimize Time to First Byte</title><description>&lt;p&gt;Learn how to optimize for the Time to First Byte metric.&lt;/p&gt;

&lt;p&gt;&lt;style&gt;
  .wd-author__links {
    display: flex;
  }
  &lt;/style&gt;

&lt;/p&gt;&lt;p&gt;&lt;/p&gt;

&lt;p&gt;
&lt;/p&gt;&lt;p class=&quot;wd-pubdates&quot; style=&quot;font-size: 90%; border-bottom: 1px solid var(--devsite-ref-palette--grey200);color: var(--devsite-ref-palette--blue-grey700);&quot;&gt;
  Published: January 19, 2023, Last updated: November 28, 2025
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://web.dev/articles/ttfb&quot;&gt;Time to First Byte (TTFB)&lt;/a&gt; is a foundational web performance metric that precedes every other meaningful user experience metric such as &lt;a href=&quot;https://web.dev/articles/fcp&quot;&gt;First Contentful Paint (FCP)&lt;/a&gt; and &lt;a href=&quot;https://web.dev/articles/lcp&quot;&gt;Largest Contentful Paint (LCP)&lt;/a&gt;. This means that high TTFB values add time to the metrics that follow it.&lt;/p&gt;

&lt;p&gt;It&#39;s recommended that your server responds to navigation requests quickly enough so that the &lt;strong&gt;75th percentile&lt;/strong&gt; of users experience an FCP &lt;a href=&quot;https://web.dev/articles/fcp#what_is_a_good_fcp_score&quot;&gt;within the &quot;good&quot; threshold&lt;/a&gt;. As a rough guide, most sites should strive to have a TTFB of &lt;strong&gt;0.8 seconds or less&lt;/strong&gt;.&lt;/p&gt;

&lt;figure&gt;
  &lt;picture&gt;
    &lt;source srcset=&quot;https://web.dev/static/articles/optimize-ttfb/image/good-ttfb-values.svg&quot; media=&quot;(min-width: 640px)&quot; width=&quot;800&quot; height=&quot;200&quot;&gt;
    &lt;img src=&quot;https://web.dev/static/articles/optimize-ttfb/image/good-ttfb-values-are-08-cf9b35258c90c.svg&quot; alt=&quot;Good TTFB values are 0.8 seconds or less, poor values are greater than 1.8 seconds, and anything in between needs improvement&quot; width=&quot;640&quot; height=&quot;480&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;/picture&gt;
  &lt;figcaption&gt;Good TTFB values are 0.8 seconds or less, poor values are greater than 1.8 seconds&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;aside class=&quot;key-point&quot;&gt;
  &lt;p&gt;
    &lt;b&gt;Key point:&lt;/b&gt; TTFB is not a &lt;a href=&quot;https://web.dev/articles/vitals&quot;&gt;Core Web Vitals&lt;/a&gt; metric, so it&#39;s not absolutely necessary that sites meet the &quot;good&quot; TTFB threshold, provided that it doesn&#39;t impede their ability to score well on the metrics that matter.
  &lt;/p&gt;
  &lt;p&gt;
    Websites vary in how they deliver content. A low TTFB is crucial for getting markup out to the client as soon as possible. However, if a website delivers the initial markup quickly, but that markup then requires JavaScript to populate it with meaningful content—as is the the case with Single Page Applications (SPAs)—then achieving the lowest possible TTFB is especially important so that the client-rendering of markup can occur sooner.
  &lt;/p&gt;
  &lt;p&gt;
    Conversely, a server-rendered site that requires less client-side work could have a higher TTFB, but better FCP and LCP values than an entirely client-rendered experience. This is why the TTFB thresholds are a rough guide and should be weighed against how your site delivers its core content.
  &lt;/p&gt;
&lt;/aside&gt;

&lt;h2 id=&quot;how_to_measure_ttfb&quot; data-text=&quot;How to Measure TTFB&quot; tabindex=&quot;-1&quot;&gt;How to Measure TTFB&lt;/h2&gt;

&lt;p&gt;Before you can optimize TTFB, you need to observe how it affects your website&#39;s users. You should rely on &lt;a href=&quot;https://web.dev/lab-and-field-data-differences#field-data&quot;&gt;field data&lt;/a&gt; as a primary source of observing TTFB as it affected by redirects, whereas lab-based tools are often measured using the final URL therefore missing this extra delay.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://pagespeed.web.dev/&quot;&gt;PageSpeed Insights&lt;/a&gt; is one way to get both field and lab information for public websites that are available in the &lt;a href=&quot;https://developer.chrome.com/docs/crux&quot;&gt;Chrome User Experience Report&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;TTFB for real users is shown in the top &lt;strong&gt;Discover what your real users are experiencing&lt;/strong&gt; section:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/optimize-ttfb/image/psi-real-user-data-updated.png&quot; alt=&quot;PageSpeed Insights includes field data for the TTFB metric.&quot; width=&quot;800&quot; height=&quot;475&quot; srcset=&quot;https://web.dev/static/articles/optimize-ttfb/image/psi-real-user-data-updated_36.png 36w,https://web.dev/static/articles/optimize-ttfb/image/psi-real-user-data-updated_48.png 48w,https://web.dev/static/articles/optimize-ttfb/image/psi-real-user-data-updated_72.png 72w,https://web.dev/static/articles/optimize-ttfb/image/psi-real-user-data-updated_96.png 96w,https://web.dev/static/articles/optimize-ttfb/image/psi-real-user-data-updated_480.png 480w,https://web.dev/static/articles/optimize-ttfb/image/psi-real-user-data-updated_720.png 720w,https://web.dev/static/articles/optimize-ttfb/image/psi-real-user-data-updated_856.png 856w,https://web.dev/static/articles/optimize-ttfb/image/psi-real-user-data-updated_960.png 960w,https://web.dev/static/articles/optimize-ttfb/image/psi-real-user-data-updated_1440.png 1440w,https://web.dev/static/articles/optimize-ttfb/image/psi-real-user-data-updated_1920.png 1920w,https://web.dev/static/articles/optimize-ttfb/image/psi-real-user-data-updated_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption&gt;PageSpeed Insights real user data.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;For lab data, TTFB issues are shown in the &lt;a href=&quot;https://developer.chrome.com/docs/performance/insights/document-latency&quot;&gt;Document request latency&lt;/a&gt; insight:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/optimize-ttfb/image/lighthouse-document-request-latency-insight.png&quot; alt=&quot;Document request latency insight showing Avoids Redirects, Server Responds quickly, and Applies text compression audits.&quot; width=&quot;800&quot; height=&quot;198&quot; srcset=&quot;https://web.dev/static/articles/optimize-ttfb/image/lighthouse-document-request-latency-insight_36.png 36w,https://web.dev/static/articles/optimize-ttfb/image/lighthouse-document-request-latency-insight_48.png 48w,https://web.dev/static/articles/optimize-ttfb/image/lighthouse-document-request-latency-insight_72.png 72w,https://web.dev/static/articles/optimize-ttfb/image/lighthouse-document-request-latency-insight_96.png 96w,https://web.dev/static/articles/optimize-ttfb/image/lighthouse-document-request-latency-insight_480.png 480w,https://web.dev/static/articles/optimize-ttfb/image/lighthouse-document-request-latency-insight_720.png 720w,https://web.dev/static/articles/optimize-ttfb/image/lighthouse-document-request-latency-insight_856.png 856w,https://web.dev/static/articles/optimize-ttfb/image/lighthouse-document-request-latency-insight_960.png 960w,https://web.dev/static/articles/optimize-ttfb/image/lighthouse-document-request-latency-insight_1440.png 1440w,https://web.dev/static/articles/optimize-ttfb/image/lighthouse-document-request-latency-insight_1920.png 1920w,https://web.dev/static/articles/optimize-ttfb/image/lighthouse-document-request-latency-insight_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption&gt;PageSpeed Insights Document request latency insight.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;aside class=&quot;key-point&quot;&gt;
  &lt;b&gt;Key point:&lt;/b&gt; The server response time audit in Lighthouse excludes DNS lookup and redirect times, so it only represents a subset of TTFB.
&lt;/aside&gt;

&lt;p&gt;To find out more ways how to measure TTFB in both the field and the lab, &lt;a href=&quot;https://web.dev/ttfb#how-to-measure-ttfb&quot;&gt;consult the TTFB metric page&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;understand_differences_between_field_and_lab_ttfb&quot; data-text=&quot;Understand differences between field and lab TTFB&quot; tabindex=&quot;-1&quot;&gt;Understand differences between field and lab TTFB&lt;/h3&gt;

&lt;p&gt;Lab and field TTFB can differ for a number of reasons and when they do differ, it&#39;s important to understand why to be able to effectively use the lab data to improve your user experiences.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;When lab TTFB is much larger than field TTFB&lt;/strong&gt; this indicates that the lab environment is more constrained that the typical user experiences. This isn&#39;t necessarily a problem, as the lab results and recommendations are still be valid, but may exaggerate the impact and the improvement.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;When field TTFB is much larger than lab TTFB&lt;/strong&gt; this indicates issues that aren&#39;t apparent in the lab run, such as use of server-side caching, redirects, or network differences. In this case, the lab results and recommendations may be less useful, as they miss one of the main issues.&lt;/p&gt;

&lt;p&gt;To see if server-side caching is impacting lab TTFB, try testing less common pages or using different URL parameters to get uncached content to see if the TTFB is more inline with field TTFB. It can also be useful to you have the ability to bypass server-side caching with a particular URL parameter. See the &lt;a href=&quot;https://web.dev/articles/optimize-ttfb?hl=en#use-caching&quot;&gt;cached content section&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For redirects and network differences, analytics of how traffic is coming to our site, and where it&#39;s coming from, can be useful to diagnose if those are potential issues.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;debug_high_ttfb_in_the_field_with_server-timing&quot; data-text=&quot;Debug high TTFB in the field with Server-Timing&quot; tabindex=&quot;-1&quot;&gt;Debug high TTFB in the field with &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;Server-Timing&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;The &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Headers/Server-Timing&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;Server-Timing&lt;/code&gt; response header&lt;/a&gt; can be used in your application backend to measure distinct backend processes that could contribute to high latency. The header value&#39;s structure is flexible, accepting, at minimum, a handle that you define. Optional values include a duration value (via &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;dur&lt;/code&gt;), as well as an optional human-readable description (via &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;desc&lt;/code&gt;).&lt;/p&gt;

&lt;aside class=&quot;note&quot;&gt;&lt;b&gt;Note: &lt;/b&gt; If measuring backend latency with &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;Server-Timing&lt;/code&gt; is not feasible, then a suitable alternative may be to rely on &lt;a href=&quot;https://en.wikipedia.org/wiki/Application_performance_management&quot;&gt;Application Performance Monitoring (APM)&lt;/a&gt; to detect and diagnose backend performance problems. &lt;/aside&gt;

&lt;p&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;Serving-Timing&lt;/code&gt; can be used to measure many application backend processes, but there are some that you may want to pay special attention to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database queries&lt;/li&gt;
&lt;li&gt;Server-side rendering time, if applicable&lt;/li&gt;
&lt;li&gt;Disk seeks&lt;/li&gt;
&lt;li&gt;Edge server cache hits or misses (if using a CDN)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All parts of a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;Server-Timing&lt;/code&gt; entry are colon-separated, and multiple entries can be separated by a comma:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;Bash Session&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-go&quot;&gt;// Two metrics with descriptions and values&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-go&quot;&gt;Server-Timing: db;desc=&quot;Database&quot;;dur=121.3, ssr;desc=&quot;Server-side Rendering&quot;;dur=212.2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;The header can be set using your application backend&#39;s language of choice. In PHP, for example, you could set the header like so:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;PHP&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;&lt;span class=&quot;devsite-syntax-x&quot;&gt;?php&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-x&quot;&gt;// Get a high-resolution timestamp before&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-x&quot;&gt;// the database query is performed:&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-x&quot;&gt;$dbReadStartTime = hrtime(true);&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-x&quot;&gt;// Perform a database query and get results...&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-x&quot;&gt;// ...&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-x&quot;&gt;// Get a high-resolution timestamp after&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-x&quot;&gt;// the database query is performed:&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-x&quot;&gt;$dbReadEndTime = hrtime(true);&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-x&quot;&gt;// Get the total time, converting nanoseconds to&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-x&quot;&gt;// milliseconds (or whatever granularity you need):&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-x&quot;&gt;$dbReadTotalTime = ($dbReadEndTime - $dbReadStartTime) / 1e+6;&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-x&quot;&gt;// Set the Server-Timing header:&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-x&quot;&gt;header(&#39;Server-Timing: db;desc=&quot;Database&quot;;dur=&#39; . $dbReadTotalTime);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-x&quot;&gt;?&lt;/span&gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;When this header is set, it surfaces information you can use in both &lt;a href=&quot;https://web.dev/articles/lab-and-field-data-differences#lab_data&quot;&gt;the lab&lt;/a&gt;, and in &lt;a href=&quot;https://web.dev/articles/lab-and-field-data-differences#field_data&quot;&gt;the field&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the field, any page with a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;Server-Timing&lt;/code&gt; response header set populates the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;serverTiming&lt;/code&gt; property in the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Navigation_timing_API&quot;&gt;Navigation Timing API&lt;/a&gt;:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JavaScript&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;// Get the serverTiming entry for the first navigation request:&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-nx&quot;&gt;performance&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;getEntries&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;navigation&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;serverTiming&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&amp;gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;// Log the server timing data:&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;duration&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;In the lab, data from the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;Server-Timing&lt;/code&gt; response header is visualized in the
&lt;strong&gt;Timings&lt;/strong&gt; panel of the &lt;strong&gt;Network&lt;/strong&gt; tab in Chrome DevTools:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/optimize-ttfb/image/a-visualization-server-t-9700559c46421.png&quot; alt=&quot;The Server-Timing header values are measuring whether or not a CDN edge server encountered a cache hit or miss, as well as the time to retrieve the resource from the edge and the origin server.&quot; width=&quot;777&quot; height=&quot;143&quot; srcset=&quot;https://web.dev/static/articles/optimize-ttfb/image/a-visualization-server-t-9700559c46421_36.png 36w,https://web.dev/static/articles/optimize-ttfb/image/a-visualization-server-t-9700559c46421_48.png 48w,https://web.dev/static/articles/optimize-ttfb/image/a-visualization-server-t-9700559c46421_72.png 72w,https://web.dev/static/articles/optimize-ttfb/image/a-visualization-server-t-9700559c46421_96.png 96w,https://web.dev/static/articles/optimize-ttfb/image/a-visualization-server-t-9700559c46421_480.png 480w,https://web.dev/static/articles/optimize-ttfb/image/a-visualization-server-t-9700559c46421_720.png 720w,https://web.dev/static/articles/optimize-ttfb/image/a-visualization-server-t-9700559c46421_856.png 856w,https://web.dev/static/articles/optimize-ttfb/image/a-visualization-server-t-9700559c46421_960.png 960w,https://web.dev/static/articles/optimize-ttfb/image/a-visualization-server-t-9700559c46421_1440.png 1440w,https://web.dev/static/articles/optimize-ttfb/image/a-visualization-server-t-9700559c46421_1920.png 1920w,https://web.dev/static/articles/optimize-ttfb/image/a-visualization-server-t-9700559c46421_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption&gt;Server-Timing header values in the Network tab of Chrome DevTools.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Here, &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;Server-Timing&lt;/code&gt; is used to measure whether a request for a resource has hit the CDN cache, and how long it takes for the request to hit the CDN&#39;s edge server and then the origin.&lt;/p&gt;

&lt;p&gt;Server timings are also surfaced in the Network track of the &lt;strong&gt;Performance&lt;/strong&gt; panel in Chrome DevTools:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/optimize-ttfb/image/performance-panel-network-track-server-timing.png&quot; alt=&quot;A visualization of Server-Timing header values in the details tab of the Network track of the Performance panel Chrome DevTools.&quot; width=&quot;800&quot; height=&quot;289&quot; srcset=&quot;https://web.dev/static/articles/optimize-ttfb/image/performance-panel-network-track-server-timing_36.png 36w,https://web.dev/static/articles/optimize-ttfb/image/performance-panel-network-track-server-timing_48.png 48w,https://web.dev/static/articles/optimize-ttfb/image/performance-panel-network-track-server-timing_72.png 72w,https://web.dev/static/articles/optimize-ttfb/image/performance-panel-network-track-server-timing_96.png 96w,https://web.dev/static/articles/optimize-ttfb/image/performance-panel-network-track-server-timing_480.png 480w,https://web.dev/static/articles/optimize-ttfb/image/performance-panel-network-track-server-timing_720.png 720w,https://web.dev/static/articles/optimize-ttfb/image/performance-panel-network-track-server-timing_856.png 856w,https://web.dev/static/articles/optimize-ttfb/image/performance-panel-network-track-server-timing_960.png 960w,https://web.dev/static/articles/optimize-ttfb/image/performance-panel-network-track-server-timing_1440.png 1440w,https://web.dev/static/articles/optimize-ttfb/image/performance-panel-network-track-server-timing_1920.png 1920w,https://web.dev/static/articles/optimize-ttfb/image/performance-panel-network-track-server-timing_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption&gt;Server-Timing header values in the Network track of the Performance panel of Chrome DevTools.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Once you&#39;ve determined that you have a problematic TTFB by analyzing the data available, then you can move onto fixing the problem.&lt;/p&gt;

&lt;h2 id=&quot;ways_to_optimize_ttfb&quot; data-text=&quot;Ways to optimize TTFB&quot; tabindex=&quot;-1&quot;&gt;Ways to optimize TTFB&lt;/h2&gt;

&lt;p&gt;The most challenging aspect of optimizing TTFB is that, while the web&#39;s frontend stack is always be HTML, CSS, and JavaScript, backend stacks can vary significantly. There are numerous backend stacks and database products that each have their own optimization techniques. Therefore, this guide focuses on what applies to most architectures, rather than focusing solely on stack-specific guidance.&lt;/p&gt;

&lt;h3 id=&quot;platform-specific_guidance&quot; data-text=&quot;Platform-specific guidance&quot; tabindex=&quot;-1&quot;&gt;Platform-specific guidance&lt;/h3&gt;

&lt;p&gt;The platform you use for your website can heavily impact TTFB. For example, WordPress performance is impacted by the number and quality of plugins, or what themes are used. Other platforms are similarly impacted when the platform is customized. You should consult the documentation for your platform for vendor-specific advice to supplement the more general performance advice in this post. The Lighthouse insight also includes some limited &lt;a href=&quot;https://developer.chrome.com/docs/performance/insights/document-latency#stack-specific_guidance&quot;&gt;stack-specific guidance&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;hosting_hosting_hosting&quot; data-text=&quot;Hosting, hosting, hosting&quot; tabindex=&quot;-1&quot;&gt;Hosting, hosting, hosting&lt;/h3&gt;

&lt;p&gt;Before you even consider other optimization approaches, hosting should be the first thing you consider. There&#39;s not much in the way of specific guidance that can be offered here, but a general rule of thumb is to ensure that your website&#39;s host is capable of handling the traffic you send to it.&lt;/p&gt;

&lt;p&gt;Shared hosting is generally be slower. If you&#39;re running a small personal website that serves mostly static files, this is probably fine. Use some of the optimization techniques that follow to help you reduce your TTFB as much as possible.&lt;/p&gt;

&lt;p&gt;However, if you&#39;re running a larger application with many users that involves personalization, database querying, and other intensive server-side operations, your choice of hosting becomes critical to lower TTFB in the field.&lt;/p&gt;

&lt;p&gt;When choosing a hosting provider, these are some things to look out for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How much memory is your application instance allocated? If your application has insufficient memory, it will thrash and struggle to serve pages up as fast as possible.&lt;/li&gt;
&lt;li&gt;Does your hosting provider keep your backend stack up to date? As new versions of application backend languages, HTTP implementations, and database software are released, performance in that software improves over time. It&#39;s key to partner with a hosting provider that prioritizes this kind of crucial maintenance.&lt;/li&gt;
&lt;li&gt;If you have very specific application requirements and want the lowest level access to server configuration files, ask if it makes sense to customize your own application instance&#39;s backend.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are many hosting providers that take care of these things for you, but if you start to observe long TTFB values even in dedicated hosting providers, it may be a sign that you might need to re-evaluate your current hosting provider&#39;s capabilities so that you can deliver the best possible user experience.&lt;/p&gt;

&lt;h3 id=&quot;use_a_content_delivery_network_cdn&quot; data-text=&quot;Use a Content Delivery Network (CDN)&quot; tabindex=&quot;-1&quot;&gt;Use a Content Delivery Network (CDN)&lt;/h3&gt;

&lt;p&gt;The topic &lt;a href=&quot;https://web.dev/articles/content-delivery-networks&quot;&gt;CDN usage&lt;/a&gt; is a well-worn one, but for good reason: you could have a very well-optimized application backend, but users located far from your origin server may still experience high TTFB in the field.&lt;/p&gt;

&lt;p&gt;CDNs solve the problem of user proximity from your origin server by using a distributed network of servers that cache resources on servers that are physically closer to your users. These servers are called &lt;em&gt;edge servers&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;CDN providers may also offer benefits beyond edge servers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CDN providers usually offer extremely fast DNS resolution times.&lt;/li&gt;
&lt;li&gt;A CDN likely serves your content from edge servers using modern protocols such as HTTP/2 or HTTP/3.&lt;/li&gt;
&lt;li&gt;HTTP/3 in particular solves the head-of-line blocking problem present in TCP (which HTTP/2 relies on) by using the &lt;a href=&quot;https://en.wikipedia.org/wiki/User_Datagram_Protocol&quot;&gt;UDP protocol&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;A CDN likely provides modern versions of TLS, which lowers the latency involved in TLS negotiation time. &lt;a href=&quot;https://en.wikipedia.org/wiki/Transport_Layer_Security#TLS_1.3_handshake&quot;&gt;TLS 1.3&lt;/a&gt; in particular is designed to keep TLS negotiation as short as possible.&lt;/li&gt;
&lt;li&gt;Some CDN providers provide a feature often called an &quot;edge worker&quot;, which uses an API similar to that of &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Service_Worker_API&quot;&gt;the Service Worker API&lt;/a&gt; to intercept requests, programmatically manage responses in edge caches, or rewrite responses altogether.&lt;/li&gt;
&lt;li&gt;CDN providers are very good at optimizing for compression. Compression is tricky to get right on your own, and may lead to slower response times in certain cases with dynamically generated markup, which must be compressed on the fly.&lt;/li&gt;
&lt;li&gt;Also, CDN providers automatically cache compressed responses for static resources, leading to the best mix of compression ratio and response time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While adopting a CDN involves a varying amount of effort from trivial to significant, it should be a high priority to pursue in optimizing your TTFB if your website is not already using one.&lt;/p&gt;

&lt;h3 id=&quot;use-caching&quot; data-text=&quot;Use cached content where possible&quot; tabindex=&quot;-1&quot;&gt;Use cached content where possible&lt;/h3&gt;

&lt;p&gt;CDNs allow content to be cached at edge servers which are located physically closer to visitors, provided the content is configured with the appropriate &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Headers/Cache-Control&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;Cache-Control&lt;/code&gt; HTTP headers&lt;/a&gt;. While this is not appropriate for personalized content, requiring a trip all the way back to the origin can negate much of the value of a CDN.&lt;/p&gt;

&lt;p&gt;For sites that frequently update their content, even a short caching time can result in noticeable performance gains for busy sites, since only the first visitor during that time experiences the fully latency back to the origin server, while all other visitors can reuse the cached resource from the edge server. Some CDNs allow cache invalidation on site releases allowing the best of both worlds—long cache times, but instant updates when needed.&lt;/p&gt;

&lt;p&gt;Even where caching is correctly configured, this can be ignored through the use of unique query string parameters for analytics measurement. These may look like different content to the CDN despite being the same, and so the cached version won&#39;t be used.&lt;/p&gt;
&lt;aside class=&quot;caution&quot;&gt;&lt;strong&gt;Caution:&lt;/strong&gt;&lt;span&gt; Caching is great, but it can mask a slow backend making diagnosing long
TTFBs harder since testing a cached page often shows no TTFB issues. We
recommend implementing a way to skip the CDN and other caching layers,
such as with a URL parameter. This helps you can understand the full server time
that some users experience as TTFB.&lt;/span&gt;&lt;/aside&gt;
&lt;p&gt;Older or less visited content may also not be cached, which can result in higher TTFB values on some pages than others. Increasing caching times can reduce the impact of this, but be aware that with increased caching times comes a greater possibility of serving potentially stale content.&lt;/p&gt;

&lt;p&gt;The impact of cached content does not just affect those using CDNs. Server infrastructure may need to generate content from costly database lookups when cached content cannot be reused. More frequently accessed data or precached pages can often perform better.&lt;/p&gt;

&lt;h3 id=&quot;avoid_multiple_page_redirects&quot; data-text=&quot;Avoid multiple page redirects&quot; tabindex=&quot;-1&quot;&gt;Avoid multiple page redirects&lt;/h3&gt;

&lt;p&gt;One common contributor to a high TTFB is &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Redirections&quot;&gt;redirects&lt;/a&gt;. Redirects occur when a navigation request for a document receives a response that informs the browser that the resource exists at another location. One redirect can certainly add unwanted latency to a navigation request, but it can certainly get worse if that redirect points to another resource that results in &lt;em&gt;another&lt;/em&gt; redirect—and so on. This can particularly impact sites that receive high volumes of visitors from advertisements or newsletters, since they often redirect through analytics services for measurement purposes. Eliminating redirects under your direct control can help to achieve a good TTFB.&lt;/p&gt;

&lt;p&gt;There are two types of redirects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Same-origin redirects&lt;/strong&gt;, where the redirect occurs entirely on your website.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cross-origin redirects&lt;/strong&gt;, where the redirect occurs initially on another origin—such as from a social media URL shortening service, for example—before arriving at your website.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Focus on eliminating same-origin redirects, as this is something you have direct control over. This would involve checking links on your website to see if any of them result in a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;302&lt;/code&gt; or &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;301&lt;/code&gt; response code. Often this can be the result of not including the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;https://&lt;/code&gt; scheme (so browsers default to &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;http://&lt;/code&gt; which then redirects) or because trailing slashes are not appropriately included or excluded in the URL.&lt;/p&gt;

&lt;p&gt;Cross-origin redirects are trickier as these are often outside of your control, but try to avoid multiple redirects where possible—for example, by using multiple link shorteners when sharing links. Ensure the URL provided to advertisers or newsletters is the correct final URL, so as not to add another redirect to the ones used by those services.&lt;/p&gt;

&lt;p&gt;Another important source of redirect time can come from HTTP-to-HTTPS redirects. One way you can get around this is to use the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Headers/Strict-Transport-Security&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;Strict-Transport-Security&lt;/code&gt; header&lt;/a&gt; (HSTS), which enforces HTTPS on the first visit to an origin, and then tells the browser to immediately access the origin through the HTTPS scheme on future visits.&lt;/p&gt;

&lt;p&gt;Once you have a good HSTS policy in place, you can speed things up on the first visit to an origin &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Headers/Strict-Transport-Security#preloading_strict_transport_security&quot;&gt;by adding your site to the HSTS preload list&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;stream_markup_to_the_browser&quot; data-text=&quot;Stream markup to the browser&quot; tabindex=&quot;-1&quot;&gt;Stream markup to the browser&lt;/h3&gt;

&lt;p&gt;Browsers are optimized to process markup efficiently when it&#39;s streamed, meaning that markup is handled in chunks as it arrives from the server. This is crucial where large markup payloads are concerned, as it means the browser can parse that the chunks of markup incrementally, as opposed to waiting for the entire response to arrive before parsing can begin.&lt;/p&gt;

&lt;p&gt;Though browsers are great at handling streaming markup, it&#39;s crucial to do all that you can to keep that stream flowing so those initial bits of markup are on their way as soon as possible. If the backend is holding things up, that&#39;s a problem. Because backend stacks are numerous, it would be beyond the scope of this guide to cover every single stack and the issues that could arise in each specific one.&lt;/p&gt;

&lt;p&gt;React, for example—and other frameworks that can &lt;a href=&quot;https://web.dev/articles/rendering-on-the-web#server-side_rendering&quot;&gt;render markup on demand on the server&lt;/a&gt;—have used a synchronous approach to server-side rendering. However, newer versions of React have implemented &lt;a href=&quot;https://reactjs.org/docs/react-dom-server.html#overview&quot;&gt;server methods for streaming markup&lt;/a&gt; as it&#39;s being rendered. This means you don&#39;t have to wait for a React server API method to render the entire response before it&#39;s sent.&lt;/p&gt;

&lt;aside class=&quot;note&quot;&gt;
  &lt;b&gt;Note:&lt;/b&gt; Not every language runtime can take advantage of streaming server-side rendering. JavaScript runtimes such as &lt;a href=&quot;https://deno.land/&quot;&gt;Deno&lt;/a&gt; and &lt;a href=&quot;https://nodejs.org/&quot;&gt;Node.js&lt;/a&gt; support this out of the box, but other platforms may not support it. Check to see if this is the case for you, and see what you can do to upgrade or switch your runtime for better server-side rendering performance.
&lt;/aside&gt;

&lt;p&gt;Another way to ensure markup is streamed to the browser quickly is to rely on &lt;a href=&quot;https://web.dev/articles/rendering-on-the-web#static_rendering&quot;&gt;static rendering&lt;/a&gt; which generates HTML files during build time. With the full file available immediately, web servers can start sending the file immediately and the inherent nature of HTTP results in streaming markup. While this approach isn&#39;t suitable for every page on every website—such as those requiring a dynamic response as part of the user experience—it can be beneficial for those pages that don&#39;t require markup to be personalized to a specific user.&lt;/p&gt;

&lt;h3 id=&quot;use_a_service_worker&quot; data-text=&quot;Use a service worker&quot; tabindex=&quot;-1&quot;&gt;Use a service worker&lt;/h3&gt;

&lt;p&gt;The &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Service_Worker_API&quot;&gt;Service Worker API&lt;/a&gt; can have a big impact on the TTFB for both documents and the resources they load. The reason for this is that a service worker acts as a proxy between the browser and the server—but whether there is an impact on your website&#39;s TTFB depends on how you set up your service worker, and if that setup aligns with your application requirements.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Use a &lt;a href=&quot;https://developer.chrome.com/docs/workbox/caching-strategies-overview#stale-while-revalidate&quot;&gt;stale-while-revalidate strategy&lt;/a&gt; for assets.&lt;/strong&gt; If an asset is in the service worker cache, be it a document or a resource the document requires, the stale-while-revalidate strategy services that resource from the cache &lt;em&gt;first&lt;/em&gt;, then downloads that asset in the background and serves it from the cache for future interactions.
&lt;ul&gt;
&lt;li&gt;If you have document resources that don&#39;t change very often, using a stale-while-revalidate strategy can make a page&#39;s TTFB nearly instant. However, this doesn&#39;t work so well if your website sends dynamically generated markup—such as markup that changes based on whether a user is authenticated. In such cases, you&#39;ll always want to hit the network &lt;em&gt;first&lt;/em&gt;, so the document is as fresh as possible.&lt;/li&gt;
&lt;li&gt;If your document loads non-critical resources that change with some frequency, but fetching the stale resource won&#39;t greatly affect the user experience—such as select images or other resources that aren&#39;t critical—the TTFB for those resources can be greatly reduced using a stale-while-revalidate strategy.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use &lt;a href=&quot;https://developer.chrome.com/blog/app-shell&quot;&gt;the app shell model&lt;/a&gt; for client-rendered applications.&lt;/strong&gt; This model fits best for SPAs where the &quot;shell&quot; of the page can be delivered instantly from the service worker cache, and the dynamic content of the page is populated and rendered later on in the page lifecycle.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;use_103_early_hints_for_render-critical_resources&quot; data-text=&quot;Use 103 Early Hints for render-critical resources&quot; tabindex=&quot;-1&quot;&gt;Use &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;103 Early Hints&lt;/code&gt; for render-critical resources&lt;/h3&gt;

&lt;p&gt;No matter how well your application backend is optimized, there could still be a significant amount of work the server needs to do in order to prepare a response, including expensive (yet necessary) database work that delays the navigation response from arriving as quickly as it could. The potential effect of this is that some subsequent render-critical resources could be delayed, such as CSS or—in some cases—JavaScript that renders markup on the client.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://developer.chrome.com/blog/early-hints&quot;&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;103 Early Hints&lt;/code&gt; header&lt;/a&gt; is an early response code that the server can send to the browser while the backend is busy preparing markup. This header can be used to hint to the browser that there are render-critical resources the page should begin downloading while the markup is being prepared. For &lt;a href=&quot;https://caniuse.com/mdn-http_status_103&quot;&gt;supporting browsers&lt;/a&gt;, the effect can be faster document rendering (CSS) and quicker page loading.&lt;/p&gt;

&lt;aside class=&quot;note&quot;&gt;
  &lt;b&gt;Note:&lt;/b&gt; If your website doesn&#39;t do a lot of processing on the backend to prepare markup—for example, static sites—then &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;103 Early Hints&lt;/code&gt; probably won&#39;t help much. It tends to work best for sites that involve considerable backend time before markup can be sent to the user.
&lt;/aside&gt;

&lt;p&gt;One downside to 103 Early Hints is that, like caching, they can mask the &quot;real&quot; TTFB of a site. If a server infrastructure is slow (either because it&#39;s underpowered or the code needs optimizing), then that can be less obvious when 103 Early Hints is used as the TTFB looks fast. Sites using 103 Early Hints should consider measuring the actual server time (though &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;Server-Timing&lt;/code&gt; or &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;finalResponseHeadersStart&lt;/code&gt; of the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/PerformanceNavigationTiming&quot;&gt;PerformanceNavigationTiming API&lt;/a&gt;).&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot; data-text=&quot;Conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Since there are so many combinations of backend application stacks, there&#39;s no one article that can encapsulate &lt;em&gt;everything&lt;/em&gt; you can do to lower your website&#39;s TTFB. However, these are some options you can explore to try and get things going just a little bit faster on the server side of things.&lt;/p&gt;

&lt;p&gt;As with optimizing every metric, the approach is largely similar: measure your TTFB in the field, use lab tools to drill down into the causes, and then apply optimizations where possible. While some techniques may not be viable for your situation, some likely are. As always, you&#39;ll need to keep a close eye on your field data, and make adjustments as needed to ensure the fastest possible user experience.&lt;/p&gt;

  

  
</description><link>https://web.dev/articles/optimize-ttfb?hl=en</link><guid isPermaLink="false">https://web.dev/articles/optimize-ttfb?hl=en</guid><pubDate>Fri, 28 Nov 2025 08:00:00 GMT</pubDate></item><item><title>Time to First Byte (TTFB)</title><description>&lt;p&gt;&lt;style&gt;
  .wd-author__links {
    display: flex;
  }
  &lt;/style&gt;

&lt;/p&gt;&lt;p&gt;&lt;/p&gt;

&lt;p&gt;
&lt;/p&gt;&lt;p class=&quot;wd-pubdates&quot; style=&quot;font-size: 90%; border-bottom: 1px solid var(--devsite-ref-palette--grey200);color: var(--devsite-ref-palette--blue-grey700);&quot;&gt;
  Published: October 26, 2021, Last updated: November 18, 2025
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;devsite-iframe&gt;&lt;iframe src=&quot;https://web.dev/frame/articles/ttfb/index_ea093f0474e08041922fc86eaa73904104e453571f1eda0c026de18f5fe24e44.frame&quot; class=&quot;framebox inherit-locale &quot; allow=&quot;clipboard-write https://web-dot-devsite-v2-prod-3p.appspot.com&quot; allowfullscreen=&quot;&quot; is-upgraded=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;&lt;/devsite-iframe&gt;&lt;/p&gt;

&lt;aside class=&quot;note&quot;&gt;
  &lt;b&gt;Note:&lt;/b&gt; Time to First Byte (TTFB) is a foundational metric for measuring connection setup time and web server responsiveness in both the lab and the field. It helps identify when a web server is too slow to respond to requests. In the case of navigation requests—that is, requests for an HTML document—it precedes every other meaningful loading performance metric.
&lt;/aside&gt;

&lt;h2 id=&quot;what_is_ttfb&quot; data-text=&quot;What is TTFB?&quot; tabindex=&quot;-1&quot;&gt;What is TTFB?&lt;/h2&gt;

&lt;p&gt;TTFB is a metric that measures the time between starting navigating to a page and when the first byte of a response begins to arrive.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/ttfb/image/performance-navigation-timing-timestamp-diagram.svg&quot; alt=&quot;A visualization of network request timings. The timings from left to right are Redirect, Service Worker Init, Service Worker Fetch event, HTTP Cache, DNS, TCP, Request, Early Hints (103), Response (which overlaps with Prompt for Unload), Processing, and Load. The associated timings are redirectStart and redirectEnd, fetchStart, domainLookupStart, domainLookupEnd, connectStart, secureConnectionStart, connectEnd, requestStart, interimResponseStart, responseStart, unloadEventStart, unloadEventEnd, responseEnd, domInteractive, domContentLoadedEventStart, domContentLoadedEventEnd, domComplete, loadEventStart, and loadEventEnd.&quot; width=&quot;800&quot; height=&quot;319&quot; style=&quot;background-color: white;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption class=&quot;wd-caption&quot;&gt;
    A diagram of network request phases and their associated timings. TTFB measures the elapsed time between &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;startTime&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;responseStart&lt;/code&gt;.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;TTFB is the sum of the following request phases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Redirect time&lt;/li&gt;
&lt;li&gt;Service worker startup time (if applicable)&lt;/li&gt;
&lt;li&gt;DNS lookup&lt;/li&gt;
&lt;li&gt;Connection and TLS negotiation&lt;/li&gt;
&lt;li&gt;Request, up until the point at which the first byte of the response has arrived&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reducing latency in connection setup time and on the backend can lower your TTFB.&lt;/p&gt;

&lt;h3 id=&quot;other-definitions&quot; data-text=&quot;TTFB and Early Hints&quot; tabindex=&quot;-1&quot;&gt;TTFB and Early Hints&lt;/h3&gt;

&lt;p&gt;The introduction of &lt;a href=&quot;https://developer.chrome.com/docs/web-platform/early-hints&quot;&gt;103 Early Hints&lt;/a&gt; causes some confusion as to what &quot;first byte&quot; TTFB measures. The 103 Early Hints counts as the &quot;first bytes&quot;. The &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/finalResponseHeadersStart&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;finalResponseHeadersStart&lt;/code&gt;&lt;/a&gt; is an additional timing entry to &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;responseStart&lt;/code&gt; that measures the the start of the final document response (typically an HTTP 200 response) to be measured.&lt;/p&gt;

&lt;aside class=&quot;note&quot;&gt;&lt;b&gt;Note:&lt;/b&gt; Based on feedback from the community, Chrome updated the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;responseStart&lt;/code&gt; (and so the TTFB) to the start of the final document response (that was later standardized as &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;finalResponseHeadersStart&lt;/code&gt;) from Chrome 115. However, this created a compatibility issue across many tools and so &lt;a href=&quot;https://chromestatus.com/feature/5158830722514944&quot;&gt;was reverted in Chrome 133&lt;/a&gt;.&lt;/aside&gt;

&lt;p&gt;Early Hints is just a newer example of responding early. Some servers allow early flushing of the document response to happen before the main body is available—either with just the HTTP headers, or with the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;head&amp;gt;&lt;/code&gt; element, both of which could be considered similar in effect to Early Hints. This is another reason why all these are measured as &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;responseStart&lt;/code&gt; and so TTFB.&lt;/p&gt;

&lt;p&gt;There is real value in sending back data early if the full response is going to take some more time. However, this does make it difficult to compare TTFB across different platforms or technologies depending on what features they use, and how that impacts the TTFB measurement being used. What is most important is to understand what the measure the tool you are using measures and how that is affected by the platform being measured.&lt;/p&gt;

&lt;h3 id=&quot;good-ttfb-score&quot; data-text=&quot;What is a good TTFB score?&quot; tabindex=&quot;-1&quot;&gt;What is a good TTFB score?&lt;/h3&gt;

&lt;p&gt;Because TTFB precedes &lt;a href=&quot;https://web.dev/articles/user-centric-performance-metrics&quot;&gt;user-centric metrics&lt;/a&gt; such as &lt;a href=&quot;https://web.dev/articles/fcp&quot;&gt;First Contentful Paint (FCP)&lt;/a&gt; and &lt;a href=&quot;https://web.dev/articles/lcp&quot;&gt;Largest Contentful Paint (LCP)&lt;/a&gt;, it&#39;s recommended that your server responds to navigation requests quickly enough so that the &lt;strong&gt;75th percentile&lt;/strong&gt; of users experience an &lt;a href=&quot;https://web.dev/articles/fcp#what_is_a_good_fcp_score&quot;&gt;FCP within the &quot;good&quot; threshold&lt;/a&gt;. As a rough guide, most sites should strive to have a TTFB of &lt;strong&gt;0.8 seconds&lt;/strong&gt; or less.&lt;/p&gt;

&lt;figure&gt;
  &lt;picture&gt;
    &lt;source srcset=&quot;https://web.dev/static/articles/ttfb/image/good-ttfb-values.svg&quot; media=&quot;(min-width: 640px)&quot; width=&quot;800&quot; height=&quot;200&quot;&gt;
    &lt;img src=&quot;https://web.dev/static/articles/ttfb/image/good-ttfb-values-are-08-ca73d93f5df2d.svg&quot; alt=&quot;Good TTFB values are 0.8 seconds or less, poor values are greater than 1.8 seconds, and anything in between needs improvement&quot; width=&quot;640&quot; height=&quot;480&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;/picture&gt;
  &lt;figcaption class=&quot;wd-caption&quot;&gt;
    Good TTFB values are 0.8 seconds or less, and poor values are greater than 1.8 seconds.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;aside class=&quot;key-point&quot;&gt;
  &lt;p&gt;
    &lt;b&gt;Key point:&lt;/b&gt; Because TTFB isn&#39;t a &lt;a href=&quot;https://web.dev/articles/vitals&quot;&gt;Core Web Vitals&lt;/a&gt; metric, it&#39;s not absolutely necessary that sites meet the &quot;good&quot; TTFB threshold, provided that it doesn&#39;t impede their ability to score well on the metrics that matter.
  &lt;/p&gt;
  &lt;p&gt;
    Websites vary in how they deliver content. A low TTFB is crucial for getting markup out to the client as soon as possible. However, if a website delivers the initial markup quickly, but that markup then requires JavaScript to populate it with meaningful content—as is the the case with Single Page Applications (SPAs)—then achieving the lowest possible TTFB is especially important so that the client-rendering of markup can occur sooner.
  &lt;/p&gt;
  &lt;p&gt;
    Conversely, a server-rendered site that does not require as much client-side work could have a higher TTFB, but better FCP and LCP values than an entirely client-rendered experience. This is why the TTFB thresholds are a &quot;rough guide&quot;, and will need to be weighed against how your site delivers its core content.
  &lt;/p&gt;
&lt;/aside&gt;

&lt;h2 id=&quot;measure-ttfb&quot; data-text=&quot;How to measure TTFB&quot; tabindex=&quot;-1&quot;&gt;How to measure TTFB&lt;/h2&gt;

&lt;p&gt;TTFB can be measured in &lt;a href=&quot;https://web.dev/articles/user-centric-performance-metrics#in_the_lab&quot;&gt;the lab&lt;/a&gt; or in &lt;a href=&quot;https://web.dev/articles/user-centric-performance-metrics#in_the_field&quot;&gt;the field&lt;/a&gt; in the following ways.&lt;/p&gt;

&lt;h3 id=&quot;field_tools&quot; data-text=&quot;Field tools&quot; tabindex=&quot;-1&quot;&gt;Field tools&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/crux&quot;&gt;Chrome User Experience Report&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/GoogleChrome/web-vitals&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;web-vitals&lt;/code&gt; JavaScript library&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;lab_tools&quot; data-text=&quot;Lab tools&quot; tabindex=&quot;-1&quot;&gt;Lab tools&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;In the &lt;a href=&quot;https://developer.chrome.com/docs/devtools/network&quot;&gt;network panel&lt;/a&gt; of Chrome&#39;s DevTools&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.webpagetest.org/&quot;&gt;WebPageTest&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;measure-in-javascript&quot; data-text=&quot;Measure TTFB in JavaScript&quot; tabindex=&quot;-1&quot;&gt;Measure TTFB in JavaScript&lt;/h3&gt;

&lt;p&gt;You can measure the TTFB of &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Request/mode&quot;&gt;navigation requests&lt;/a&gt; in the browser with the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Navigation_timing_API&quot;&gt;Navigation Timing API&lt;/a&gt;. The following example shows how to create a &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/PerformanceObserver&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;PerformanceObserver&lt;/code&gt;&lt;/a&gt; that listens for a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;navigation&lt;/code&gt; entry and logs it to the console:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JavaScript&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-ow&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;PerformanceObserver&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;entryList&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&amp;gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;pageNav&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;entryList&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;getEntriesByType&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;navigation&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-sb&quot;&gt;`TTFB: &lt;/span&gt;&lt;span class=&quot;devsite-syntax-si&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;pageNav&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;responseStart&lt;/span&gt;&lt;span class=&quot;devsite-syntax-si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;devsite-syntax-sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}).&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;({&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;navigation&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;buffered&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;web-vitals&lt;/code&gt; JavaScript library&lt;/a&gt; can also measure TTFB in the browser more succinctly:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JavaScript&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;onTTFB&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kr&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;web-vitals&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-c1&quot;&gt;// Measure and log TTFB as soon as it&#39;s available.&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-nx&quot;&gt;onTTFB&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;h3 id=&quot;measure-resource-requests&quot; data-text=&quot;Measure resource requests&quot; tabindex=&quot;-1&quot;&gt;Measure resource requests&lt;/h3&gt;

&lt;p&gt;TTFB can also be measured on &lt;em&gt;all&lt;/em&gt; requests, not just navigation requests. In particular, resources hosted on cross-origin servers can introduce latency due to the need to set up connections to those servers.&lt;/p&gt;

&lt;aside class=&quot;note&quot;&gt;
  &lt;b&gt;Note:&lt;/b&gt; CrUX and other measurement tools may only provide TTFB data on the main navigation request.
&lt;/aside&gt;

&lt;p&gt;To measure TTFB for resources in the field, use the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Resource_Timing_API/Using_the_Resource_Timing_API&quot;&gt;Resource Timing API&lt;/a&gt; within a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;PerformanceObserver&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JavaScript&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-ow&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;PerformanceObserver&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;entryList&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&amp;gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;entries&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;entryList&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;getEntries&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;entries&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;// Some resources may have a responseStart value of 0, due&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;// to the resource being cached, or a cross-origin resource&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;// being served without a Timing-Allow-Origin header set.&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;responseStart&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &amp;gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-sb&quot;&gt;`TTFB: &lt;/span&gt;&lt;span class=&quot;devsite-syntax-si&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;responseStart&lt;/span&gt;&lt;span class=&quot;devsite-syntax-si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;devsite-syntax-sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}).&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;({&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;resource&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;buffered&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;The previous code snippet is similar to the one used to measure the TTFB for a navigation request, except instead of querying for &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&#39;navigation&#39;&lt;/code&gt; entries, you query for &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&#39;resource&#39;&lt;/code&gt; entries instead. It also accounts for the fact that some resources loaded from the primary origin may return a value of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;0&lt;/code&gt;, since the connection is already open, or a resource is instantaneously retrieved from a cache.&lt;/p&gt;

&lt;aside class=&quot;tip&quot;&gt;
  TTFB for cross-origin requests won&#39;t be accurately measurable in the field if cross-origin servers don&#39;t set a &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Headers/Timing-Allow-Origin&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;Timing-Allow-Origin&lt;/code&gt; header&lt;/a&gt;.
&lt;/aside&gt;

&lt;h2 id=&quot;how_to_improve_ttfb&quot; data-text=&quot;How to improve TTFB&quot; tabindex=&quot;-1&quot;&gt;How to improve TTFB&lt;/h2&gt;

&lt;p&gt;For guidance on improving your site&#39;s TTFB, see our in-depth guide to &lt;a href=&quot;https://web.dev/articles/optimize-ttfb&quot;&gt;optimizing TTFB&lt;/a&gt;.&lt;/p&gt;
  

  
</description><link>https://web.dev/articles/ttfb?hl=en</link><guid isPermaLink="false">https://web.dev/articles/ttfb?hl=en</guid><pubDate>Fri, 28 Nov 2025 08:00:00 GMT</pubDate></item><item><title>&lt;dialog&gt; and popover: Baseline layered UI patterns</title><description>&lt;p&gt;&lt;style&gt;
  .wd-author__links {
    display: flex;
  }
  &lt;/style&gt;

&lt;/p&gt;

&lt;p class=&quot;wd-pubdates&quot; style=&quot;font-size: 90%; border-bottom: 1px solid var(--devsite-ref-palette--grey200);color: var(--devsite-ref-palette--blue-grey700);&quot;&gt;
  Published: November 25, 2025
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Elements and windows that pop up on the screen are one of the most common patterns on the web. With use cases spanning from alerts and brief forms requesting data, to the now ubiquitous cookie settings prompt, these layered UI patterns are used frequently by developers.&lt;/p&gt;

&lt;p&gt;Most of these user interface elements are added to web applications using either custom JavaScript or common libraries. With that route there&#39;s a lot to make sure that you or the library you choose gets right.&lt;/p&gt;

&lt;p&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; element and the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;popover&lt;/code&gt; attribute are two Baseline layered UI patterns that developers can reach for instead of custom implementations. To show the advantages of using layered UI patterns built into today&#39;s web browsers—and to give an example of when you might reach for &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; or use the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;popover&lt;/code&gt; attribute—this article walks through an example of a modal that appears when the user attempts to save an image to a favorites list without being logged in.&lt;/p&gt;

&lt;div class=&quot;wd-embed&quot; style=&quot;height: 640px; width: 100%&quot;&gt;
   &lt;iframe allow=&quot;camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;&quot; loading=&quot;lazy&quot; src=&quot;https://codepen.io/web-dot-dev/embed/GgZOwxL?height=250&amp;amp;theme-id=light&amp;amp;default-tab=result&amp;amp;editable=true&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot; title=&quot;Pen GgZOwxL by web-dot-dev on Codepen&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
   &lt;/iframe&gt;
&lt;/div&gt;
&lt;aside class=&quot;note&quot;&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;span&gt; This demo is also &lt;a href=&quot;https://github.com/GoogleChromeLabs/baseline-demos/tree/main/src/dialog&quot;&gt;available on GitHub&lt;/a&gt;.&lt;/span&gt;&lt;/aside&gt;
&lt;h2 id=&quot;a_modal_dialog&quot; data-text=&quot;A modal &lt;dialog&gt;&quot; tabindex=&quot;-1&quot;&gt;A modal &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;The &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/dialog&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; element&lt;/a&gt; is a box that appears over other content that a user can interact with to complete a task or input information. They can be modal or non-modal. The main difference is that when a modal &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; is open, interaction with the rest of the content underneath is disabled.&lt;/p&gt;

&lt;p&gt;&lt;devsite-iframe&gt;&lt;iframe src=&quot;https://web.dev/frame/articles/baseline-in-action-dialog-popover/index_40afad7b4072ec4593e4cfd94c625600db5fa4abf94520feb00525d37543fbea.frame&quot; class=&quot;framebox inherit-locale &quot; allow=&quot;clipboard-write https://web-dot-devsite-v2-prod-3p.appspot.com&quot; allowfullscreen=&quot;&quot; is-upgraded=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;&lt;/devsite-iframe&gt;&lt;/p&gt;
&lt;aside class=&quot;note&quot;&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;span&gt; Read more about the differences between the two in the &lt;a href=&quot;https://web.dev/learn/html/dialog#modal_dialogs&quot;&gt;dialog module&lt;/a&gt; of &lt;a href=&quot;https://web.dev/learn/html&quot;&gt;Learn HTML&lt;/a&gt;.&lt;/span&gt;&lt;/aside&gt;
&lt;p&gt;Each image in the gallery demo has a &quot;favorite&quot; button that can be pressed to save the image to what would be a favorites list. Since the user is not logged in, a pop up prompts them to sign up with an email address in order to complete the action.&lt;/p&gt;

&lt;p&gt;The modal in this example is a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; element inserted right after the image gallery.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;HTML&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;dialog aria-labelledby=&quot;dialog-title&quot; aria-describedby=&quot;description&quot;&amp;gt;
  &amp;lt;h2 id=&quot;dialog-title&quot;&amp;gt;Yay! You found a favorite!&amp;lt;/h2&amp;gt;
  &amp;lt;p id=&quot;description&quot;&amp;gt;Sign up with your email address to start adding to your favorites list.&amp;lt;/p&amp;gt;
&amp;lt;/dialog&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;h3 id=&quot;use_the_showmodal_method_to_open_a_modal_dialog&quot; data-text=&quot;Use the showModal() method to open a modal &lt;dialog&gt;&quot; tabindex=&quot;-1&quot;&gt;Use the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;showModal()&lt;/code&gt; method to open a modal &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; element is hidden by default. To display it as a modal, you can use the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;showModal()&lt;/code&gt; method.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JavaScript&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;openDialogBtns&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;querySelectorAll&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-sb&quot;&gt;`[data-button*=&quot;show-dialog&quot;]`&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;dialog&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;dialog&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-nx&quot;&gt;openDialogBtns&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;openDialogBtn&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&amp;gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;openDialogBtn&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;click&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&amp;gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;dialog&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;showModal&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;In addition to opening the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; as a modal, using the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;showModal()&lt;/code&gt; method:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adds the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;dialog&lt;/code&gt; to the &lt;a href=&quot;https://developer.mozilla.org/docs/Glossary/Top_layer&quot;&gt;top layer&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Attaches a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;::backdrop&lt;/code&gt; pseudo-element directly underneath it.&lt;/li&gt;
&lt;li&gt;Implicitly adds the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;aria-modal=&quot;true&quot;&lt;/code&gt; attribute.&lt;/li&gt;
&lt;li&gt;Automatically makes all of the elements on the page underneath the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;dialog&lt;/code&gt; &lt;a href=&quot;https://web.dev/learn/html/focus#the_inert_attribute&quot;&gt;inert&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;aside class=&quot;note&quot;&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;span&gt; An alternative approach, that behaves identically to &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;dialog.showModal&lt;/code&gt;, lets you open and close a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; declaratively with the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/button#command&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;command&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/button#commandfor&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;commandfor&lt;/code&gt;&lt;/a&gt; attributes. Read more about &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;command&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;commandfor&lt;/code&gt; on the &lt;a href=&quot;https://developer.chrome.com/blog/command-and-commandfor&quot;&gt;Chrome for Developers&lt;/a&gt; blog.&lt;/span&gt;&lt;/aside&gt;
&lt;p&gt;&lt;devsite-iframe&gt;&lt;iframe src=&quot;https://web.dev/frame/articles/baseline-in-action-dialog-popover/index_e3519bee5804a042301a0020f7885b73a036eef7c475075c7cf350e979672559.frame&quot; class=&quot;framebox inherit-locale &quot; allow=&quot;clipboard-write https://web-dot-devsite-v2-prod-3p.appspot.com&quot; allowfullscreen=&quot;&quot; is-upgraded=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;&lt;/devsite-iframe&gt;&lt;/p&gt;

&lt;p&gt;In a custom implementation, all of the behavior included with &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;dialog.showModal&lt;/code&gt; or &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;command=&quot;show-modal&quot;&lt;/code&gt; are all details you would have to account for yourself. In addition to these characteristics of a modal, the &lt;a href=&quot;https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/&quot;&gt;WAI ARIA Authoring Practices Guide&lt;/a&gt; lists further requirements and guidelines for the ideal user experience once a modal dialog is open:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Focus moves to an element inside of the dialog.&lt;/li&gt;
&lt;li&gt;Tabbing and keyboard focus should stay within the dialog until it is closed.&lt;/li&gt;
&lt;li&gt;Since users shouldn&#39;t be able to interact with the content underneath the modal, that content should be slightly dimmed or obscured.&lt;/li&gt;
&lt;li&gt;Once a modal is closed, focus should return to the element that opened it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That&#39;s a lot to remember, test, and get right.&lt;/p&gt;

&lt;p&gt;However, with the HTML &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; element, its built-in &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;showModal()&lt;/code&gt; method, and the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;::backdrop&lt;/code&gt; CSS pseudo-selector, you get all of the web features just described that are now Baseline.&lt;/p&gt;

&lt;h2 id=&quot;how_to_close_the_dialog&quot; data-text=&quot;How to close the dialog&quot; tabindex=&quot;-1&quot;&gt;How to close the dialog&lt;/h2&gt;

&lt;p&gt;Modal dialogs can be closed by pressing &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;kbd&amp;gt;Esc&amp;lt;/kbd&amp;gt;&lt;/code&gt;, or you can add a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;button&amp;gt;&lt;/code&gt; element to explicitly close the them with the built-in &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/HTMLDialogElement/close&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;HTMLDialogElement.close()&lt;/code&gt;&lt;/a&gt; method.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JavaScript&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;// Code omitted...&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;dialog&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;dialog&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;closeDialogBtn&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;close&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-c1&quot;&gt;// Code omitted...&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-nx&quot;&gt;closeDialogBtn&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;click&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&amp;gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;dialog&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;You can also declaratively close a dialog by adding a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;form&amp;gt;&lt;/code&gt; element separate from any user inputs in the dialog, and adding &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;method=&quot;dialog&quot;&lt;/code&gt;. This sets the submission behavior for all submit events in that form to close the dialog. Adding markup that closes the dialog declaratively may feel familiar to you if you&#39;ve used component libraries with a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;DialogClose&amp;gt;&lt;/code&gt; component.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;HTML&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;dialog aria-labelledby=&quot;dialog-title&quot; aria-describedby=&quot;description&quot;&amp;gt;
  &amp;lt;form method=&quot;dialog&quot;&amp;gt;
    &amp;lt;button data-button=&quot;close&quot; type=&quot;submit&quot;&amp;gt;Close&amp;lt;/button&amp;gt;
  &amp;lt;/form&amp;gt;
&amp;lt;/dialog&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;h2 id=&quot;popover_rising_to_the_top&quot; data-text=&quot;popover, rising to the top&quot; tabindex=&quot;-1&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;popover&lt;/code&gt;, rising to the top&lt;/h2&gt;

&lt;p&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;popover&lt;/code&gt; attribute is another way to add layered UI patterns to your applications. Unlike the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; element, &lt;em&gt;any&lt;/em&gt; element can be turned into a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;popover&lt;/code&gt;. The two are similar but have some key differences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Popovers don&#39;t make the rest of the page inert, while a modal dialog does.&lt;/li&gt;
&lt;li&gt;Default popovers (&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;popover=&quot;auto&quot;&lt;/code&gt;) can be &quot;light-dismissed&quot;, while modal dialogs opened with &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;dialog.showModal&lt;/code&gt; can only be light-dismissed if the dialog has the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/HTMLDialogElement/closedBy#any&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;closedBy=&quot;any&quot;&lt;/code&gt; attribute&lt;/a&gt; .&lt;/li&gt;
&lt;li&gt;Popovers have no inherent semantics, while a dialog has an implicit ARIA role of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&quot;dialog&quot;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Popover is styled with the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;:popover-open&lt;/code&gt; pseudo class, while a dialog is styled with the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;open&lt;/code&gt; attribute&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;devsite-iframe&gt;&lt;iframe src=&quot;https://web.dev/frame/articles/baseline-in-action-dialog-popover/index_76d3d94ec159048016d62789a639cca432cc7f10dc84f5e68e01d2f0b49280a9.frame&quot; class=&quot;framebox inherit-locale &quot; allow=&quot;clipboard-write https://web-dot-devsite-v2-prod-3p.appspot.com&quot; allowfullscreen=&quot;&quot; is-upgraded=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;&lt;/devsite-iframe&gt;
&lt;devsite-iframe&gt;&lt;iframe src=&quot;https://web.dev/frame/articles/baseline-in-action-dialog-popover/index_4d109dffceb5cd3e8bae4130ec973a04859ad5fec443b4812bd731684dfc332d.frame&quot; class=&quot;framebox inherit-locale &quot; allow=&quot;clipboard-write https://web-dot-devsite-v2-prod-3p.appspot.com&quot; allowfullscreen=&quot;&quot; is-upgraded=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;&lt;/devsite-iframe&gt;&lt;/p&gt;

&lt;p&gt;One behavior that a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; and an element with the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;popover&lt;/code&gt; attribute shares is that when they are open their content is automatically promoted to the top layer. As the name implies, elements in the top layer are rendered outside of the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;document&lt;/code&gt; flow, on top of all other content. Even if content in the normal &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;document&lt;/code&gt; flow has a sky-high &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;z-index&lt;/code&gt;, it will always appear underneath content in the top layer.&lt;/p&gt;

&lt;p&gt;What happens when you open an element that gets promoted to the top layer from inside of another element that&#39;s already open in the top layer? The last element opened inside of the top layer is highest in the top layer stack, so the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;popover&lt;/code&gt; in the demo opens on top of the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;dialog&lt;/code&gt; without you having to configure a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;z-index&lt;/code&gt; position for it.&lt;/p&gt;
&lt;aside class=&quot;note&quot;&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;span&gt; Learn more about the top layer on the &lt;a href=&quot;https://developer.chrome.com/blog/what-is-the-top-layer/&quot;&gt;Chrome for developers blog&lt;/a&gt;.&lt;/span&gt;&lt;/aside&gt;&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;HTML&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;form method=&quot;dialog&quot;&amp;gt;
  &amp;lt;label for=&quot;email&quot;&amp;gt;Enter your email&amp;lt;/label&amp;gt;
  &amp;lt;button id=&quot;popover-trigger&quot; type=&quot;button&quot; popovertarget=&quot;privacy-popover&quot;&amp;gt;
    &amp;lt;span class=&quot;visually-hidden&quot;&amp;gt;How we handle your email&amp;lt;/span&amp;gt;
  &amp;lt;/button&amp;gt;
  &amp;lt;p id=&quot;privacy-popover&quot; popover&amp;gt;As with all of your information, we promise not to sell your email address&amp;lt;/p&amp;gt;
&amp;lt;/form&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;The button that will open the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;popover&lt;/code&gt; is placed next to the label for the email address input, and is made into a control for opening our popover by adding the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;popover&lt;/code&gt;s &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;id&lt;/code&gt; to the value of its &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;popovertarget&lt;/code&gt; attribute. By giving the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;popover&lt;/code&gt; the attribute of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;popover&lt;/code&gt; and assigning it an &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;id&lt;/code&gt; that matches the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;popovertarget&lt;/code&gt; attribute on the popover control, the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;popover&lt;/code&gt; can now be toggled.&lt;/p&gt;

&lt;h2 id=&quot;autofocus&quot; data-text=&quot;Autofocus&quot; tabindex=&quot;-1&quot;&gt;Autofocus&lt;/h2&gt;

&lt;p&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; traps focus inside of itself and when opened, and places focus on the first focusable element inside of it. Use the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;autofocus&lt;/code&gt; attribute if it makes more sense to apply focus on a different element instead. In the demo, the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;input&amp;gt;&lt;/code&gt; element for the email address has an &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;autofocus&lt;/code&gt; attribute, so that users can start entering their email address as soon as the dialog is open.&lt;/p&gt;

&lt;h2 id=&quot;style_the_backdrop&quot; data-text=&quot;Style the ::backdrop&quot; tabindex=&quot;-1&quot;&gt;Style the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;::backdrop&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;As mentioned earlier, a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; opened by the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;showModal()&lt;/code&gt; method is displayed in the top layer, and makes all content underneath it inert. You can style the attached &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;::backdrop&lt;/code&gt; pseudo-element to further emphasize the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt;, and adjust the visibility of content underneath.&lt;/p&gt;

&lt;p&gt;&lt;devsite-iframe&gt;&lt;iframe src=&quot;https://web.dev/frame/articles/baseline-in-action-dialog-popover/index_39547b979e5af8192c61d5e818d9009621763388a2a3fe41b14c037e3e7cce38.frame&quot; class=&quot;framebox inherit-locale &quot; allow=&quot;clipboard-write https://web-dot-devsite-v2-prod-3p.appspot.com&quot; allowfullscreen=&quot;&quot; is-upgraded=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;&lt;/devsite-iframe&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;::backdrop&lt;/code&gt; has a faint &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;linear-gradient&lt;/code&gt; and a blur effect when the dialog is open. The &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/backdrop-filter&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;backdrop-filter&lt;/code&gt;&lt;/a&gt; property lets you apply graphic effects to the area behind an element.&lt;/p&gt;

&lt;p&gt;&lt;devsite-iframe&gt;&lt;iframe src=&quot;https://web.dev/frame/articles/baseline-in-action-dialog-popover/index_cd82dc3dc3293a284dfa9280de7532aaee1ae86b9a45c8e2b9c69e0e68189473.frame&quot; class=&quot;framebox inherit-locale &quot; allow=&quot;clipboard-write https://web-dot-devsite-v2-prod-3p.appspot.com&quot; allowfullscreen=&quot;&quot; is-upgraded=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;&lt;/devsite-iframe&gt;&lt;/p&gt;

&lt;p&gt;To achieve the animation effect on the color stops in the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;linear-gradient&lt;/code&gt;, you first must register the custom properties with the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;@property&lt;/code&gt; rule. Registering the custom property in this way—as opposed to using a standard custom property—lets the browser know what data type to expect, allowing for smooth transitions and animations. You can then define &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;@keyframe&lt;/code&gt;s, or use a transition to specify when and how the color stops change.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;property&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;--backdrop-gradient-start&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;syntax&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;&amp;lt;color&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;initial-value&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;oklch&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;33&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nc&quot;&gt;894&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nc&quot;&gt;08072&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;246&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nc&quot;&gt;33&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;inherits&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;property&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;--backdrop-gradient-end&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;syntax&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;&amp;lt;color&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;initial-value&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;oklch&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;45&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nc&quot;&gt;859&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nc&quot;&gt;00345&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;174&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nc&quot;&gt;48&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nc&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;inherits&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-c&quot;&gt;/* ... */&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-nt&quot;&gt;dialog&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nd&quot;&gt;backdrop&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;opacity&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;linear-gradient&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;140&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;deg&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--backdrop-gradient-start&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;),&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--backdrop-gradient-end&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;backdrop-filter&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;blur&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;px&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;animation&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;show&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;gradient&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--transition-timing-slower&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;forwards&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;keyframes&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;show-gradient&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--backdrop-gradient-start&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;oklch&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;45.859&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;0.00345&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;174.48&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;0.3&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--backdrop-gradient-end&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;oklch&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;33.894&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;0.08072&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;246.33&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;0.3&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--backdrop-gradient-start&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;oklch&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;33.894&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;0.08072&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;246.33&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--backdrop-gradient-end&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;oklch&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;45.859&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;0.00345&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;174.48&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;0.3&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;&lt;devsite-iframe&gt;&lt;iframe src=&quot;https://web.dev/frame/articles/baseline-in-action-dialog-popover/index_bf9288640d1d3bbfb2c4ec7366d0357fd2f4e5cab541aca8e31ab7421389adf4.frame&quot; class=&quot;framebox inherit-locale &quot; allow=&quot;clipboard-write https://web-dot-devsite-v2-prod-3p.appspot.com&quot; allowfullscreen=&quot;&quot; is-upgraded=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;&lt;/devsite-iframe&gt;&lt;/p&gt;

&lt;p&gt;You can use a different technique to animate the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;dialog&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;popover&lt;/code&gt; and you&#39;ll see how that works in the next section.&lt;/p&gt;

&lt;h2 id=&quot;make_an_entrance_and_exit_with_starting-style&quot; data-text=&quot;Make an entrance (and exit) with @starting-style&quot; tabindex=&quot;-1&quot;&gt;Make an entrance (and exit) with &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;@starting-style&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; element and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;popover&lt;/code&gt; both go from being &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;display: none&lt;/code&gt; to &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;display: block&lt;/code&gt; by default. Due to how &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/@starting-style#description&quot;&gt;CSS transitions typically handle&lt;/a&gt; this flip of the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;display&lt;/code&gt; property, elements having their &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;display&lt;/code&gt; set to any visible &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;display&lt;/code&gt; property from &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;display: none&lt;/code&gt; enter and exit the page immediately. Thankfully, with a combination of Baseline features &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;@starting-style&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;transition-behavior&lt;/code&gt; you can use transitions to make their entrances and exits more seamless.&lt;/p&gt;

&lt;p&gt;In the next step, you&#39;ll animate the dialog. The demo transitions the dialog&#39;s &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;opacity&lt;/code&gt;, &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;translate&lt;/code&gt;, &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;overlay&lt;/code&gt;, and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;display&lt;/code&gt; properties. You can achieve this by setting values for the dialog&#39;s three states.&lt;/p&gt;

&lt;p&gt;The open state describes how the dialog should look once it is open:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;dialog&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--opacity&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--translate&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;&lt;aside class=&quot;note&quot;&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;span&gt; that you don&#39;t have to explicitly set the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;display&lt;/code&gt; value, unless you need the display value of the dialog to be something other than &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;display: block&lt;/code&gt; when it becomes visible.&lt;/span&gt;&lt;/aside&gt;
&lt;p&gt;The state in between, the transitioning state, is made by adding a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;@starting-style&lt;/code&gt; block. These are the styles the browser will use when the dialog is first rendered in the DOM, as it transitions from &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;display: none&lt;/code&gt; to &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;display: block&lt;/code&gt;. Without the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;@starting-style&lt;/code&gt; block, the transition would fail, since the element wouldn&#39;t have a previous state from which to transition.&lt;/p&gt;

&lt;p&gt;&lt;devsite-iframe&gt;&lt;iframe src=&quot;https://web.dev/frame/articles/baseline-in-action-dialog-popover/index_5c2f8ac66a3ab96469d525ce8a5b2f3c8a6d684526db8075a32066deee2347dc.frame&quot; class=&quot;framebox inherit-locale &quot; allow=&quot;clipboard-write https://web-dot-devsite-v2-prod-3p.appspot.com&quot; allowfullscreen=&quot;&quot; is-upgraded=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;&lt;/devsite-iframe&gt;&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-c&quot;&gt;/* Open state */&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-nt&quot;&gt;dialog&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--opacity&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--translate&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-c&quot;&gt;/* Transitioning state */&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;@starting-style&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--opacity&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--translate&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;vw&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;-10&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;rem&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-err&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;&lt;aside class=&quot;note&quot;&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;span&gt; To avoid &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;@starting-style&lt;/code&gt; rules being overridden, the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;@starting-style&lt;/code&gt; block should come after the open state styles in your stylesheet.&lt;/span&gt;&lt;/aside&gt;
&lt;p&gt;The last state to include to successfully transition elements like &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;dialog&lt;/code&gt; is the closed state. This is the default or exit state that the element will return to as it transitions to &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;display: none&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here you can include the specific transition settings for each property you are animating. In addition to the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;opacity&lt;/code&gt;, &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;translate&lt;/code&gt;, and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;display&lt;/code&gt; properties you must also transition the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/overlay&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;overlay&lt;/code&gt;&lt;/a&gt; property for animations to work.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-c&quot;&gt;/* Closed state */&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-nt&quot;&gt;dialog&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;opacity&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--opacity&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;translate&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--translate&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;vw&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;-10&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;rem&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;transition&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;opacity&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;ease-in&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;translate&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;0.6&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;ease-in-out&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;overlay&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;0.6&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;ease-in-out&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;0.6&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;linear&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;transition-behavior&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;allow-discrete&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;The final step is to specify the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;transition-behavior&lt;/code&gt; for the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;overlay&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;display&lt;/code&gt; properties that normally have &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/transition-behavior#discrete_animation_behavior&quot;&gt;discrete animation behavior&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;devsite-iframe&gt;&lt;iframe src=&quot;https://web.dev/frame/articles/baseline-in-action-dialog-popover/index_9ccd83fcc3357fea4983619b8f3aedd418fb41b2f0eea9971633fa53b98122b9.frame&quot; class=&quot;framebox inherit-locale &quot; allow=&quot;clipboard-write https://web-dot-devsite-v2-prod-3p.appspot.com&quot; allowfullscreen=&quot;&quot; is-upgraded=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;&lt;/devsite-iframe&gt;&lt;/p&gt;
&lt;aside class=&quot;note&quot;&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;span&gt; Using &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;overlay&lt;/code&gt; as an &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;allow-discrete&lt;/code&gt; transition can be a progressive enhancement until this functionality is Baseline. In the meantime, you might want to put CSS transitions that rely on &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;overlay&lt;/code&gt; inside of a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;@supports&lt;/code&gt; rule to prevent unwanted layout effects.&lt;/span&gt;&lt;/aside&gt;
&lt;h2 id=&quot;conclusion&quot; data-text=&quot;Conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Reaching for the web platform&#39;s tools can make it easier to implement layered UI patterns accessibly, and can keep you from worrying about handling all of the expected functionality yourself. This gives you more time to focus on enhancing the user experience with platform features, and less time debugging custom or library implementations.&lt;/p&gt;
  

  
</description><link>https://web.dev/articles/baseline-in-action-dialog-popover?hl=en</link><guid isPermaLink="false">https://web.dev/articles/baseline-in-action-dialog-popover?hl=en</guid><pubDate>Tue, 25 Nov 2025 08:00:00 GMT</pubDate></item><item><title>How to implement an image gallery using Baseline features</title><description>&lt;p&gt;&lt;style&gt;
  .wd-author__links {
    display: flex;
  }
  &lt;/style&gt;

&lt;/p&gt;

&lt;p class=&quot;wd-pubdates&quot; style=&quot;font-size: 90%; border-bottom: 1px solid var(--devsite-ref-palette--grey200);color: var(--devsite-ref-palette--blue-grey700);&quot;&gt;
  Published: October 29, 2025
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;From image sharing sites to online stores, image galleries are a common pattern on the web. Images can be very data heavy, and loading images can make the page take a long time to load. In addition, users have high expectations around the usability of galleries, so it&#39;s common to add additional libraries that further increase page size.&lt;/p&gt;

&lt;p&gt;The web platform has added support for many of the underlying pieces of an image gallery. So, what does it look like to code an image gallery with Baseline features? This demo showcases a variety of techniques that you can use to do just that.&lt;/p&gt;

&lt;div class=&quot;wd-embed&quot; style=&quot;height: 544px; width: 100%&quot;&gt;
   &lt;iframe allow=&quot;camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;&quot; loading=&quot;lazy&quot; src=&quot;https://codepen.io/web-dot-dev/embed/yyeEwJJ?height=250&amp;amp;theme-id=light&amp;amp;default-tab=result&amp;amp;editable=true&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot; title=&quot;Pen yyeEwJJ by web-dot-dev on Codepen&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
   &lt;/iframe&gt;
&lt;/div&gt;
&lt;aside class=&quot;note&quot;&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;span&gt; This demo is also &lt;a href=&quot;https://github.com/GoogleChromeLabs/baseline-demos/tree/main/src/image-gallery&quot;&gt;available on GitHub&lt;/a&gt;.&lt;/span&gt;&lt;/aside&gt;
&lt;h2 id=&quot;how_to_deal_with_large_image_sizes&quot; data-text=&quot;How to deal with large image sizes&quot; tabindex=&quot;-1&quot;&gt;How to deal with large image sizes&lt;/h2&gt;

&lt;p&gt;Images can be some of the largest assets you ask your users to download, and image galleries by their nature often have many images. This demo uses a couple of Baseline features to help reduce the performance impact on users.&lt;/p&gt;

&lt;h3 id=&quot;lazy_loading&quot; data-text=&quot;Lazy loading&quot; tabindex=&quot;-1&quot;&gt;Lazy loading&lt;/h3&gt;

&lt;p&gt;Loading all of the images when the page loads can make the other content slower to load overall. It&#39;s a useful pattern to defer loading of images that aren&#39;t on the screen until they are on the screen.&lt;/p&gt;

&lt;p&gt;But it&#39;s a balance—you don&#39;t want users to have to wait for images to load as they scroll. You want them to already be loaded when they scroll into view.&lt;/p&gt;

&lt;p&gt;JavaScript libraries have made this possible in the past, and you could also roll your own with an IntersectionObserver. However, both options mean shipping more code, and leave more room for bugs.&lt;/p&gt;

&lt;p&gt;Browsers have made this possible with an HTML-based progressive enhancement: By adding &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;loading=&quot;lazy&quot;&lt;/code&gt; to &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;img&amp;gt;&lt;/code&gt; elements, the browser will defer loading those images until they are needed.&lt;/p&gt;

&lt;p&gt;&lt;devsite-iframe&gt;&lt;iframe src=&quot;https://web.dev/frame/articles/baseline-in-action-image-gallery/index_0970153533b85366bf3d596e8f6a6a784912e4cdaa7b9ae2698d8b058005925e.frame&quot; class=&quot;framebox inherit-locale &quot; allow=&quot;clipboard-write https://web-dot-devsite-v2-prod-3p.appspot.com&quot; allowfullscreen=&quot;&quot; is-upgraded=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;&lt;/devsite-iframe&gt;&lt;/p&gt;

&lt;p&gt;If you have a sense of how many images will be seen on screens when the page is first loaded, you can set &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;loading=&quot;eager&quot;&lt;/code&gt; on the first few images, to make sure that those are loaded immediately. Because &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;loading=&quot;eager&quot;&lt;/code&gt; is the default, you can also just omit the attribute for those images.&lt;/p&gt;

&lt;p&gt;If you view the Network panel in your browser&#39;s developer tools as you load the demo, only the first six will load immediately. Depending on your screen size and how many are visible, several others may be loaded. As you scroll, the browser will load the remaining images.&lt;/p&gt;

&lt;p&gt;Lazy loading images works with more than just scrolling. If an image is initially hidden—for example, in a dialog—the image won&#39;t load until the dialog is opened. This would be useful in an image gallery if you are loading a larger image size when the user clicks on an image.&lt;/p&gt;

&lt;p&gt;When you add lazy loading to your images, make sure that you are providing dimensions for the image. This prevents a layout shift if the image doesn&#39;t get loaded in time. In this demo, the inline size is determined by the grid definition, and the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;aspect-ratio&lt;/code&gt; CSS property on the image gallery grid items is set to &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1 / 1&lt;/code&gt;, so the block size matches that. You can also &lt;a href=&quot;https://web.dev/articles/browser-level-image-lazy-loading#dimension-attributes&quot;&gt;set dimension attributes&lt;/a&gt; directly.&lt;/p&gt;

&lt;h3 id=&quot;avif&quot; data-text=&quot;AVIF&quot; tabindex=&quot;-1&quot;&gt;AVIF&lt;/h3&gt;
&lt;aside class=&quot;note&quot;&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;span&gt; To learn more about using images on the web, check out the &lt;a href=&quot;https://web.dev/learn/images&quot;&gt;Learn Images course&lt;/a&gt;.&lt;/span&gt;&lt;/aside&gt;
&lt;p&gt;While you want to show your users high resolution images, you likely don&#39;t want to show them the full resolution image, especially in the gallery of thumbnails. You&#39;ll want to either compress and resize your images before uploading to your server, or configure your server to resize images for you.&lt;/p&gt;

&lt;p&gt;The AVIF file format is a relatively new format that can be an alternative to legacy formats such as JPEG and PNG, and is worth investigating for potential loading performance benefits.&lt;/p&gt;

&lt;p&gt;&lt;devsite-iframe&gt;&lt;iframe src=&quot;https://web.dev/frame/articles/baseline-in-action-image-gallery/index_bf547c7eb797a0cdc12daf612a2c1aaae2aabe45f4488cbafc3188f4c4c7b5e1.frame&quot; class=&quot;framebox inherit-locale &quot; allow=&quot;clipboard-write https://web-dot-devsite-v2-prod-3p.appspot.com&quot; allowfullscreen=&quot;&quot; is-upgraded=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;&lt;/devsite-iframe&gt;&lt;/p&gt;

&lt;p&gt;Along with supporting animated images and transparency, AVIF can have lower file sizes than other image formats. The images in this demo were converted to AVIF from JPG, and are much smaller while retaining suitable image quality. AVIF also supports HDR images.&lt;/p&gt;

&lt;p&gt;Online tools like &lt;a href=&quot;https://squoosh.app/&quot;&gt;Squoosh&lt;/a&gt; and &lt;a href=&quot;https://cloudinary.com/tools/image-to-avif&quot;&gt;Cloudinary&lt;/a&gt; are useful for one-off conversions to AVIF if you&#39;re looking to experiment with the format. Design tools such as Photoshop (version 26+) and GIMP also export to AVIF. You can also incorporate AVIF encoding in your image
processing pipelines using &lt;a href=&quot;https://imagemagick.org/script/formats.php/#supported&quot;&gt;ImageMagick&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;lightbox_effect&quot; data-text=&quot;Lightbox effect&quot; tabindex=&quot;-1&quot;&gt;Lightbox effect&lt;/h2&gt;

&lt;p&gt;In an image gallery, users have the expectation that they can click an image to view a larger version. In the past, this often required &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;z-index&lt;/code&gt; hacks, but now you can use the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; element, which is Baseline Widely available. Built with accessibility in mind, the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; element handles focus management more effectively, and requires less custom JavaScript.&lt;/p&gt;

&lt;p&gt;&lt;devsite-iframe&gt;&lt;iframe src=&quot;https://web.dev/frame/articles/baseline-in-action-image-gallery/index_40afad7b4072ec4593e4cfd94c625600db5fa4abf94520feb00525d37543fbea.frame&quot; class=&quot;framebox inherit-locale &quot; allow=&quot;clipboard-write https://web-dot-devsite-v2-prod-3p.appspot.com&quot; allowfullscreen=&quot;&quot; is-upgraded=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;&lt;/devsite-iframe&gt;&lt;/p&gt;

&lt;p&gt;A dialog places your content in a new top layer, on top of everything else. To create the lightbox effect, you can use the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;::backdrop&lt;/code&gt; pseudo element. This is a styleable box below the dialog, but above the rest of the page. This all works without you needing to worry about the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;z-index&lt;/code&gt; of anything.&lt;/p&gt;

&lt;p&gt;&lt;devsite-iframe&gt;&lt;iframe src=&quot;https://web.dev/frame/articles/baseline-in-action-image-gallery/index_39547b979e5af8192c61d5e818d9009621763388a2a3fe41b14c037e3e7cce38.frame&quot; class=&quot;framebox inherit-locale &quot; allow=&quot;clipboard-write https://web-dot-devsite-v2-prod-3p.appspot.com&quot; allowfullscreen=&quot;&quot; is-upgraded=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;&lt;/devsite-iframe&gt;&lt;/p&gt;

&lt;h3 id=&quot;transition_the_lightbox&quot; data-text=&quot;Transition the lightbox&quot; tabindex=&quot;-1&quot;&gt;Transition the lightbox&lt;/h3&gt;

&lt;p&gt;By default, a dialog appears and disappears with no transition effects. Adding some slight transitions can make the experience feel more smooth, and help the user mentally transition. The demo uses &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;allow-discrete&lt;/code&gt; transition behavior and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;@starting-style&lt;/code&gt; for the dialog to define the transition for the dialog.&lt;/p&gt;

&lt;p&gt;&lt;devsite-iframe&gt;&lt;iframe src=&quot;https://web.dev/frame/articles/baseline-in-action-image-gallery/index_d1ce846b9770557fdeba887bbd55b445a18b6e694ebb58c9fbecc8cffce83a82.frame&quot; class=&quot;framebox inherit-locale &quot; allow=&quot;clipboard-write https://web-dot-devsite-v2-prod-3p.appspot.com&quot; allowfullscreen=&quot;&quot; is-upgraded=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;&lt;/devsite-iframe&gt;
&lt;devsite-iframe&gt;&lt;iframe src=&quot;https://web.dev/frame/articles/baseline-in-action-image-gallery/index_5c2f8ac66a3ab96469d525ce8a5b2f3c8a6d684526db8075a32066deee2347dc.frame&quot; class=&quot;framebox inherit-locale &quot; allow=&quot;clipboard-write https://web-dot-devsite-v2-prod-3p.appspot.com&quot; allowfullscreen=&quot;&quot; is-upgraded=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;&lt;/devsite-iframe&gt;&lt;/p&gt;

&lt;h2 id=&quot;style_the_button&quot; data-text=&quot;Style the button&quot; tabindex=&quot;-1&quot;&gt;Style the button&lt;/h2&gt;

&lt;h3 id=&quot;backdrop_filter&quot; data-text=&quot;Backdrop filter&quot; tabindex=&quot;-1&quot;&gt;Backdrop filter&lt;/h3&gt;

&lt;p&gt;A common design pattern for image galleries is to place text on top of the image. This comes with the risk that the image won&#39;t provide sufficient contrast with the text, making the text difficult or impossible to read. One way of handling that is to add a semi-transparent background behind the text, but this doesn&#39;t always provide the needed effect. The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;backdrop-filter&lt;/code&gt; CSS property gives you more control over the effect.&lt;/p&gt;

&lt;p&gt;&lt;devsite-iframe&gt;&lt;iframe src=&quot;https://web.dev/frame/articles/baseline-in-action-image-gallery/index_cd82dc3dc3293a284dfa9280de7532aaee1ae86b9a45c8e2b9c69e0e68189473.frame&quot; class=&quot;framebox inherit-locale &quot; allow=&quot;clipboard-write https://web-dot-devsite-v2-prod-3p.appspot.com&quot; allowfullscreen=&quot;&quot; is-upgraded=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;&lt;/devsite-iframe&gt;&lt;/p&gt;

&lt;p&gt;With &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;backdrop-filter&lt;/code&gt;, you can combine color effects and blurs that apply to the content behind the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; element. You can also use pseudo classes to update the filter when the button is hovered or focused.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;button&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--btn-filter&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;blur&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;em&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;backdrop-filter&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--btn-filter&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&amp;amp;&lt;span class=&quot;devsite-syntax-err&quot;&gt;:hover,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&amp;amp;&lt;span class=&quot;devsite-syntax-err&quot;&gt;:focus&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--btn-filter&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;blur&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;0.75&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;em&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;saturate&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;350&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-err&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;h3 id=&quot;resolution_media_query&quot; data-text=&quot;Resolution media query&quot; tabindex=&quot;-1&quot;&gt;Resolution media query&lt;/h3&gt;

&lt;p&gt;&lt;devsite-iframe&gt;&lt;iframe src=&quot;https://web.dev/frame/articles/baseline-in-action-image-gallery/index_242cab73cdedc26a18f7cc164f4a775bb582082ca138507ef036c5eb6ad6821c.frame&quot; class=&quot;framebox inherit-locale &quot; allow=&quot;clipboard-write https://web-dot-devsite-v2-prod-3p.appspot.com&quot; allowfullscreen=&quot;&quot; is-upgraded=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;&lt;/devsite-iframe&gt;&lt;/p&gt;

&lt;p&gt;The demo also uses a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;resolution&lt;/code&gt; media query to add a bit of extra polish. The button&#39;s top border is &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1px&lt;/code&gt;, which will serve as a default size, but you can choose to display a subpixel border on higher resolution screens.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;media&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;min-resolution&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;2x&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;--border-size&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nc&quot;&gt;5px&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;h2 id=&quot;aspect_ratio&quot; data-text=&quot;Aspect ratio&quot; tabindex=&quot;-1&quot;&gt;Aspect ratio&lt;/h2&gt;

&lt;p&gt;&lt;devsite-iframe&gt;&lt;iframe src=&quot;https://web.dev/frame/articles/baseline-in-action-image-gallery/index_78645a768143a62b7fa429bfbb04031b475d19ee2d35652b6932fcda7ce73aaa.frame&quot; class=&quot;framebox inherit-locale &quot; allow=&quot;clipboard-write https://web-dot-devsite-v2-prod-3p.appspot.com&quot; allowfullscreen=&quot;&quot; is-upgraded=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;&lt;/devsite-iframe&gt;&lt;/p&gt;

&lt;p&gt;For the images to be displayed in a consistent size, you can set an aspect ratio. This works best when paired with &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;object-fit&lt;/code&gt; to instruct the image how best to fill the space.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;data-img&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;aspect-ratio&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;object-fit&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;cover&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;The gallery example uses an aspect ratio of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1 / 1&lt;/code&gt; meaning the width and height will be equal. The width comes from &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;grid-template-columns&lt;/code&gt; which has a minimum size of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;16em&lt;/code&gt;, but can grow until there is enough room to add another column. The height then matches the width.&lt;/p&gt;

&lt;p&gt;With an object-fit value of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;cover&lt;/code&gt;, this will ensure each image fills its container in both dimensions maintaining its intrinsic aspect ratio, and will be cropped if it&#39;s overflowing.&lt;/p&gt;

&lt;p&gt;As you saw in the section on lazy loading images, this demo also uses &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;aspect-ratio&lt;/code&gt; to make sure an image doesn&#39;t change dimensions when it loads, preventing a layout shift.&lt;/p&gt;

&lt;h2 id=&quot;take_it_further&quot; data-text=&quot;Take it further&quot; tabindex=&quot;-1&quot;&gt;Take it further&lt;/h2&gt;

&lt;p&gt;There are other Baseline features that you may want to incorporate into your image gallery. If you add navigation to the previous and next images from within the dialog, you may want to animate the change with &lt;a href=&quot;https://web.dev/learn/css/view-transitions-spas&quot;&gt;view transitions&lt;/a&gt;. If you have the ability to generate multiple sizes of the images, you can use &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/picture&quot;&gt;the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; element&lt;/a&gt; to serve different resolutions of images on different screen sizes.&lt;/p&gt;

&lt;p&gt;Baseline web features are making it possible to make a performant and usable image gallery with less code, and without relying on additional libraries. The next time you need to build an image gallery, you can use this demo as a pattern, or you can adopt pieces of it to enhance your current tools today.&lt;/p&gt;
  

  
</description><link>https://web.dev/articles/baseline-in-action-image-gallery?hl=en</link><guid isPermaLink="false">https://web.dev/articles/baseline-in-action-image-gallery?hl=en</guid><pubDate>Wed, 29 Oct 2025 07:00:00 GMT</pubDate></item><item><title>Container queries and units in action</title><description>&lt;p&gt;&lt;style&gt;
  .wd-author__links {
    display: flex;
  }
  &lt;/style&gt;

&lt;/p&gt;

&lt;p class=&quot;wd-pubdates&quot; style=&quot;font-size: 90%; border-bottom: 1px solid var(--devsite-ref-palette--grey200);color: var(--devsite-ref-palette--blue-grey700);&quot;&gt;
  Published: October 23, 2025
&lt;/p&gt;

&lt;devsite-iframe&gt;&lt;iframe src=&quot;https://web.dev/frame/articles/baseline-in-action-container-queries/index_d4944f7b8a2fa9281042fbdeabb8d4d974b3486dcc2af5bd8a3d0c32d0c56356.frame&quot; class=&quot;framebox inherit-locale &quot; allow=&quot;clipboard-write https://web-dot-devsite-v2-prod-3p.appspot.com&quot; allowfullscreen=&quot;&quot; is-upgraded=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;&lt;/devsite-iframe&gt;&lt;p&gt;&lt;/p&gt;

&lt;div class=&quot;wd-embed&quot; style=&quot;height: 544px; width: 100%&quot;&gt;
   &lt;iframe allow=&quot;camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;&quot; loading=&quot;lazy&quot; src=&quot;https://codepen.io/web-dot-dev/embed/XJXEOEg?height=250&amp;amp;theme-id=light&amp;amp;default-tab=result&amp;amp;editable=true&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot; title=&quot;Pen XJXEOEg by web-dot-dev on Codepen&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
   &lt;/iframe&gt;
&lt;/div&gt;
&lt;aside class=&quot;note&quot;&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;span&gt; This demo is also &lt;a href=&quot;https://github.com/GoogleChromeLabs/baseline-demos/tree/main/src/container-queries&quot;&gt;available on GitHub&lt;/a&gt;.&lt;/span&gt;&lt;/aside&gt;
&lt;p&gt;One of the goals when writing CSS is to build component parts that will adapt well to different (and unexpected) contexts. Ideally, a component can be placed inside any &quot;container&quot; element without it feeling broken or out of place. How can you accomplish this in a complex layout like a store where the primary component—the &quot;product&quot;—has to fit into a variety of list layouts, including the sidebar?&lt;/p&gt;

&lt;h2 id=&quot;responsive_typography_with_cqi_units&quot; data-text=&quot;Responsive typography with cqi units&quot; tabindex=&quot;-1&quot;&gt;Responsive typography with &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;cqi&lt;/code&gt; units&lt;/h2&gt;

&lt;p&gt;The first step is to define some basic sizing variables that could be reused across the project—starting with whitespace sizes. But before creating any custom properties, the browser provides some useful named values as CSS units:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1em&lt;/code&gt;: the current font size.&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1rem&lt;/code&gt;: the font size on the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;:root (html)&lt;/code&gt; element.&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1lh&lt;/code&gt; / &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1rlh&lt;/code&gt;: the current and root line heights.&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1vw&lt;/code&gt;: the viewport width.&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1vi&lt;/code&gt;: the viewport &quot;inline&quot; size (for English, this is the same as &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;vw&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1cqi&lt;/code&gt;: the inline size of the nearest &quot;container&quot; (defaulting to the viewport).&lt;/li&gt;
&lt;/ul&gt;
&lt;aside class=&quot;note&quot;&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;span&gt; &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;lh&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;rlh&lt;/code&gt; units are &lt;a href=&quot;https://web.dev/baseline&quot;&gt;Baseline&lt;/a&gt; Newly available since November of 2023. Read more about relative lengths in the &lt;a href=&quot;https://web.dev/learn/css/sizing#relative_lengths&quot;&gt;Learn CSS sizing units module&lt;/a&gt;.&lt;/span&gt;&lt;/aside&gt;
&lt;p&gt;You can think of these units as common variables provided by the browser, with a shorthand syntax for multiplication. If you wanted half of a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;--line-height&lt;/code&gt; custom property in CSS, you would need to write the entire calculation &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;calc(0.5 * var(--line-height))&lt;/code&gt;, but with the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;lh&lt;/code&gt; unit, you can ask for &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;0.5lh&lt;/code&gt; instead.&lt;/p&gt;

&lt;p&gt;Custom properties like &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;--brand-color&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;--button-background&lt;/code&gt; have different meanings and serve a different purpose, even when they result in the same &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;deepPink&lt;/code&gt; color (another browser-provided variable). Similarly, &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1em&lt;/code&gt; might sometimes be equal to &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;16px&lt;/code&gt;, but that&#39;s not a stable relationship. Like any other variable, units should be used to express a relationship, rather than an expected value.&lt;/p&gt;

&lt;p&gt;Both &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1em&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1lh&lt;/code&gt; are font-relative units that could be used for spacing, but only one of them has a reliable relationship to the current line height. If this page involved a lot of elements with prose in them—paragraphs and lists, for example—the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;lh&lt;/code&gt; unit would work well for spacing between them:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;ul&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;ol&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;margin-block&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;lh&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;That will maintain a consistent baseline rhythm, without any extra work. But this page has almost no prose. Instead of spacing within a flow of text, this layout requires spacing to push text away from the edge of a card, and spacing between columns and rows in a grid or stacked layout. In this case there are several things to consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiples (or fractions) of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1lh&lt;/code&gt; may still be useful for maintaining vertical rhythm across the page.&lt;/li&gt;
&lt;li&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;cqi&lt;/code&gt; unit would account for the amount of &lt;em&gt;available space&lt;/em&gt; in a given context.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By combining these two units in a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;round()&lt;/code&gt; function, the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;--gap&lt;/code&gt; variable is based primarily on the container size, but rounded up to a multiple of quarter-lines:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--gap&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;round&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;up&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;cqi&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;0.25&lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;lh&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;&lt;devsite-iframe&gt;&lt;iframe src=&quot;https://web.dev/frame/articles/baseline-in-action-container-queries/index_7658f9dcdc87c46d99c89c621561b34ec0590ba20b6ea8c78025459873b7e6d5.frame&quot; class=&quot;framebox inherit-locale &quot; allow=&quot;clipboard-write https://web-dot-devsite-v2-prod-3p.appspot.com&quot; allowfullscreen=&quot;&quot; is-upgraded=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;&lt;/devsite-iframe&gt;&lt;/p&gt;

&lt;p&gt;If the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;line-height&lt;/code&gt; is &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;20px&lt;/code&gt;, then the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;--&lt;/code&gt;gap will be multiples of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;5px&lt;/code&gt;—but the exact multiple will depend on available space. If either the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;line-height&lt;/code&gt; or available space change, the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;--gap&lt;/code&gt; variable will adapt to its new context. To make the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;--gap&lt;/code&gt; consistent across the entire design, you could replace the container-relative &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;cqi&lt;/code&gt; units with viewport-relative &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;vi&lt;/code&gt; units.&lt;/p&gt;

&lt;p&gt;The same approach is useful when establishing &quot;fluid&quot; font sizes that respond to available space. This &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;--body-text&lt;/code&gt; variable is based on the user-provided font preference, with some range to adapt based on the container. In this case, &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;clamp()&lt;/code&gt; ensures the font will be at least as large as the user&#39;s preference, can grow some as the container size increases, but will stop growing at some point:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--body-text&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;clamp&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;rem&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;0.875&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;rem&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;cqi&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;1.25&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;rem&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;ul&gt;
&lt;li&gt;The range is clamped between &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1rem&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1.25rem&lt;/code&gt;, to stay near the user-selected font size.&lt;/li&gt;
&lt;li&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;cqi&lt;/code&gt; value determines how &lt;em&gt;fast&lt;/em&gt; the font will grow or shrink in relation to available space, which is added to a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;rem&lt;/code&gt; value to offset that growth based on the user-selected size.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keeping the central &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;rem&lt;/code&gt; value near &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1&lt;/code&gt; and the added &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;cqi&lt;/code&gt; value low ensures that users still have significant control when zooming in or out. You can adjust those two values, and then use browser zoom to see how they interact. The closer you get to &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;100cqi&lt;/code&gt;, and the farther you fall below &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1rem&lt;/code&gt;, the less influence user font preferences will have—and the less font sizes will respond to zooming in or out.&lt;/p&gt;

&lt;p&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;--item-title&lt;/code&gt; variable is slightly larger and more responsive, and the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;--list-title&lt;/code&gt; size responds to the viewport size (using &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;vi&lt;/code&gt;) rather than the immediate container size. That way item headings respond to their context, but the main list headings all match in size no matter where they show up.&lt;/p&gt;
&lt;aside class=&quot;note&quot;&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;span&gt; Product titles in the cart sidebar might be slightly different from product titles in the main list—but the &quot;cart&quot; heading is always identical to the &quot;products&quot; list heading.&lt;/span&gt;&lt;/aside&gt;
&lt;h2 id=&quot;defining_containers_to_measure_in_context&quot; data-text=&quot;Defining containers to measure in context&quot; tabindex=&quot;-1&quot;&gt;Defining containers to measure in context&lt;/h2&gt;

&lt;p&gt;At this point, container query units have been used to create adaptive typography on a web page, but new containers haven&#39;t been defined.&lt;/p&gt;

&lt;p&gt;By default, &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1cqi&lt;/code&gt; (&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1/100&lt;/code&gt; container query inline size) is the same as &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1svi&lt;/code&gt; (&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1/100&lt;/code&gt; small viewport inline size) because the &lt;a href=&quot;https://web.dev/learn/css/sizing#alternative_viewport-relative_units&quot;&gt;&quot;small&quot; viewport&lt;/a&gt; acts as the initial container for any web page. In order to take full advantage of the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;cqi&lt;/code&gt; unit, you need to define additional &quot;containers&quot; within the page. The primary layout containers on this page are the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;product-list&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;shopping-cart&lt;/code&gt;—so they are set to expose their &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;inline-size&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;product-list&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-nt&quot;&gt;shopping-cart&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;container-type&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;inline&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;&lt;aside class=&quot;note&quot;&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;span&gt; &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;div&amp;gt;&lt;/code&gt; or &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;section&amp;gt;&lt;/code&gt; elements with &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;.product-list&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;.shopping-cart&lt;/code&gt; classes would also work here. Since those have no semantic meaning or functionality in HTML, it also works to define unregistered custom elements with those names. This doesn&#39;t impact the CSS, except that custom elements use a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;display&lt;/code&gt; of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;inline&lt;/code&gt; by default.&lt;/span&gt;&lt;/aside&gt;
&lt;p&gt;Container query units—including &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;cqi&lt;/code&gt;—aren&#39;t able to measure the element that they are used on. If you set the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;width&lt;/code&gt; of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;shopping-cart&lt;/code&gt; to &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;25cqi&lt;/code&gt;, it would be a paradox to determine the container&#39;s width based on its own width! Instead, the result will be based on the next ancestor container in the tree hierarchy &lt;em&gt;that contains&lt;/em&gt; &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;shopping-cart&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;product-detail&lt;/code&gt; cards are part of a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;product-list&lt;/code&gt; grid that can be changed by the user. The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;list&lt;/code&gt; layout option displays each card at full width. In that case, referring to the parent container size is useful, but both the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;small-grid&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;large-grid&lt;/code&gt; options cause the card to grow and shrink depending on how many columns fit into the container. Even when the container is quite large, the cards can remain tightly packed into smaller grid cells.&lt;/p&gt;

&lt;p&gt;There&#39;s currently no way to declare those grid cells as &quot;containers&quot; directly. Instead, an extra element is needed to measure within each cell. That&#39;s why each &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;product-detail&lt;/code&gt; instance has an &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;article&amp;gt;&lt;/code&gt; element nested directly inside. &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;product-detail &amp;gt; article&lt;/code&gt; is the card to be styled, while &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;product-detail&lt;/code&gt; itself is used only as a container to measure. That allows the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;cqi&lt;/code&gt;-based text and spacing calculations previously defined to be recalculated for the space available to each card.&lt;/p&gt;

&lt;h2 id=&quot;explicit_container_queries&quot; data-text=&quot;Explicit container queries&quot; tabindex=&quot;-1&quot;&gt;Explicit container queries&lt;/h2&gt;

&lt;p&gt;Container units are powerful, but sometimes it&#39;s useful to make more dramatic changes in a component layout when the available size crosses a threshold. These are often called &lt;em&gt;breakpoints&lt;/em&gt;—since the fix is applied at the point when a given layout begins to break. You may already be familiar with using &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;@media&lt;/code&gt; to add breakpoints based on the viewport size:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;grid&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;grid-template&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;controls&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;auto&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;cart&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;fr&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;list&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;auto&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;minmax&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;-content&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;fr&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;@media&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;(width&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &amp;gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;30em)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;grid-template&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;controls controls&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;auto&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;list cart&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;fr&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;fr&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;fr&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-err&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;Here, &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;shopping-cart&lt;/code&gt; appears above the main &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;product-list&lt;/code&gt; on small screens—but at a certain point, there&#39;s more horizontal space, and a sidebar makes more sense. Since that shift depends on the overall viewport, a media query is used to handle the change. However, the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;product-list&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;product-detail&lt;/code&gt; components might appear in different contexts, somewhat independent of the viewport size. When the viewport grows wider than the sidebar breakpoint, the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;shopping-cart&lt;/code&gt; component suddenly becomes &lt;em&gt;smaller&lt;/em&gt;, providing less space for products inside. This is where container queries become necessary. The syntax is nearly identical to a media query, but uses &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;@container&lt;/code&gt; rather than &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;@media&lt;/code&gt; at the start of the rule:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;article&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;grid&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;grid-template&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;image&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;auto&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;title&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;auto&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;summary&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;auto&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;button&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;auto&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;auto&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;@container&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;(inline-size&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &amp;gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;40ch)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nv&quot;&gt;--image-ratio&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;grid-template&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;image title&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;auto&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;image summary&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;fr&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;image button&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;auto&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;minmax&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;px&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nf&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;px&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;fr&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;container&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;inline-size&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &amp;gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;50ch&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;grid-template&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;image title title&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;auto&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;image summary button&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;1fr&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;minmax&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;50px&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;%,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;500px&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;1fr&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;fit-content&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;%);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-err&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;The initial goal of this post is that components should be able to respond to any context. To make that work, each component defines its own internal behavior, without explicit knowledge of the surrounding components. Container queries help us accomplish that. The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;product-list&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;product-detail&lt;/code&gt; components don&#39;t need to be aware of why they have more or less space in a given context, they only need to know &lt;em&gt;how much space&lt;/em&gt; is currently available.&lt;/p&gt;

&lt;h2 id=&quot;transition_grid_templates_and_visibility&quot; data-text=&quot;Transition grid templates and visibility&quot; tabindex=&quot;-1&quot;&gt;Transition grid templates and visibility&lt;/h2&gt;

&lt;p&gt;With a layout that&#39;s changing often based on container queries, how do we smooth out all of the transitions? When using grids for layout, it&#39;s possible to animate the size of a column or row, as well as the gap between columns and rows. In this case, the cart area and gap are expanded from &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;0&lt;/code&gt; width when the cart is opened. In order to animate grid templates like this, two things are required:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The initial and end states must have the same number of tracks (columns or rows).&lt;/li&gt;
&lt;li&gt;The animated tracks must use comparable units.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;While it would be possible to change from a one-column grid to a two-column grid when the sidebar is hidden, the empty sidebar column is instead resized to &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;0&lt;/code&gt;, and the column-gap is also set to &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;0&lt;/code&gt;. When the cart is open in the sidebar, the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;0&lt;/code&gt;-width column transitions to &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;calc(15em + 1cqi)&lt;/code&gt;. Since the calculation results in a normal length value, the transition can be animated from length &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;0&lt;/code&gt;. The gap also animates from one length to another—from &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;0&lt;/code&gt; to &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;var(--gap)&lt;/code&gt;—which was defined earlier:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;transition&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;grid-template&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;250&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;ms&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;gap&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;250&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;ms&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;h3 id=&quot;non-baseline_animation_enhancements&quot; data-text=&quot;Non-Baseline animation enhancements&quot; tabindex=&quot;-1&quot;&gt;Non-Baseline animation enhancements&lt;/h3&gt;

&lt;p&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;shopping-cart&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;product-detail&lt;/code&gt; components are also animated when hidden or shown, and this is done using two recent features that are not yet Baseline, but work as progressive enhancements for browsers that do have support:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Applying &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;interpolate-size: allow-keywords&lt;/code&gt; allows &lt;a href=&quot;https://developer.chrome.com/docs/css-ui/animate-to-height-auto#animate_to_and_from_intrinsic_sizing_keywords_with_interpolate-size&quot;&gt;transitioning element dimensions from &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;0&lt;/code&gt; to &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;auto&lt;/code&gt;&lt;/a&gt;. This is used for transitioning the products to and from a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;block-size&lt;/code&gt; of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;0&lt;/code&gt; when they are added or removed from the cart. Since the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;interpolate-size&lt;/code&gt; property inherits, that only needs to be defined once on the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;html&amp;gt;&lt;/code&gt; element, and it is available to every other element on the page. This is only supported in Chrome-based browsers (Chrome, Edge, and others), but can be used as a progressive enhancement. The fallback works as expected, just without the animated transition.&lt;br&gt;

  
    
    
    
    
  
  
  
    
      
    
  
    
      
    
  
    
      
    
   

    
    
      
      &lt;div class=&quot;wd-browser-compat&quot; translate=&quot;no&quot;&gt;
        &lt;p&gt;Browser Support&lt;/p&gt;
        &lt;ul&gt;
          
  
  
    
      
        
          
          
        
      
    
    
  

  
  
  
  
    
  
  
  
    
  

  &lt;li data-support=&quot;yes&quot;&gt;
    &lt;img alt=&quot;Chrome: 129.&quot; src=&quot;data:image/svg+xml,%3Csvg xmlns=&#39;http://www.w3.org/2000/svg&#39; width=&#39;24&#39; height=&#39;24&#39; viewBox=&#39;-10 -10 276 276&#39;%3E%3ClinearGradient id=&#39;a&#39; x1=&#39;145&#39; x2=&#39;34&#39; y1=&#39;253&#39; y2=&#39;61&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%231e8e3e&#39;/%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%2334a853&#39;/%3E%3C/linearGradient%3E%3ClinearGradient id=&#39;b&#39; x1=&#39;111&#39; x2=&#39;222&#39; y1=&#39;254&#39; y2=&#39;62&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%23fcc934&#39;/%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%23fbbc04&#39;/%3E%3C/linearGradient%3E%3ClinearGradient id=&#39;c&#39; x1=&#39;17&#39; x2=&#39;239&#39; y1=&#39;80&#39; y2=&#39;80&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%23d93025&#39;/%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%23ea4335&#39;/%3E%3C/linearGradient%3E%3Ccircle cx=&#39;128&#39; cy=&#39;128&#39; r=&#39;64&#39; fill=&#39;%23fff&#39;/%3E%3Cpath fill=&#39;url(%23a)&#39; d=&#39;M96 183a64 64 0 0 1-23-23L17 64a128 128 0 0 0 111 192l55-96a64 64 0 0 1-87 23Z&#39;/%3E%3Cpath fill=&#39;url(%23b)&#39; d=&#39;M192 128a64 64 0 0 1-9 32l-55 96A128 128 0 0 0 239 64H128a64 64 0 0 1 64 64Z&#39;/%3E%3Ccircle cx=&#39;128&#39; cy=&#39;128&#39; r=&#39;52&#39; fill=&#39;%231a73e8&#39;/%3E%3Cpath fill=&#39;url(%23c)&#39; d=&#39;M96 73a64 64 0 0 1 32-9h111a128 128 0 0 0-222 0l56 96a64 64 0 0 1 23-87Z&#39;/%3E%3C/svg%3E&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
    &lt;span aria-hidden=&quot;true&quot;&gt;
      
        129
      
    &lt;/span&gt;
  &lt;/li&gt;
  
  
          
  
  
    
      
        
          
          
        
      
    
    
  

  
  
  
  
    
  
  
  
    
  

  &lt;li data-support=&quot;yes&quot;&gt;
    &lt;img alt=&quot;Edge: 129.&quot; src=&quot;data:image/svg+xml,%3Csvg xmlns=&#39;http://www.w3.org/2000/svg&#39; xmlns:xlink=&#39;http://www.w3.org/1999/xlink&#39; width=&#39;24&#39; height=&#39;24&#39; viewBox=&#39;0 0 27600 27600&#39;%3E%3ClinearGradient id=&#39;A&#39; gradientUnits=&#39;userSpaceOnUse&#39;/%3E%3ClinearGradient id=&#39;B&#39; x1=&#39;6870&#39; x2=&#39;24704&#39; y1=&#39;18705&#39; y2=&#39;18705&#39; xlink:href=&#39;%23A&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%230c59a4&#39;/%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%23114a8b&#39;/%3E%3C/linearGradient%3E%3ClinearGradient id=&#39;C&#39; x1=&#39;16272&#39; x2=&#39;5133&#39; y1=&#39;10968&#39; y2=&#39;23102&#39; xlink:href=&#39;%23A&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%231b9de2&#39;/%3E%3Cstop offset=&#39;.16&#39; stop-color=&#39;%231595df&#39;/%3E%3Cstop offset=&#39;.67&#39; stop-color=&#39;%230680d7&#39;/%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%230078d4&#39;/%3E%3C/linearGradient%3E%3CradialGradient id=&#39;D&#39; cx=&#39;16720&#39; cy=&#39;18747&#39; r=&#39;9538&#39; xlink:href=&#39;%23A&#39;%3E%3Cstop offset=&#39;.72&#39; stop-opacity=&#39;0&#39;/%3E%3Cstop offset=&#39;.95&#39; stop-opacity=&#39;.53&#39;/%3E%3Cstop offset=&#39;1&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;E&#39; cx=&#39;7130&#39; cy=&#39;19866&#39; r=&#39;14324&#39; gradientTransform=&#39;matrix(.14843 -.98892 .79688 .1196 -8759 25542)&#39; xlink:href=&#39;%23A&#39;%3E%3Cstop offset=&#39;.76&#39; stop-opacity=&#39;0&#39;/%3E%3Cstop offset=&#39;.95&#39; stop-opacity=&#39;.5&#39;/%3E%3Cstop offset=&#39;1&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;F&#39; cx=&#39;2523&#39; cy=&#39;4680&#39; r=&#39;20243&#39; gradientTransform=&#39;matrix(-.03715 .99931 -2.12836 -.07913 13579 3530)&#39; xlink:href=&#39;%23A&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%2335c1f1&#39;/%3E%3Cstop offset=&#39;.11&#39; stop-color=&#39;%2334c1ed&#39;/%3E%3Cstop offset=&#39;.23&#39; stop-color=&#39;%232fc2df&#39;/%3E%3Cstop offset=&#39;.31&#39; stop-color=&#39;%232bc3d2&#39;/%3E%3Cstop offset=&#39;.67&#39; stop-color=&#39;%2336c752&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;G&#39; cx=&#39;24247&#39; cy=&#39;7758&#39; r=&#39;9734&#39; gradientTransform=&#39;matrix(.28109 .95968 -.78353 .22949 24510 -16292)&#39; xlink:href=&#39;%23A&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%2366eb6e&#39;/%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%2366eb6e&#39; stop-opacity=&#39;0&#39;/%3E%3C/radialGradient%3E%3Cpath id=&#39;H&#39; d=&#39;M24105 20053a9345 9345 0 01-1053 472 10202 10202 0 01-3590 646c-4732 0-8855-3255-8855-7432 0-1175 680-2193 1643-2729-4280 180-5380 4640-5380 7253 0 7387 6810 8137 8276 8137 791 0 1984-230 2704-456l130-44a12834 12834 0 006660-5282c220-350-168-757-535-565z&#39;/%3E%3Cpath id=&#39;I&#39; d=&#39;M11571 25141a7913 7913 0 01-2273-2137 8145 8145 0 01-1514-4740 8093 8093 0 013093-6395 8082 8082 0 011373-859c312-148 846-414 1554-404a3236 3236 0 012569 1297 3184 3184 0 01636 1866c0-21 2446-7960-8005-7960-4390 0-8004 4166-8004 7820 0 2319 538 4170 1212 5604a12833 12833 0 007684 6757 12795 12795 0 003908 610c1414 0 2774-233 4045-656a7575 7575 0 01-6278-803z&#39;/%3E%3Cpath id=&#39;J&#39; d=&#39;M16231 15886c-80 105-330 250-330 566 0 260 170 512 472 723 1438 1003 4149 868 4156 868a5954 5954 0 003027-839 6147 6147 0 001133-850 6180 6180 0 001910-4437c26-2242-796-3732-1133-4392-2120-4141-6694-6525-11668-6525-7011 0-12703 5635-12798 12620 47-3654 3679-6605 7996-6605 350 0 2346 34 4200 1007 1634 858 2490 1894 3086 2921 618 1067 728 2415 728 2952s-271 1333-780 1990z&#39;/%3E%3Cuse fill=&#39;url(%23B)&#39; xlink:href=&#39;%23H&#39;/%3E%3Cuse fill=&#39;url(%23D)&#39; opacity=&#39;.35&#39; xlink:href=&#39;%23H&#39;/%3E%3Cuse fill=&#39;url(%23C)&#39; xlink:href=&#39;%23I&#39;/%3E%3Cuse fill=&#39;url(%23E)&#39; opacity=&#39;.4&#39; xlink:href=&#39;%23I&#39;/%3E%3Cuse fill=&#39;url(%23F)&#39; xlink:href=&#39;%23J&#39;/%3E%3Cuse fill=&#39;url(%23G)&#39; xlink:href=&#39;%23J&#39;/%3E%3C/svg%3E&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
    &lt;span aria-hidden=&quot;true&quot;&gt;
      
        129
      
    &lt;/span&gt;
  &lt;/li&gt;
  
  
          
  
  
    
      
      
    
    
  

  
  
  
  
    
  
  
  
    
  

  &lt;li data-support=&quot;no&quot;&gt;
    &lt;img alt=&quot;Firefox: not supported.&quot; src=&quot;data:image/svg+xml,%3Csvg xmlns=&#39;http://www.w3.org/2000/svg&#39; width=&#39;24&#39; height=&#39;24&#39; viewBox=&#39;0 0 512 512&#39;%3E%3Cdefs%3E%3CradialGradient id=&#39;ff-b&#39; cx=&#39;428.5&#39; cy=&#39;55.1&#39; r=&#39;501&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23ffbd4f&#39;/%3E%3Cstop offset=&#39;.2&#39; stop-color=&#39;%23ffac31&#39;/%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23ff9d17&#39;/%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23ff980e&#39;/%3E%3Cstop offset=&#39;.4&#39; stop-color=&#39;%23ff563b&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff3750&#39;/%3E%3Cstop offset=&#39;.7&#39; stop-color=&#39;%23f5156c&#39;/%3E%3Cstop offset=&#39;.8&#39; stop-color=&#39;%23eb0878&#39;/%3E%3Cstop offset=&#39;.9&#39; stop-color=&#39;%23e50080&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-c&#39; cx=&#39;245.4&#39; cy=&#39;259.9&#39; r=&#39;501&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23960e18&#39;/%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23b11927&#39; stop-opacity=&#39;.7&#39;/%3E%3Cstop offset=&#39;.4&#39; stop-color=&#39;%23db293d&#39; stop-opacity=&#39;.3&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23f5334b&#39; stop-opacity=&#39;.1&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff3750&#39; stop-opacity=&#39;0&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-d&#39; cx=&#39;305.8&#39; cy=&#39;-58.6&#39; r=&#39;363&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23fff44f&#39;/%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23ffdc3e&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff9d12&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff980e&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-e&#39; cx=&#39;190&#39; cy=&#39;390.8&#39; r=&#39;238.6&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%233a8ee6&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%235c79f0&#39;/%3E%3Cstop offset=&#39;.7&#39; stop-color=&#39;%239059ff&#39;/%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%23c139e6&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-f&#39; cx=&#39;252.2&#39; cy=&#39;201.3&#39; r=&#39;126.5&#39; gradientTransform=&#39;matrix(1 0 0 1 -48 31)&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.2&#39; stop-color=&#39;%239059ff&#39; stop-opacity=&#39;0&#39;/%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%238c4ff3&#39; stop-opacity=&#39;.1&#39;/%3E%3Cstop offset=&#39;.8&#39; stop-color=&#39;%237716a8&#39; stop-opacity=&#39;.5&#39;/%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%236e008b&#39; stop-opacity=&#39;.6&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-g&#39; cx=&#39;239.1&#39; cy=&#39;34.6&#39; r=&#39;171.6&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%23ffe226&#39;/%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23ffdb27&#39;/%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23ffc82a&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ffa930&#39;/%3E%3Cstop offset=&#39;.7&#39; stop-color=&#39;%23ff7e37&#39;/%3E%3Cstop offset=&#39;.8&#39; stop-color=&#39;%23ff7139&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-h&#39; cx=&#39;374&#39; cy=&#39;-74.3&#39; r=&#39;732.2&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23fff44f&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff980e&#39;/%3E%3Cstop offset=&#39;.6&#39; stop-color=&#39;%23ff5634&#39;/%3E%3Cstop offset=&#39;.7&#39; stop-color=&#39;%23ff3647&#39;/%3E%3Cstop offset=&#39;.9&#39; stop-color=&#39;%23e31587&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-i&#39; cx=&#39;304.6&#39; cy=&#39;7.1&#39; r=&#39;536.4&#39; gradientTransform=&#39;rotate(84 303 4)&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%23fff44f&#39;/%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23ffe847&#39;/%3E%3Cstop offset=&#39;.2&#39; stop-color=&#39;%23ffc830&#39;/%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23ff980e&#39;/%3E%3Cstop offset=&#39;.4&#39; stop-color=&#39;%23ff8b16&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff672a&#39;/%3E%3Cstop offset=&#39;.6&#39; stop-color=&#39;%23ff3647&#39;/%3E%3Cstop offset=&#39;.7&#39; stop-color=&#39;%23e31587&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-j&#39; cx=&#39;235&#39; cy=&#39;98.1&#39; r=&#39;457.1&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23fff44f&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff980e&#39;/%3E%3Cstop offset=&#39;.6&#39; stop-color=&#39;%23ff5634&#39;/%3E%3Cstop offset=&#39;.7&#39; stop-color=&#39;%23ff3647&#39;/%3E%3Cstop offset=&#39;.9&#39; stop-color=&#39;%23e31587&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-k&#39; cx=&#39;355.7&#39; cy=&#39;124.9&#39; r=&#39;500.3&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23fff44f&#39;/%3E%3Cstop offset=&#39;.2&#39; stop-color=&#39;%23ffe141&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ffaf1e&#39;/%3E%3Cstop offset=&#39;.6&#39; stop-color=&#39;%23ff980e&#39;/%3E%3C/radialGradient%3E%3ClinearGradient id=&#39;ff-a&#39; x1=&#39;446.9&#39; y1=&#39;76.8&#39; x2=&#39;47.9&#39; y2=&#39;461.8&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23fff44f&#39;/%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23ffe847&#39;/%3E%3Cstop offset=&#39;.2&#39; stop-color=&#39;%23ffc830&#39;/%3E%3Cstop offset=&#39;.4&#39; stop-color=&#39;%23ff980e&#39;/%3E%3Cstop offset=&#39;.4&#39; stop-color=&#39;%23ff8b16&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff672a&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff3647&#39;/%3E%3Cstop offset=&#39;.7&#39; stop-color=&#39;%23e31587&#39;/%3E%3C/linearGradient%3E%3ClinearGradient id=&#39;ff-l&#39; x1=&#39;442.1&#39; y1=&#39;74.8&#39; x2=&#39;102.6&#39; y2=&#39;414.3&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.2&#39; stop-color=&#39;%23fff44f&#39; stop-opacity=&#39;.8&#39;/%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23fff44f&#39; stop-opacity=&#39;.6&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23fff44f&#39; stop-opacity=&#39;.2&#39;/%3E%3Cstop offset=&#39;.6&#39; stop-color=&#39;%23fff44f&#39; stop-opacity=&#39;0&#39;/%3E%3C/linearGradient%3E%3C/defs%3E%3Cpath d=&#39;M479 166c-11-25-32-52-49-60a249 249 0 0 1 25 73c-27-68-73-95-111-155a255 255 0 0 1-8-14 44 44 0 0 1-4-9 1 1 0 0 0 0-1 1 1 0 0 0-1 0c-60 35-81 101-83 134a120 120 0 0 0-66 25 71 71 0 0 0-6-5 111 111 0 0 1-1-58c-25 11-44 29-58 44-9-12-9-52-8-60l-8 4a175 175 0 0 0-24 21 210 210 0 0 0-22 26 203 203 0 0 0-32 73l-1 2-2 15a229 229 0 0 0-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121zM202 355l3 1-3-1zm55-145zm198-31z&#39; fill=&#39;url(%23ff-a)&#39;/%3E%3Cpath d=&#39;M479 166c-11-25-32-52-49-60 14 26 22 53 25 72v1a207 207 0 0 1-206 279c-113-3-212-87-231-197-3-17 0-26 2-40-2 11-3 14-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121z&#39; fill=&#39;url(%23ff-b)&#39;/%3E%3Cpath d=&#39;M479 166c-11-25-32-52-49-60 14 26 22 53 25 72v1a207 207 0 0 1-206 279c-113-3-212-87-231-197-3-17 0-26 2-40-2 11-3 14-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121z&#39; fill=&#39;url(%23ff-c)&#39;/%3E%3Cpath d=&#39;m362 195 1 1a130 130 0 0 0-22-29C266 92 322 5 331 0c-60 35-81 101-83 134l9-1c45 0 84 25 105 62z&#39; fill=&#39;url(%23ff-d)&#39;/%3E%3Cpath d=&#39;M257 210c-1 6-22 26-29 26-68 0-80 41-80 41 3 35 28 64 57 79l4 2 7 3a107 107 0 0 0 31 6c120 6 143-143 57-186 22-4 45 5 58 14-21-37-60-62-105-62l-9 1a120 120 0 0 0-66 25l17 16c16 16 58 33 58 35z&#39; fill=&#39;url(%23ff-e)&#39;/%3E%3Cpath d=&#39;M257 210c-1 6-22 26-29 26-68 0-80 41-80 41 3 35 28 64 57 79l4 2 7 3a107 107 0 0 0 31 6c120 6 143-143 57-186 22-4 45 5 58 14-21-37-60-62-105-62l-9 1a120 120 0 0 0-66 25l17 16c16 16 58 33 58 35z&#39; fill=&#39;url(%23ff-f)&#39;/%3E%3Cpath d=&#39;m171 151 5 3a111 111 0 0 1-1-58c-25 11-44 29-58 44 1 0 36 0 54 11z&#39; fill=&#39;url(%23ff-g)&#39;/%3E%3Cpath d=&#39;M18 261a242 242 0 0 0 231 197 207 207 0 0 0 206-279c8 56-20 110-64 146-86 71-169 43-186 31l-3-1c-50-24-71-70-67-110-42 0-57-35-57-35s38-28 89-4c46 22 90 4 90 4 0-2-42-19-58-35l-17-16a71 71 0 0 0-6-5l-5-3c-18-11-52-11-54-11-9-12-9-51-8-60l-8 4a175 175 0 0 0-24 21 210 210 0 0 0-22 26 203 203 0 0 0-32 73c0 1-9 38-5 57z&#39; fill=&#39;url(%23ff-h)&#39;/%3E%3Cpath d=&#39;M341 167a130 130 0 0 1 22 29 46 46 0 0 1 4 3c55 50 26 121 24 126 44-36 72-90 64-146-27-68-73-95-111-155a255 255 0 0 1-8-14 44 44 0 0 1-4-9 1 1 0 0 0 0-1 1 1 0 0 0-1 0c-9 5-65 92 10 167z&#39; fill=&#39;url(%23ff-i)&#39;/%3E%3Cpath d=&#39;M367 199a46 46 0 0 0-4-3l-1-1c-13-9-36-18-58-15 86 44 63 193-57 187a107 107 0 0 1-31-6 131 131 0 0 1-11-5c17 12 99 39 186-31 2-5 31-76-24-126z&#39; fill=&#39;url(%23ff-j)&#39;/%3E%3Cpath d=&#39;M148 277s12-41 80-41c7 0 28-20 29-26s-44 18-90-4c-51-24-89 4-89 4s15 35 57 35c-4 40 16 85 67 110l3 1c-29-15-54-44-57-79z&#39; fill=&#39;url(%23ff-k)&#39;/%3E%3Cpath d=&#39;M479 166c-11-25-32-52-49-60a249 249 0 0 1 25 73c-27-68-73-95-111-155a255 255 0 0 1-8-14 44 44 0 0 1-4-9 1 1 0 0 0 0-1 1 1 0 0 0-1 0c-60 35-81 101-83 134l9-1c45 0 84 25 105 62-13-9-36-18-58-14 86 43 63 192-57 186a107 107 0 0 1-31-6 131 131 0 0 1-11-5l-3-1 3 1c-29-15-54-44-57-79 0 0 12-41 80-41 7 0 28-20 29-26 0-2-42-19-58-35l-17-16a71 71 0 0 0-6-5 111 111 0 0 1-1-58c-25 11-44 29-58 44-9-12-9-52-8-60l-8 4a175 175 0 0 0-24 21 210 210 0 0 0-22 26 203 203 0 0 0-32 73l-1 2-2 15a279 279 0 0 0-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121zm-24 13z&#39; fill=&#39;url(%23ff-l)&#39;/%3E%3C/svg%3E&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
    &lt;span aria-hidden=&quot;true&quot;&gt;
      
        x
      
    &lt;/span&gt;
  &lt;/li&gt;
  
  
          
  
  
    
      
      
    
    
  

  
  
  
  
    
  
  
  
    
  

  &lt;li data-support=&quot;no&quot;&gt;
    &lt;img alt=&quot;Safari: not supported.&quot; src=&quot;data:image/svg+xml,%3Csvg xmlns=&#39;http://www.w3.org/2000/svg&#39; xmlns:xlink=&#39;http://www.w3.org/1999/xlink&#39; width=&#39;24&#39; height=&#39;24&#39; viewBox=&#39;195 190 135 135&#39;%3E%3Cdefs%3E%3ClinearGradient id=&#39;s-a&#39; x1=&#39;132.6&#39; x2=&#39;134.4&#39; y1=&#39;111.7&#39; y2=&#39;-105.3&#39; xlink:href=&#39;%23s-b&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%23d2d2d2&#39; /%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23f2f2f2&#39; /%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%23fff&#39; /%3E%3C/linearGradient%3E%3ClinearGradient id=&#39;s-b&#39; gradientUnits=&#39;userSpaceOnUse&#39; /%3E%3ClinearGradient id=&#39;s-c&#39; x1=&#39;65.4&#39; x2=&#39;67.4&#39; y1=&#39;115.7&#39; y2=&#39;17.1&#39; xlink:href=&#39;%23s-b&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%23005ad5&#39; /%3E%3Cstop offset=&#39;.2&#39; stop-color=&#39;%230875f0&#39; /%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23218cee&#39; /%3E%3Cstop offset=&#39;.6&#39; stop-color=&#39;%2327a5f3&#39; /%3E%3Cstop offset=&#39;.8&#39; stop-color=&#39;%2325aaf2&#39; /%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%2321aaef&#39; /%3E%3C/linearGradient%3E%3ClinearGradient id=&#39;s-d&#39; x1=&#39;158.7&#39; x2=&#39;176.3&#39; y1=&#39;96.7&#39; y2=&#39;79.5&#39; xlink:href=&#39;%23s-b&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%23c72e24&#39; /%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%23fd3b2f&#39; /%3E%3C/linearGradient%3E%3CradialGradient id=&#39;s-i&#39; cx=&#39;-69.9&#39; cy=&#39;69.3&#39; r=&#39;54&#39; gradientTransform=&#39;matrix(.9 -.01 .04 2.72 -9 -120)&#39; xlink:href=&#39;%23s-b&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%2324a5f3&#39; stop-opacity=&#39;0&#39; /%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%231e8ceb&#39; /%3E%3C/radialGradient%3E%3CradialGradient id=&#39;s-j&#39; cx=&#39;109.3&#39; cy=&#39;13.8&#39; r=&#39;93.1&#39; gradientTransform=&#39;matrix(-.02 1.1 -1.04 -.02 137 -115)&#39; xlink:href=&#39;%23s-b&#39;%3E%3Cstop offset=&#39;0&#39; stop-opacity=&#39;0&#39; /%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%235488d6&#39; stop-opacity=&#39;0&#39; /%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%235d96eb&#39; /%3E%3C/radialGradient%3E%3C/defs%3E%3Crect width=&#39;220&#39; height=&#39;220&#39; x=&#39;22&#39; y=&#39;-107&#39; fill=&#39;url(%23s-a)&#39; ry=&#39;49&#39; transform=&#39;matrix(.57 0 0 .57 187 256)&#39; /%3E%3Cg transform=&#39;translate(194 190)&#39;%3E%3Ccircle cx=&#39;67.8&#39; cy=&#39;67.7&#39; fill=&#39;url(%23s-c)&#39; paint-order=&#39;stroke fill markers&#39; r=&#39;54&#39; /%3E%3Ccircle cx=&#39;-69.9&#39; cy=&#39;69.3&#39; fill=&#39;url(%23s-i)&#39; transform=&#39;translate(138 -2)&#39; r=&#39;54&#39; /%3E%3C/g%3E%3Cellipse cx=&#39;120&#39; cy=&#39;14.2&#39; fill=&#39;url(%23s-j)&#39; rx=&#39;93.1&#39; ry=&#39;93.7&#39; transform=&#39;matrix(.58 0 0 .58 192 250)&#39; /%3E%3Cg transform=&#39;matrix(.58 0 0 .57 197 182)&#39;%3E%3Cpath fill=&#39;%23cac7c8&#39; d=&#39;M46 192h1l72-48-7-9-66 57Z&#39; /%3E%3Cpath fill=&#39;%23fbfffc&#39; d=&#39;M46 191v1l66-57-7-9-59 65Z&#39; /%3E%3Cpath fill=&#39;url(%23s-d)&#39; d=&#39;m119 144-7-9 66-57-59 66Z&#39; /%3E%3Cpath fill=&#39;%23fb645c&#39; d=&#39;m105 126 7 9 66-57-1-1-72 49Z&#39; /%3E%3C/g%3E%3Cpath stroke=&#39;%23fff&#39; stroke-linecap=&#39;round&#39; stroke-miterlimit=&#39;1&#39; stroke-width=&#39;1.3&#39; d=&#39;m287 278 3-2m-12-17 8-2m-8-3h4m-4-13 8 2m-8 3h4m-1-13 7 3m-4-11 7 4m-2-11 6 6m0-12 6 7m1-11 4 6m4-10 3 7m5-9 2 7m15-7-1 7m10-5-3 7m11-4-4 7m11-2-5 6m16 7-7 4m10 4-7 3m10 6-8 1m8 16-8-2m5 10-7-3m4 11-7-4m2 11-6-5m0 11-5-6m-2 11-4-7m-4 11-3-8m-6 10-1-8m-16 8 2-8m-10 5 3-7m-11 4 4-7m-11 2 5-6m-8 3 3-3m4 8 2-3m5 8 2-4m6 7 1-4m8 5v-4m8 4v-4m9 3-1-4m9 1-2-4m9 0-2-4m9-2-3-3m8-4-3-2m8-5-4-2m7-6-4-1m5-8h-4m4-8h-4m3-9-4 1m1-9-4 2m-1-9-3 2m-2-9-3 3m-4-8-2 3m-5-8-2 4m-6-6-1 3m-8-5v4m-8-4v4m-9-2 1 3m-9 0 2 3m-9 1 2 3m-9 2 3 3m-8 4 3 2m-8 5 4 2m-7 6 4 1m-4 25 4-1m-2 5 7-3m-6 7 4-2m-2 6 7-4m-13-21h8m41-41v-8m0 99v-8m49-42h-8&#39; transform=&#39;translate(-65 8)&#39; /%3E%3C/svg%3E&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
    &lt;span aria-hidden=&quot;true&quot;&gt;
      
        x
      
    &lt;/span&gt;
  &lt;/li&gt;
  
  
        &lt;/ul&gt;
        
        &lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/Reference/Properties/interpolate-size&quot;&gt;Source&lt;/a&gt;&lt;/p&gt;
        
      &lt;/div&gt;
    
    
    
  &lt;/li&gt;
&lt;li&gt;&quot;Discrete&quot; properties like &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;display&lt;/code&gt; can now be transitioned as well, even though there are no intermediate values between &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;grid&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;none&lt;/code&gt;. Instead the transition is applied at the start or end of the duration provided. To achieve that, &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;allow-discrete&lt;/code&gt; is added to the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;transition-behavior&lt;/code&gt; property. While &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;transition-behavior&lt;/code&gt; is otherwise Baseline, animating the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;display&lt;/code&gt; property is not yet supported in Firefox. But again, this works well as a progressive enhancement.&lt;br&gt;

  
    
    
    
    
  
  
  
    
      
    
  
    
      
    
  
    
      
    
  
    
      
    
   

    
    
      
      &lt;div class=&quot;wd-browser-compat&quot; translate=&quot;no&quot;&gt;
        &lt;p&gt;Browser Support&lt;/p&gt;
        &lt;ul&gt;
          
  
  
    
      
        
          
          
        
      
    
    
  

  
  
  
  
    
  
  
  
    
  

  &lt;li data-support=&quot;yes&quot;&gt;
    &lt;img alt=&quot;Chrome: 117.&quot; src=&quot;data:image/svg+xml,%3Csvg xmlns=&#39;http://www.w3.org/2000/svg&#39; width=&#39;24&#39; height=&#39;24&#39; viewBox=&#39;-10 -10 276 276&#39;%3E%3ClinearGradient id=&#39;a&#39; x1=&#39;145&#39; x2=&#39;34&#39; y1=&#39;253&#39; y2=&#39;61&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%231e8e3e&#39;/%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%2334a853&#39;/%3E%3C/linearGradient%3E%3ClinearGradient id=&#39;b&#39; x1=&#39;111&#39; x2=&#39;222&#39; y1=&#39;254&#39; y2=&#39;62&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%23fcc934&#39;/%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%23fbbc04&#39;/%3E%3C/linearGradient%3E%3ClinearGradient id=&#39;c&#39; x1=&#39;17&#39; x2=&#39;239&#39; y1=&#39;80&#39; y2=&#39;80&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%23d93025&#39;/%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%23ea4335&#39;/%3E%3C/linearGradient%3E%3Ccircle cx=&#39;128&#39; cy=&#39;128&#39; r=&#39;64&#39; fill=&#39;%23fff&#39;/%3E%3Cpath fill=&#39;url(%23a)&#39; d=&#39;M96 183a64 64 0 0 1-23-23L17 64a128 128 0 0 0 111 192l55-96a64 64 0 0 1-87 23Z&#39;/%3E%3Cpath fill=&#39;url(%23b)&#39; d=&#39;M192 128a64 64 0 0 1-9 32l-55 96A128 128 0 0 0 239 64H128a64 64 0 0 1 64 64Z&#39;/%3E%3Ccircle cx=&#39;128&#39; cy=&#39;128&#39; r=&#39;52&#39; fill=&#39;%231a73e8&#39;/%3E%3Cpath fill=&#39;url(%23c)&#39; d=&#39;M96 73a64 64 0 0 1 32-9h111a128 128 0 0 0-222 0l56 96a64 64 0 0 1 23-87Z&#39;/%3E%3C/svg%3E&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
    &lt;span aria-hidden=&quot;true&quot;&gt;
      
        117
      
    &lt;/span&gt;
  &lt;/li&gt;
  
  
          
  
  
    
      
        
          
          
        
      
    
    
  

  
  
  
  
    
  
  
  
    
  

  &lt;li data-support=&quot;yes&quot;&gt;
    &lt;img alt=&quot;Edge: 117.&quot; src=&quot;data:image/svg+xml,%3Csvg xmlns=&#39;http://www.w3.org/2000/svg&#39; xmlns:xlink=&#39;http://www.w3.org/1999/xlink&#39; width=&#39;24&#39; height=&#39;24&#39; viewBox=&#39;0 0 27600 27600&#39;%3E%3ClinearGradient id=&#39;A&#39; gradientUnits=&#39;userSpaceOnUse&#39;/%3E%3ClinearGradient id=&#39;B&#39; x1=&#39;6870&#39; x2=&#39;24704&#39; y1=&#39;18705&#39; y2=&#39;18705&#39; xlink:href=&#39;%23A&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%230c59a4&#39;/%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%23114a8b&#39;/%3E%3C/linearGradient%3E%3ClinearGradient id=&#39;C&#39; x1=&#39;16272&#39; x2=&#39;5133&#39; y1=&#39;10968&#39; y2=&#39;23102&#39; xlink:href=&#39;%23A&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%231b9de2&#39;/%3E%3Cstop offset=&#39;.16&#39; stop-color=&#39;%231595df&#39;/%3E%3Cstop offset=&#39;.67&#39; stop-color=&#39;%230680d7&#39;/%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%230078d4&#39;/%3E%3C/linearGradient%3E%3CradialGradient id=&#39;D&#39; cx=&#39;16720&#39; cy=&#39;18747&#39; r=&#39;9538&#39; xlink:href=&#39;%23A&#39;%3E%3Cstop offset=&#39;.72&#39; stop-opacity=&#39;0&#39;/%3E%3Cstop offset=&#39;.95&#39; stop-opacity=&#39;.53&#39;/%3E%3Cstop offset=&#39;1&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;E&#39; cx=&#39;7130&#39; cy=&#39;19866&#39; r=&#39;14324&#39; gradientTransform=&#39;matrix(.14843 -.98892 .79688 .1196 -8759 25542)&#39; xlink:href=&#39;%23A&#39;%3E%3Cstop offset=&#39;.76&#39; stop-opacity=&#39;0&#39;/%3E%3Cstop offset=&#39;.95&#39; stop-opacity=&#39;.5&#39;/%3E%3Cstop offset=&#39;1&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;F&#39; cx=&#39;2523&#39; cy=&#39;4680&#39; r=&#39;20243&#39; gradientTransform=&#39;matrix(-.03715 .99931 -2.12836 -.07913 13579 3530)&#39; xlink:href=&#39;%23A&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%2335c1f1&#39;/%3E%3Cstop offset=&#39;.11&#39; stop-color=&#39;%2334c1ed&#39;/%3E%3Cstop offset=&#39;.23&#39; stop-color=&#39;%232fc2df&#39;/%3E%3Cstop offset=&#39;.31&#39; stop-color=&#39;%232bc3d2&#39;/%3E%3Cstop offset=&#39;.67&#39; stop-color=&#39;%2336c752&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;G&#39; cx=&#39;24247&#39; cy=&#39;7758&#39; r=&#39;9734&#39; gradientTransform=&#39;matrix(.28109 .95968 -.78353 .22949 24510 -16292)&#39; xlink:href=&#39;%23A&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%2366eb6e&#39;/%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%2366eb6e&#39; stop-opacity=&#39;0&#39;/%3E%3C/radialGradient%3E%3Cpath id=&#39;H&#39; d=&#39;M24105 20053a9345 9345 0 01-1053 472 10202 10202 0 01-3590 646c-4732 0-8855-3255-8855-7432 0-1175 680-2193 1643-2729-4280 180-5380 4640-5380 7253 0 7387 6810 8137 8276 8137 791 0 1984-230 2704-456l130-44a12834 12834 0 006660-5282c220-350-168-757-535-565z&#39;/%3E%3Cpath id=&#39;I&#39; d=&#39;M11571 25141a7913 7913 0 01-2273-2137 8145 8145 0 01-1514-4740 8093 8093 0 013093-6395 8082 8082 0 011373-859c312-148 846-414 1554-404a3236 3236 0 012569 1297 3184 3184 0 01636 1866c0-21 2446-7960-8005-7960-4390 0-8004 4166-8004 7820 0 2319 538 4170 1212 5604a12833 12833 0 007684 6757 12795 12795 0 003908 610c1414 0 2774-233 4045-656a7575 7575 0 01-6278-803z&#39;/%3E%3Cpath id=&#39;J&#39; d=&#39;M16231 15886c-80 105-330 250-330 566 0 260 170 512 472 723 1438 1003 4149 868 4156 868a5954 5954 0 003027-839 6147 6147 0 001133-850 6180 6180 0 001910-4437c26-2242-796-3732-1133-4392-2120-4141-6694-6525-11668-6525-7011 0-12703 5635-12798 12620 47-3654 3679-6605 7996-6605 350 0 2346 34 4200 1007 1634 858 2490 1894 3086 2921 618 1067 728 2415 728 2952s-271 1333-780 1990z&#39;/%3E%3Cuse fill=&#39;url(%23B)&#39; xlink:href=&#39;%23H&#39;/%3E%3Cuse fill=&#39;url(%23D)&#39; opacity=&#39;.35&#39; xlink:href=&#39;%23H&#39;/%3E%3Cuse fill=&#39;url(%23C)&#39; xlink:href=&#39;%23I&#39;/%3E%3Cuse fill=&#39;url(%23E)&#39; opacity=&#39;.4&#39; xlink:href=&#39;%23I&#39;/%3E%3Cuse fill=&#39;url(%23F)&#39; xlink:href=&#39;%23J&#39;/%3E%3Cuse fill=&#39;url(%23G)&#39; xlink:href=&#39;%23J&#39;/%3E%3C/svg%3E&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
    &lt;span aria-hidden=&quot;true&quot;&gt;
      
        117
      
    &lt;/span&gt;
  &lt;/li&gt;
  
  
          
  
  
    
      
      
    
    
  

  
  
  
  
    
  
  
  
    
  

  &lt;li data-support=&quot;no&quot;&gt;
    &lt;img alt=&quot;Firefox: not supported.&quot; src=&quot;data:image/svg+xml,%3Csvg xmlns=&#39;http://www.w3.org/2000/svg&#39; width=&#39;24&#39; height=&#39;24&#39; viewBox=&#39;0 0 512 512&#39;%3E%3Cdefs%3E%3CradialGradient id=&#39;ff-b&#39; cx=&#39;428.5&#39; cy=&#39;55.1&#39; r=&#39;501&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23ffbd4f&#39;/%3E%3Cstop offset=&#39;.2&#39; stop-color=&#39;%23ffac31&#39;/%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23ff9d17&#39;/%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23ff980e&#39;/%3E%3Cstop offset=&#39;.4&#39; stop-color=&#39;%23ff563b&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff3750&#39;/%3E%3Cstop offset=&#39;.7&#39; stop-color=&#39;%23f5156c&#39;/%3E%3Cstop offset=&#39;.8&#39; stop-color=&#39;%23eb0878&#39;/%3E%3Cstop offset=&#39;.9&#39; stop-color=&#39;%23e50080&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-c&#39; cx=&#39;245.4&#39; cy=&#39;259.9&#39; r=&#39;501&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23960e18&#39;/%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23b11927&#39; stop-opacity=&#39;.7&#39;/%3E%3Cstop offset=&#39;.4&#39; stop-color=&#39;%23db293d&#39; stop-opacity=&#39;.3&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23f5334b&#39; stop-opacity=&#39;.1&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff3750&#39; stop-opacity=&#39;0&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-d&#39; cx=&#39;305.8&#39; cy=&#39;-58.6&#39; r=&#39;363&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23fff44f&#39;/%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23ffdc3e&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff9d12&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff980e&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-e&#39; cx=&#39;190&#39; cy=&#39;390.8&#39; r=&#39;238.6&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%233a8ee6&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%235c79f0&#39;/%3E%3Cstop offset=&#39;.7&#39; stop-color=&#39;%239059ff&#39;/%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%23c139e6&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-f&#39; cx=&#39;252.2&#39; cy=&#39;201.3&#39; r=&#39;126.5&#39; gradientTransform=&#39;matrix(1 0 0 1 -48 31)&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.2&#39; stop-color=&#39;%239059ff&#39; stop-opacity=&#39;0&#39;/%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%238c4ff3&#39; stop-opacity=&#39;.1&#39;/%3E%3Cstop offset=&#39;.8&#39; stop-color=&#39;%237716a8&#39; stop-opacity=&#39;.5&#39;/%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%236e008b&#39; stop-opacity=&#39;.6&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-g&#39; cx=&#39;239.1&#39; cy=&#39;34.6&#39; r=&#39;171.6&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%23ffe226&#39;/%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23ffdb27&#39;/%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23ffc82a&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ffa930&#39;/%3E%3Cstop offset=&#39;.7&#39; stop-color=&#39;%23ff7e37&#39;/%3E%3Cstop offset=&#39;.8&#39; stop-color=&#39;%23ff7139&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-h&#39; cx=&#39;374&#39; cy=&#39;-74.3&#39; r=&#39;732.2&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23fff44f&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff980e&#39;/%3E%3Cstop offset=&#39;.6&#39; stop-color=&#39;%23ff5634&#39;/%3E%3Cstop offset=&#39;.7&#39; stop-color=&#39;%23ff3647&#39;/%3E%3Cstop offset=&#39;.9&#39; stop-color=&#39;%23e31587&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-i&#39; cx=&#39;304.6&#39; cy=&#39;7.1&#39; r=&#39;536.4&#39; gradientTransform=&#39;rotate(84 303 4)&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%23fff44f&#39;/%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23ffe847&#39;/%3E%3Cstop offset=&#39;.2&#39; stop-color=&#39;%23ffc830&#39;/%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23ff980e&#39;/%3E%3Cstop offset=&#39;.4&#39; stop-color=&#39;%23ff8b16&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff672a&#39;/%3E%3Cstop offset=&#39;.6&#39; stop-color=&#39;%23ff3647&#39;/%3E%3Cstop offset=&#39;.7&#39; stop-color=&#39;%23e31587&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-j&#39; cx=&#39;235&#39; cy=&#39;98.1&#39; r=&#39;457.1&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23fff44f&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff980e&#39;/%3E%3Cstop offset=&#39;.6&#39; stop-color=&#39;%23ff5634&#39;/%3E%3Cstop offset=&#39;.7&#39; stop-color=&#39;%23ff3647&#39;/%3E%3Cstop offset=&#39;.9&#39; stop-color=&#39;%23e31587&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-k&#39; cx=&#39;355.7&#39; cy=&#39;124.9&#39; r=&#39;500.3&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23fff44f&#39;/%3E%3Cstop offset=&#39;.2&#39; stop-color=&#39;%23ffe141&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ffaf1e&#39;/%3E%3Cstop offset=&#39;.6&#39; stop-color=&#39;%23ff980e&#39;/%3E%3C/radialGradient%3E%3ClinearGradient id=&#39;ff-a&#39; x1=&#39;446.9&#39; y1=&#39;76.8&#39; x2=&#39;47.9&#39; y2=&#39;461.8&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23fff44f&#39;/%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23ffe847&#39;/%3E%3Cstop offset=&#39;.2&#39; stop-color=&#39;%23ffc830&#39;/%3E%3Cstop offset=&#39;.4&#39; stop-color=&#39;%23ff980e&#39;/%3E%3Cstop offset=&#39;.4&#39; stop-color=&#39;%23ff8b16&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff672a&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff3647&#39;/%3E%3Cstop offset=&#39;.7&#39; stop-color=&#39;%23e31587&#39;/%3E%3C/linearGradient%3E%3ClinearGradient id=&#39;ff-l&#39; x1=&#39;442.1&#39; y1=&#39;74.8&#39; x2=&#39;102.6&#39; y2=&#39;414.3&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.2&#39; stop-color=&#39;%23fff44f&#39; stop-opacity=&#39;.8&#39;/%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23fff44f&#39; stop-opacity=&#39;.6&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23fff44f&#39; stop-opacity=&#39;.2&#39;/%3E%3Cstop offset=&#39;.6&#39; stop-color=&#39;%23fff44f&#39; stop-opacity=&#39;0&#39;/%3E%3C/linearGradient%3E%3C/defs%3E%3Cpath d=&#39;M479 166c-11-25-32-52-49-60a249 249 0 0 1 25 73c-27-68-73-95-111-155a255 255 0 0 1-8-14 44 44 0 0 1-4-9 1 1 0 0 0 0-1 1 1 0 0 0-1 0c-60 35-81 101-83 134a120 120 0 0 0-66 25 71 71 0 0 0-6-5 111 111 0 0 1-1-58c-25 11-44 29-58 44-9-12-9-52-8-60l-8 4a175 175 0 0 0-24 21 210 210 0 0 0-22 26 203 203 0 0 0-32 73l-1 2-2 15a229 229 0 0 0-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121zM202 355l3 1-3-1zm55-145zm198-31z&#39; fill=&#39;url(%23ff-a)&#39;/%3E%3Cpath d=&#39;M479 166c-11-25-32-52-49-60 14 26 22 53 25 72v1a207 207 0 0 1-206 279c-113-3-212-87-231-197-3-17 0-26 2-40-2 11-3 14-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121z&#39; fill=&#39;url(%23ff-b)&#39;/%3E%3Cpath d=&#39;M479 166c-11-25-32-52-49-60 14 26 22 53 25 72v1a207 207 0 0 1-206 279c-113-3-212-87-231-197-3-17 0-26 2-40-2 11-3 14-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121z&#39; fill=&#39;url(%23ff-c)&#39;/%3E%3Cpath d=&#39;m362 195 1 1a130 130 0 0 0-22-29C266 92 322 5 331 0c-60 35-81 101-83 134l9-1c45 0 84 25 105 62z&#39; fill=&#39;url(%23ff-d)&#39;/%3E%3Cpath d=&#39;M257 210c-1 6-22 26-29 26-68 0-80 41-80 41 3 35 28 64 57 79l4 2 7 3a107 107 0 0 0 31 6c120 6 143-143 57-186 22-4 45 5 58 14-21-37-60-62-105-62l-9 1a120 120 0 0 0-66 25l17 16c16 16 58 33 58 35z&#39; fill=&#39;url(%23ff-e)&#39;/%3E%3Cpath d=&#39;M257 210c-1 6-22 26-29 26-68 0-80 41-80 41 3 35 28 64 57 79l4 2 7 3a107 107 0 0 0 31 6c120 6 143-143 57-186 22-4 45 5 58 14-21-37-60-62-105-62l-9 1a120 120 0 0 0-66 25l17 16c16 16 58 33 58 35z&#39; fill=&#39;url(%23ff-f)&#39;/%3E%3Cpath d=&#39;m171 151 5 3a111 111 0 0 1-1-58c-25 11-44 29-58 44 1 0 36 0 54 11z&#39; fill=&#39;url(%23ff-g)&#39;/%3E%3Cpath d=&#39;M18 261a242 242 0 0 0 231 197 207 207 0 0 0 206-279c8 56-20 110-64 146-86 71-169 43-186 31l-3-1c-50-24-71-70-67-110-42 0-57-35-57-35s38-28 89-4c46 22 90 4 90 4 0-2-42-19-58-35l-17-16a71 71 0 0 0-6-5l-5-3c-18-11-52-11-54-11-9-12-9-51-8-60l-8 4a175 175 0 0 0-24 21 210 210 0 0 0-22 26 203 203 0 0 0-32 73c0 1-9 38-5 57z&#39; fill=&#39;url(%23ff-h)&#39;/%3E%3Cpath d=&#39;M341 167a130 130 0 0 1 22 29 46 46 0 0 1 4 3c55 50 26 121 24 126 44-36 72-90 64-146-27-68-73-95-111-155a255 255 0 0 1-8-14 44 44 0 0 1-4-9 1 1 0 0 0 0-1 1 1 0 0 0-1 0c-9 5-65 92 10 167z&#39; fill=&#39;url(%23ff-i)&#39;/%3E%3Cpath d=&#39;M367 199a46 46 0 0 0-4-3l-1-1c-13-9-36-18-58-15 86 44 63 193-57 187a107 107 0 0 1-31-6 131 131 0 0 1-11-5c17 12 99 39 186-31 2-5 31-76-24-126z&#39; fill=&#39;url(%23ff-j)&#39;/%3E%3Cpath d=&#39;M148 277s12-41 80-41c7 0 28-20 29-26s-44 18-90-4c-51-24-89 4-89 4s15 35 57 35c-4 40 16 85 67 110l3 1c-29-15-54-44-57-79z&#39; fill=&#39;url(%23ff-k)&#39;/%3E%3Cpath d=&#39;M479 166c-11-25-32-52-49-60a249 249 0 0 1 25 73c-27-68-73-95-111-155a255 255 0 0 1-8-14 44 44 0 0 1-4-9 1 1 0 0 0 0-1 1 1 0 0 0-1 0c-60 35-81 101-83 134l9-1c45 0 84 25 105 62-13-9-36-18-58-14 86 43 63 192-57 186a107 107 0 0 1-31-6 131 131 0 0 1-11-5l-3-1 3 1c-29-15-54-44-57-79 0 0 12-41 80-41 7 0 28-20 29-26 0-2-42-19-58-35l-17-16a71 71 0 0 0-6-5 111 111 0 0 1-1-58c-25 11-44 29-58 44-9-12-9-52-8-60l-8 4a175 175 0 0 0-24 21 210 210 0 0 0-22 26 203 203 0 0 0-32 73l-1 2-2 15a279 279 0 0 0-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121zm-24 13z&#39; fill=&#39;url(%23ff-l)&#39;/%3E%3C/svg%3E&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
    &lt;span aria-hidden=&quot;true&quot;&gt;
      
        x
      
    &lt;/span&gt;
  &lt;/li&gt;
  
  
          
  
  
    
      
        
          
          
        
      
    
    
  

  
  
  
  
    
  
  
  
    
  

  &lt;li data-support=&quot;yes&quot;&gt;
    &lt;img alt=&quot;Safari: 18.&quot; src=&quot;data:image/svg+xml,%3Csvg xmlns=&#39;http://www.w3.org/2000/svg&#39; xmlns:xlink=&#39;http://www.w3.org/1999/xlink&#39; width=&#39;24&#39; height=&#39;24&#39; viewBox=&#39;195 190 135 135&#39;%3E%3Cdefs%3E%3ClinearGradient id=&#39;s-a&#39; x1=&#39;132.6&#39; x2=&#39;134.4&#39; y1=&#39;111.7&#39; y2=&#39;-105.3&#39; xlink:href=&#39;%23s-b&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%23d2d2d2&#39; /%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23f2f2f2&#39; /%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%23fff&#39; /%3E%3C/linearGradient%3E%3ClinearGradient id=&#39;s-b&#39; gradientUnits=&#39;userSpaceOnUse&#39; /%3E%3ClinearGradient id=&#39;s-c&#39; x1=&#39;65.4&#39; x2=&#39;67.4&#39; y1=&#39;115.7&#39; y2=&#39;17.1&#39; xlink:href=&#39;%23s-b&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%23005ad5&#39; /%3E%3Cstop offset=&#39;.2&#39; stop-color=&#39;%230875f0&#39; /%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23218cee&#39; /%3E%3Cstop offset=&#39;.6&#39; stop-color=&#39;%2327a5f3&#39; /%3E%3Cstop offset=&#39;.8&#39; stop-color=&#39;%2325aaf2&#39; /%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%2321aaef&#39; /%3E%3C/linearGradient%3E%3ClinearGradient id=&#39;s-d&#39; x1=&#39;158.7&#39; x2=&#39;176.3&#39; y1=&#39;96.7&#39; y2=&#39;79.5&#39; xlink:href=&#39;%23s-b&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%23c72e24&#39; /%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%23fd3b2f&#39; /%3E%3C/linearGradient%3E%3CradialGradient id=&#39;s-i&#39; cx=&#39;-69.9&#39; cy=&#39;69.3&#39; r=&#39;54&#39; gradientTransform=&#39;matrix(.9 -.01 .04 2.72 -9 -120)&#39; xlink:href=&#39;%23s-b&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%2324a5f3&#39; stop-opacity=&#39;0&#39; /%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%231e8ceb&#39; /%3E%3C/radialGradient%3E%3CradialGradient id=&#39;s-j&#39; cx=&#39;109.3&#39; cy=&#39;13.8&#39; r=&#39;93.1&#39; gradientTransform=&#39;matrix(-.02 1.1 -1.04 -.02 137 -115)&#39; xlink:href=&#39;%23s-b&#39;%3E%3Cstop offset=&#39;0&#39; stop-opacity=&#39;0&#39; /%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%235488d6&#39; stop-opacity=&#39;0&#39; /%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%235d96eb&#39; /%3E%3C/radialGradient%3E%3C/defs%3E%3Crect width=&#39;220&#39; height=&#39;220&#39; x=&#39;22&#39; y=&#39;-107&#39; fill=&#39;url(%23s-a)&#39; ry=&#39;49&#39; transform=&#39;matrix(.57 0 0 .57 187 256)&#39; /%3E%3Cg transform=&#39;translate(194 190)&#39;%3E%3Ccircle cx=&#39;67.8&#39; cy=&#39;67.7&#39; fill=&#39;url(%23s-c)&#39; paint-order=&#39;stroke fill markers&#39; r=&#39;54&#39; /%3E%3Ccircle cx=&#39;-69.9&#39; cy=&#39;69.3&#39; fill=&#39;url(%23s-i)&#39; transform=&#39;translate(138 -2)&#39; r=&#39;54&#39; /%3E%3C/g%3E%3Cellipse cx=&#39;120&#39; cy=&#39;14.2&#39; fill=&#39;url(%23s-j)&#39; rx=&#39;93.1&#39; ry=&#39;93.7&#39; transform=&#39;matrix(.58 0 0 .58 192 250)&#39; /%3E%3Cg transform=&#39;matrix(.58 0 0 .57 197 182)&#39;%3E%3Cpath fill=&#39;%23cac7c8&#39; d=&#39;M46 192h1l72-48-7-9-66 57Z&#39; /%3E%3Cpath fill=&#39;%23fbfffc&#39; d=&#39;M46 191v1l66-57-7-9-59 65Z&#39; /%3E%3Cpath fill=&#39;url(%23s-d)&#39; d=&#39;m119 144-7-9 66-57-59 66Z&#39; /%3E%3Cpath fill=&#39;%23fb645c&#39; d=&#39;m105 126 7 9 66-57-1-1-72 49Z&#39; /%3E%3C/g%3E%3Cpath stroke=&#39;%23fff&#39; stroke-linecap=&#39;round&#39; stroke-miterlimit=&#39;1&#39; stroke-width=&#39;1.3&#39; d=&#39;m287 278 3-2m-12-17 8-2m-8-3h4m-4-13 8 2m-8 3h4m-1-13 7 3m-4-11 7 4m-2-11 6 6m0-12 6 7m1-11 4 6m4-10 3 7m5-9 2 7m15-7-1 7m10-5-3 7m11-4-4 7m11-2-5 6m16 7-7 4m10 4-7 3m10 6-8 1m8 16-8-2m5 10-7-3m4 11-7-4m2 11-6-5m0 11-5-6m-2 11-4-7m-4 11-3-8m-6 10-1-8m-16 8 2-8m-10 5 3-7m-11 4 4-7m-11 2 5-6m-8 3 3-3m4 8 2-3m5 8 2-4m6 7 1-4m8 5v-4m8 4v-4m9 3-1-4m9 1-2-4m9 0-2-4m9-2-3-3m8-4-3-2m8-5-4-2m7-6-4-1m5-8h-4m4-8h-4m3-9-4 1m1-9-4 2m-1-9-3 2m-2-9-3 3m-4-8-2 3m-5-8-2 4m-6-6-1 3m-8-5v4m-8-4v4m-9-2 1 3m-9 0 2 3m-9 1 2 3m-9 2 3 3m-8 4 3 2m-8 5 4 2m-7 6 4 1m-4 25 4-1m-2 5 7-3m-6 7 4-2m-2 6 7-4m-13-21h8m41-41v-8m0 99v-8m49-42h-8&#39; transform=&#39;translate(-65 8)&#39; /%3E%3C/svg%3E&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
    &lt;span aria-hidden=&quot;true&quot;&gt;
      
        18
      
    &lt;/span&gt;
  &lt;/li&gt;
  
  
        &lt;/ul&gt;
        
      &lt;/div&gt;
    
    
    
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;querying_media_versus_containers&quot; data-text=&quot;Querying media versus containers&quot; tabindex=&quot;-1&quot;&gt;Querying media versus containers&lt;/h2&gt;

&lt;p&gt;There are many situations where container queries and units can be used to replace media queries and viewport units, and that&#39;s great when it helps you express the intent of a design more clearly. But one is not meant to replace the other, and there&#39;s not a &lt;em&gt;better&lt;/em&gt; query or unit that will work in every situation. CSS works best when the relationships established in code match the goals and purpose of the design. When you want text and spacing that is relative to immediate context, container queries provide that functionality, but when you want consistent sizing relative to the overall viewport, media queries and viewport units are still an excellent option. Most websites will likely involve a mix of both.&lt;/p&gt;
  

  
</description><link>https://web.dev/articles/baseline-in-action-container-queries?hl=en</link><guid isPermaLink="false">https://web.dev/articles/baseline-in-action-container-queries?hl=en</guid><pubDate>Thu, 23 Oct 2025 07:00:00 GMT</pubDate></item><item><title>content-visibility: the new CSS property that boosts your rendering performance</title><description>&lt;p&gt;Improve initial load time by skipping the rendering of offscreen content.&lt;/p&gt;

&lt;p&gt;&lt;style&gt;
  .wd-author__links {
    display: flex;
  }
  &lt;/style&gt;

&lt;/p&gt;&lt;p&gt;&lt;/p&gt;

&lt;p&gt;
&lt;/p&gt;&lt;p class=&quot;wd-pubdates&quot; style=&quot;font-size: 90%; border-bottom: 1px solid var(--devsite-ref-palette--grey200);color: var(--devsite-ref-palette--blue-grey700);&quot;&gt;
  Published: August 5, 2020
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;

&lt;aside class=&quot;success&quot;&gt;
  &lt;b&gt;Celebration:&lt;/b&gt; This web feature is now available in all three major browser engines, and becomes &lt;strong&gt;Baseline Newly available&lt;/strong&gt; as of September 15, 2025.
&lt;/aside&gt;

&lt;p&gt;&lt;devsite-iframe&gt;&lt;iframe src=&quot;https://web.dev/frame/articles/content-visibility/index_ec9c555ab7d70c54491bee8dc0f3b7667d026259426329aacda7148b550d120f.frame&quot; class=&quot;framebox inherit-locale &quot; allow=&quot;clipboard-write https://web-dot-devsite-v2-prod-3p.appspot.com&quot; allowfullscreen=&quot;&quot; is-upgraded=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;&lt;/devsite-iframe&gt;&lt;/p&gt;

&lt;p&gt;The
&lt;a href=&quot;https://drafts.csswg.org/css-contain/#propdef-content-visibility&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;content-visibility&lt;/code&gt;&lt;/a&gt;
property enables the
user agent to skip an element&#39;s rendering work, including layout and painting,
until it is needed. Because rendering is skipped, if a large portion of your
content is off-screen, using the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;content-visibility&lt;/code&gt; property makes the
initial user load much faster. It also allows for faster interactions with the
on-screen content. Pretty neat.&lt;/p&gt;

&lt;p&gt;
  
    
    
    
    
  
  
  
    
      
    
  
    
      
    
  
    
      
    
   

    
    
      
      &lt;/p&gt;&lt;div class=&quot;wd-browser-compat&quot; translate=&quot;no&quot;&gt;
        &lt;p&gt;Browser Support&lt;/p&gt;
        &lt;ul&gt;
          
  
  
    
      
        
          
          
        
      
    
    
  

  
  
  
  
    
  
  
  
    
  

  &lt;li data-support=&quot;yes&quot;&gt;
    &lt;img alt=&quot;Chrome: 85.&quot; src=&quot;data:image/svg+xml,%3Csvg xmlns=&#39;http://www.w3.org/2000/svg&#39; width=&#39;24&#39; height=&#39;24&#39; viewBox=&#39;-10 -10 276 276&#39;%3E%3ClinearGradient id=&#39;a&#39; x1=&#39;145&#39; x2=&#39;34&#39; y1=&#39;253&#39; y2=&#39;61&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%231e8e3e&#39;/%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%2334a853&#39;/%3E%3C/linearGradient%3E%3ClinearGradient id=&#39;b&#39; x1=&#39;111&#39; x2=&#39;222&#39; y1=&#39;254&#39; y2=&#39;62&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%23fcc934&#39;/%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%23fbbc04&#39;/%3E%3C/linearGradient%3E%3ClinearGradient id=&#39;c&#39; x1=&#39;17&#39; x2=&#39;239&#39; y1=&#39;80&#39; y2=&#39;80&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%23d93025&#39;/%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%23ea4335&#39;/%3E%3C/linearGradient%3E%3Ccircle cx=&#39;128&#39; cy=&#39;128&#39; r=&#39;64&#39; fill=&#39;%23fff&#39;/%3E%3Cpath fill=&#39;url(%23a)&#39; d=&#39;M96 183a64 64 0 0 1-23-23L17 64a128 128 0 0 0 111 192l55-96a64 64 0 0 1-87 23Z&#39;/%3E%3Cpath fill=&#39;url(%23b)&#39; d=&#39;M192 128a64 64 0 0 1-9 32l-55 96A128 128 0 0 0 239 64H128a64 64 0 0 1 64 64Z&#39;/%3E%3Ccircle cx=&#39;128&#39; cy=&#39;128&#39; r=&#39;52&#39; fill=&#39;%231a73e8&#39;/%3E%3Cpath fill=&#39;url(%23c)&#39; d=&#39;M96 73a64 64 0 0 1 32-9h111a128 128 0 0 0-222 0l56 96a64 64 0 0 1 23-87Z&#39;/%3E%3C/svg%3E&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
    &lt;span aria-hidden=&quot;true&quot;&gt;
      
        85
      
    &lt;/span&gt;
  &lt;/li&gt;
  
  
          
  
  
    
      
        
          
          
        
      
    
    
  

  
  
  
  
    
  
  
  
    
  

  &lt;li data-support=&quot;yes&quot;&gt;
    &lt;img alt=&quot;Edge: 85.&quot; src=&quot;data:image/svg+xml,%3Csvg xmlns=&#39;http://www.w3.org/2000/svg&#39; xmlns:xlink=&#39;http://www.w3.org/1999/xlink&#39; width=&#39;24&#39; height=&#39;24&#39; viewBox=&#39;0 0 27600 27600&#39;%3E%3ClinearGradient id=&#39;A&#39; gradientUnits=&#39;userSpaceOnUse&#39;/%3E%3ClinearGradient id=&#39;B&#39; x1=&#39;6870&#39; x2=&#39;24704&#39; y1=&#39;18705&#39; y2=&#39;18705&#39; xlink:href=&#39;%23A&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%230c59a4&#39;/%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%23114a8b&#39;/%3E%3C/linearGradient%3E%3ClinearGradient id=&#39;C&#39; x1=&#39;16272&#39; x2=&#39;5133&#39; y1=&#39;10968&#39; y2=&#39;23102&#39; xlink:href=&#39;%23A&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%231b9de2&#39;/%3E%3Cstop offset=&#39;.16&#39; stop-color=&#39;%231595df&#39;/%3E%3Cstop offset=&#39;.67&#39; stop-color=&#39;%230680d7&#39;/%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%230078d4&#39;/%3E%3C/linearGradient%3E%3CradialGradient id=&#39;D&#39; cx=&#39;16720&#39; cy=&#39;18747&#39; r=&#39;9538&#39; xlink:href=&#39;%23A&#39;%3E%3Cstop offset=&#39;.72&#39; stop-opacity=&#39;0&#39;/%3E%3Cstop offset=&#39;.95&#39; stop-opacity=&#39;.53&#39;/%3E%3Cstop offset=&#39;1&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;E&#39; cx=&#39;7130&#39; cy=&#39;19866&#39; r=&#39;14324&#39; gradientTransform=&#39;matrix(.14843 -.98892 .79688 .1196 -8759 25542)&#39; xlink:href=&#39;%23A&#39;%3E%3Cstop offset=&#39;.76&#39; stop-opacity=&#39;0&#39;/%3E%3Cstop offset=&#39;.95&#39; stop-opacity=&#39;.5&#39;/%3E%3Cstop offset=&#39;1&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;F&#39; cx=&#39;2523&#39; cy=&#39;4680&#39; r=&#39;20243&#39; gradientTransform=&#39;matrix(-.03715 .99931 -2.12836 -.07913 13579 3530)&#39; xlink:href=&#39;%23A&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%2335c1f1&#39;/%3E%3Cstop offset=&#39;.11&#39; stop-color=&#39;%2334c1ed&#39;/%3E%3Cstop offset=&#39;.23&#39; stop-color=&#39;%232fc2df&#39;/%3E%3Cstop offset=&#39;.31&#39; stop-color=&#39;%232bc3d2&#39;/%3E%3Cstop offset=&#39;.67&#39; stop-color=&#39;%2336c752&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;G&#39; cx=&#39;24247&#39; cy=&#39;7758&#39; r=&#39;9734&#39; gradientTransform=&#39;matrix(.28109 .95968 -.78353 .22949 24510 -16292)&#39; xlink:href=&#39;%23A&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%2366eb6e&#39;/%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%2366eb6e&#39; stop-opacity=&#39;0&#39;/%3E%3C/radialGradient%3E%3Cpath id=&#39;H&#39; d=&#39;M24105 20053a9345 9345 0 01-1053 472 10202 10202 0 01-3590 646c-4732 0-8855-3255-8855-7432 0-1175 680-2193 1643-2729-4280 180-5380 4640-5380 7253 0 7387 6810 8137 8276 8137 791 0 1984-230 2704-456l130-44a12834 12834 0 006660-5282c220-350-168-757-535-565z&#39;/%3E%3Cpath id=&#39;I&#39; d=&#39;M11571 25141a7913 7913 0 01-2273-2137 8145 8145 0 01-1514-4740 8093 8093 0 013093-6395 8082 8082 0 011373-859c312-148 846-414 1554-404a3236 3236 0 012569 1297 3184 3184 0 01636 1866c0-21 2446-7960-8005-7960-4390 0-8004 4166-8004 7820 0 2319 538 4170 1212 5604a12833 12833 0 007684 6757 12795 12795 0 003908 610c1414 0 2774-233 4045-656a7575 7575 0 01-6278-803z&#39;/%3E%3Cpath id=&#39;J&#39; d=&#39;M16231 15886c-80 105-330 250-330 566 0 260 170 512 472 723 1438 1003 4149 868 4156 868a5954 5954 0 003027-839 6147 6147 0 001133-850 6180 6180 0 001910-4437c26-2242-796-3732-1133-4392-2120-4141-6694-6525-11668-6525-7011 0-12703 5635-12798 12620 47-3654 3679-6605 7996-6605 350 0 2346 34 4200 1007 1634 858 2490 1894 3086 2921 618 1067 728 2415 728 2952s-271 1333-780 1990z&#39;/%3E%3Cuse fill=&#39;url(%23B)&#39; xlink:href=&#39;%23H&#39;/%3E%3Cuse fill=&#39;url(%23D)&#39; opacity=&#39;.35&#39; xlink:href=&#39;%23H&#39;/%3E%3Cuse fill=&#39;url(%23C)&#39; xlink:href=&#39;%23I&#39;/%3E%3Cuse fill=&#39;url(%23E)&#39; opacity=&#39;.4&#39; xlink:href=&#39;%23I&#39;/%3E%3Cuse fill=&#39;url(%23F)&#39; xlink:href=&#39;%23J&#39;/%3E%3Cuse fill=&#39;url(%23G)&#39; xlink:href=&#39;%23J&#39;/%3E%3C/svg%3E&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
    &lt;span aria-hidden=&quot;true&quot;&gt;
      
        85
      
    &lt;/span&gt;
  &lt;/li&gt;
  
  
          
  
  
    
      
        
          
          
        
      
    
    
  

  
  
  
  
    
  
  
  
    
  

  &lt;li data-support=&quot;yes&quot;&gt;
    &lt;img alt=&quot;Firefox: 125.&quot; src=&quot;data:image/svg+xml,%3Csvg xmlns=&#39;http://www.w3.org/2000/svg&#39; width=&#39;24&#39; height=&#39;24&#39; viewBox=&#39;0 0 512 512&#39;%3E%3Cdefs%3E%3CradialGradient id=&#39;ff-b&#39; cx=&#39;428.5&#39; cy=&#39;55.1&#39; r=&#39;501&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23ffbd4f&#39;/%3E%3Cstop offset=&#39;.2&#39; stop-color=&#39;%23ffac31&#39;/%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23ff9d17&#39;/%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23ff980e&#39;/%3E%3Cstop offset=&#39;.4&#39; stop-color=&#39;%23ff563b&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff3750&#39;/%3E%3Cstop offset=&#39;.7&#39; stop-color=&#39;%23f5156c&#39;/%3E%3Cstop offset=&#39;.8&#39; stop-color=&#39;%23eb0878&#39;/%3E%3Cstop offset=&#39;.9&#39; stop-color=&#39;%23e50080&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-c&#39; cx=&#39;245.4&#39; cy=&#39;259.9&#39; r=&#39;501&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23960e18&#39;/%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23b11927&#39; stop-opacity=&#39;.7&#39;/%3E%3Cstop offset=&#39;.4&#39; stop-color=&#39;%23db293d&#39; stop-opacity=&#39;.3&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23f5334b&#39; stop-opacity=&#39;.1&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff3750&#39; stop-opacity=&#39;0&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-d&#39; cx=&#39;305.8&#39; cy=&#39;-58.6&#39; r=&#39;363&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23fff44f&#39;/%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23ffdc3e&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff9d12&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff980e&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-e&#39; cx=&#39;190&#39; cy=&#39;390.8&#39; r=&#39;238.6&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%233a8ee6&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%235c79f0&#39;/%3E%3Cstop offset=&#39;.7&#39; stop-color=&#39;%239059ff&#39;/%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%23c139e6&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-f&#39; cx=&#39;252.2&#39; cy=&#39;201.3&#39; r=&#39;126.5&#39; gradientTransform=&#39;matrix(1 0 0 1 -48 31)&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.2&#39; stop-color=&#39;%239059ff&#39; stop-opacity=&#39;0&#39;/%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%238c4ff3&#39; stop-opacity=&#39;.1&#39;/%3E%3Cstop offset=&#39;.8&#39; stop-color=&#39;%237716a8&#39; stop-opacity=&#39;.5&#39;/%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%236e008b&#39; stop-opacity=&#39;.6&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-g&#39; cx=&#39;239.1&#39; cy=&#39;34.6&#39; r=&#39;171.6&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%23ffe226&#39;/%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23ffdb27&#39;/%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23ffc82a&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ffa930&#39;/%3E%3Cstop offset=&#39;.7&#39; stop-color=&#39;%23ff7e37&#39;/%3E%3Cstop offset=&#39;.8&#39; stop-color=&#39;%23ff7139&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-h&#39; cx=&#39;374&#39; cy=&#39;-74.3&#39; r=&#39;732.2&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23fff44f&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff980e&#39;/%3E%3Cstop offset=&#39;.6&#39; stop-color=&#39;%23ff5634&#39;/%3E%3Cstop offset=&#39;.7&#39; stop-color=&#39;%23ff3647&#39;/%3E%3Cstop offset=&#39;.9&#39; stop-color=&#39;%23e31587&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-i&#39; cx=&#39;304.6&#39; cy=&#39;7.1&#39; r=&#39;536.4&#39; gradientTransform=&#39;rotate(84 303 4)&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%23fff44f&#39;/%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23ffe847&#39;/%3E%3Cstop offset=&#39;.2&#39; stop-color=&#39;%23ffc830&#39;/%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23ff980e&#39;/%3E%3Cstop offset=&#39;.4&#39; stop-color=&#39;%23ff8b16&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff672a&#39;/%3E%3Cstop offset=&#39;.6&#39; stop-color=&#39;%23ff3647&#39;/%3E%3Cstop offset=&#39;.7&#39; stop-color=&#39;%23e31587&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-j&#39; cx=&#39;235&#39; cy=&#39;98.1&#39; r=&#39;457.1&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23fff44f&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff980e&#39;/%3E%3Cstop offset=&#39;.6&#39; stop-color=&#39;%23ff5634&#39;/%3E%3Cstop offset=&#39;.7&#39; stop-color=&#39;%23ff3647&#39;/%3E%3Cstop offset=&#39;.9&#39; stop-color=&#39;%23e31587&#39;/%3E%3C/radialGradient%3E%3CradialGradient id=&#39;ff-k&#39; cx=&#39;355.7&#39; cy=&#39;124.9&#39; r=&#39;500.3&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23fff44f&#39;/%3E%3Cstop offset=&#39;.2&#39; stop-color=&#39;%23ffe141&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ffaf1e&#39;/%3E%3Cstop offset=&#39;.6&#39; stop-color=&#39;%23ff980e&#39;/%3E%3C/radialGradient%3E%3ClinearGradient id=&#39;ff-a&#39; x1=&#39;446.9&#39; y1=&#39;76.8&#39; x2=&#39;47.9&#39; y2=&#39;461.8&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23fff44f&#39;/%3E%3Cstop offset=&#39;.1&#39; stop-color=&#39;%23ffe847&#39;/%3E%3Cstop offset=&#39;.2&#39; stop-color=&#39;%23ffc830&#39;/%3E%3Cstop offset=&#39;.4&#39; stop-color=&#39;%23ff980e&#39;/%3E%3Cstop offset=&#39;.4&#39; stop-color=&#39;%23ff8b16&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff672a&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23ff3647&#39;/%3E%3Cstop offset=&#39;.7&#39; stop-color=&#39;%23e31587&#39;/%3E%3C/linearGradient%3E%3ClinearGradient id=&#39;ff-l&#39; x1=&#39;442.1&#39; y1=&#39;74.8&#39; x2=&#39;102.6&#39; y2=&#39;414.3&#39; gradientUnits=&#39;userSpaceOnUse&#39;%3E%3Cstop offset=&#39;.2&#39; stop-color=&#39;%23fff44f&#39; stop-opacity=&#39;.8&#39;/%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23fff44f&#39; stop-opacity=&#39;.6&#39;/%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23fff44f&#39; stop-opacity=&#39;.2&#39;/%3E%3Cstop offset=&#39;.6&#39; stop-color=&#39;%23fff44f&#39; stop-opacity=&#39;0&#39;/%3E%3C/linearGradient%3E%3C/defs%3E%3Cpath d=&#39;M479 166c-11-25-32-52-49-60a249 249 0 0 1 25 73c-27-68-73-95-111-155a255 255 0 0 1-8-14 44 44 0 0 1-4-9 1 1 0 0 0 0-1 1 1 0 0 0-1 0c-60 35-81 101-83 134a120 120 0 0 0-66 25 71 71 0 0 0-6-5 111 111 0 0 1-1-58c-25 11-44 29-58 44-9-12-9-52-8-60l-8 4a175 175 0 0 0-24 21 210 210 0 0 0-22 26 203 203 0 0 0-32 73l-1 2-2 15a229 229 0 0 0-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121zM202 355l3 1-3-1zm55-145zm198-31z&#39; fill=&#39;url(%23ff-a)&#39;/%3E%3Cpath d=&#39;M479 166c-11-25-32-52-49-60 14 26 22 53 25 72v1a207 207 0 0 1-206 279c-113-3-212-87-231-197-3-17 0-26 2-40-2 11-3 14-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121z&#39; fill=&#39;url(%23ff-b)&#39;/%3E%3Cpath d=&#39;M479 166c-11-25-32-52-49-60 14 26 22 53 25 72v1a207 207 0 0 1-206 279c-113-3-212-87-231-197-3-17 0-26 2-40-2 11-3 14-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121z&#39; fill=&#39;url(%23ff-c)&#39;/%3E%3Cpath d=&#39;m362 195 1 1a130 130 0 0 0-22-29C266 92 322 5 331 0c-60 35-81 101-83 134l9-1c45 0 84 25 105 62z&#39; fill=&#39;url(%23ff-d)&#39;/%3E%3Cpath d=&#39;M257 210c-1 6-22 26-29 26-68 0-80 41-80 41 3 35 28 64 57 79l4 2 7 3a107 107 0 0 0 31 6c120 6 143-143 57-186 22-4 45 5 58 14-21-37-60-62-105-62l-9 1a120 120 0 0 0-66 25l17 16c16 16 58 33 58 35z&#39; fill=&#39;url(%23ff-e)&#39;/%3E%3Cpath d=&#39;M257 210c-1 6-22 26-29 26-68 0-80 41-80 41 3 35 28 64 57 79l4 2 7 3a107 107 0 0 0 31 6c120 6 143-143 57-186 22-4 45 5 58 14-21-37-60-62-105-62l-9 1a120 120 0 0 0-66 25l17 16c16 16 58 33 58 35z&#39; fill=&#39;url(%23ff-f)&#39;/%3E%3Cpath d=&#39;m171 151 5 3a111 111 0 0 1-1-58c-25 11-44 29-58 44 1 0 36 0 54 11z&#39; fill=&#39;url(%23ff-g)&#39;/%3E%3Cpath d=&#39;M18 261a242 242 0 0 0 231 197 207 207 0 0 0 206-279c8 56-20 110-64 146-86 71-169 43-186 31l-3-1c-50-24-71-70-67-110-42 0-57-35-57-35s38-28 89-4c46 22 90 4 90 4 0-2-42-19-58-35l-17-16a71 71 0 0 0-6-5l-5-3c-18-11-52-11-54-11-9-12-9-51-8-60l-8 4a175 175 0 0 0-24 21 210 210 0 0 0-22 26 203 203 0 0 0-32 73c0 1-9 38-5 57z&#39; fill=&#39;url(%23ff-h)&#39;/%3E%3Cpath d=&#39;M341 167a130 130 0 0 1 22 29 46 46 0 0 1 4 3c55 50 26 121 24 126 44-36 72-90 64-146-27-68-73-95-111-155a255 255 0 0 1-8-14 44 44 0 0 1-4-9 1 1 0 0 0 0-1 1 1 0 0 0-1 0c-9 5-65 92 10 167z&#39; fill=&#39;url(%23ff-i)&#39;/%3E%3Cpath d=&#39;M367 199a46 46 0 0 0-4-3l-1-1c-13-9-36-18-58-15 86 44 63 193-57 187a107 107 0 0 1-31-6 131 131 0 0 1-11-5c17 12 99 39 186-31 2-5 31-76-24-126z&#39; fill=&#39;url(%23ff-j)&#39;/%3E%3Cpath d=&#39;M148 277s12-41 80-41c7 0 28-20 29-26s-44 18-90-4c-51-24-89 4-89 4s15 35 57 35c-4 40 16 85 67 110l3 1c-29-15-54-44-57-79z&#39; fill=&#39;url(%23ff-k)&#39;/%3E%3Cpath d=&#39;M479 166c-11-25-32-52-49-60a249 249 0 0 1 25 73c-27-68-73-95-111-155a255 255 0 0 1-8-14 44 44 0 0 1-4-9 1 1 0 0 0 0-1 1 1 0 0 0-1 0c-60 35-81 101-83 134l9-1c45 0 84 25 105 62-13-9-36-18-58-14 86 43 63 192-57 186a107 107 0 0 1-31-6 131 131 0 0 1-11-5l-3-1 3 1c-29-15-54-44-57-79 0 0 12-41 80-41 7 0 28-20 29-26 0-2-42-19-58-35l-17-16a71 71 0 0 0-6-5 111 111 0 0 1-1-58c-25 11-44 29-58 44-9-12-9-52-8-60l-8 4a175 175 0 0 0-24 21 210 210 0 0 0-22 26 203 203 0 0 0-32 73l-1 2-2 15a279 279 0 0 0-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121zm-24 13z&#39; fill=&#39;url(%23ff-l)&#39;/%3E%3C/svg%3E&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
    &lt;span aria-hidden=&quot;true&quot;&gt;
      
        125
      
    &lt;/span&gt;
  &lt;/li&gt;
  
  
          
  
  
    
      
        
          
          
        
      
    
    
  

  
  
  
  
    
  
  
  
    
  

  &lt;li data-support=&quot;yes&quot;&gt;
    &lt;img alt=&quot;Safari: 18.&quot; src=&quot;data:image/svg+xml,%3Csvg xmlns=&#39;http://www.w3.org/2000/svg&#39; xmlns:xlink=&#39;http://www.w3.org/1999/xlink&#39; width=&#39;24&#39; height=&#39;24&#39; viewBox=&#39;195 190 135 135&#39;%3E%3Cdefs%3E%3ClinearGradient id=&#39;s-a&#39; x1=&#39;132.6&#39; x2=&#39;134.4&#39; y1=&#39;111.7&#39; y2=&#39;-105.3&#39; xlink:href=&#39;%23s-b&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%23d2d2d2&#39; /%3E%3Cstop offset=&#39;.5&#39; stop-color=&#39;%23f2f2f2&#39; /%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%23fff&#39; /%3E%3C/linearGradient%3E%3ClinearGradient id=&#39;s-b&#39; gradientUnits=&#39;userSpaceOnUse&#39; /%3E%3ClinearGradient id=&#39;s-c&#39; x1=&#39;65.4&#39; x2=&#39;67.4&#39; y1=&#39;115.7&#39; y2=&#39;17.1&#39; xlink:href=&#39;%23s-b&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%23005ad5&#39; /%3E%3Cstop offset=&#39;.2&#39; stop-color=&#39;%230875f0&#39; /%3E%3Cstop offset=&#39;.3&#39; stop-color=&#39;%23218cee&#39; /%3E%3Cstop offset=&#39;.6&#39; stop-color=&#39;%2327a5f3&#39; /%3E%3Cstop offset=&#39;.8&#39; stop-color=&#39;%2325aaf2&#39; /%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%2321aaef&#39; /%3E%3C/linearGradient%3E%3ClinearGradient id=&#39;s-d&#39; x1=&#39;158.7&#39; x2=&#39;176.3&#39; y1=&#39;96.7&#39; y2=&#39;79.5&#39; xlink:href=&#39;%23s-b&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%23c72e24&#39; /%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%23fd3b2f&#39; /%3E%3C/linearGradient%3E%3CradialGradient id=&#39;s-i&#39; cx=&#39;-69.9&#39; cy=&#39;69.3&#39; r=&#39;54&#39; gradientTransform=&#39;matrix(.9 -.01 .04 2.72 -9 -120)&#39; xlink:href=&#39;%23s-b&#39;%3E%3Cstop offset=&#39;0&#39; stop-color=&#39;%2324a5f3&#39; stop-opacity=&#39;0&#39; /%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%231e8ceb&#39; /%3E%3C/radialGradient%3E%3CradialGradient id=&#39;s-j&#39; cx=&#39;109.3&#39; cy=&#39;13.8&#39; r=&#39;93.1&#39; gradientTransform=&#39;matrix(-.02 1.1 -1.04 -.02 137 -115)&#39; xlink:href=&#39;%23s-b&#39;%3E%3Cstop offset=&#39;0&#39; stop-opacity=&#39;0&#39; /%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%235488d6&#39; stop-opacity=&#39;0&#39; /%3E%3Cstop offset=&#39;1&#39; stop-color=&#39;%235d96eb&#39; /%3E%3C/radialGradient%3E%3C/defs%3E%3Crect width=&#39;220&#39; height=&#39;220&#39; x=&#39;22&#39; y=&#39;-107&#39; fill=&#39;url(%23s-a)&#39; ry=&#39;49&#39; transform=&#39;matrix(.57 0 0 .57 187 256)&#39; /%3E%3Cg transform=&#39;translate(194 190)&#39;%3E%3Ccircle cx=&#39;67.8&#39; cy=&#39;67.7&#39; fill=&#39;url(%23s-c)&#39; paint-order=&#39;stroke fill markers&#39; r=&#39;54&#39; /%3E%3Ccircle cx=&#39;-69.9&#39; cy=&#39;69.3&#39; fill=&#39;url(%23s-i)&#39; transform=&#39;translate(138 -2)&#39; r=&#39;54&#39; /%3E%3C/g%3E%3Cellipse cx=&#39;120&#39; cy=&#39;14.2&#39; fill=&#39;url(%23s-j)&#39; rx=&#39;93.1&#39; ry=&#39;93.7&#39; transform=&#39;matrix(.58 0 0 .58 192 250)&#39; /%3E%3Cg transform=&#39;matrix(.58 0 0 .57 197 182)&#39;%3E%3Cpath fill=&#39;%23cac7c8&#39; d=&#39;M46 192h1l72-48-7-9-66 57Z&#39; /%3E%3Cpath fill=&#39;%23fbfffc&#39; d=&#39;M46 191v1l66-57-7-9-59 65Z&#39; /%3E%3Cpath fill=&#39;url(%23s-d)&#39; d=&#39;m119 144-7-9 66-57-59 66Z&#39; /%3E%3Cpath fill=&#39;%23fb645c&#39; d=&#39;m105 126 7 9 66-57-1-1-72 49Z&#39; /%3E%3C/g%3E%3Cpath stroke=&#39;%23fff&#39; stroke-linecap=&#39;round&#39; stroke-miterlimit=&#39;1&#39; stroke-width=&#39;1.3&#39; d=&#39;m287 278 3-2m-12-17 8-2m-8-3h4m-4-13 8 2m-8 3h4m-1-13 7 3m-4-11 7 4m-2-11 6 6m0-12 6 7m1-11 4 6m4-10 3 7m5-9 2 7m15-7-1 7m10-5-3 7m11-4-4 7m11-2-5 6m16 7-7 4m10 4-7 3m10 6-8 1m8 16-8-2m5 10-7-3m4 11-7-4m2 11-6-5m0 11-5-6m-2 11-4-7m-4 11-3-8m-6 10-1-8m-16 8 2-8m-10 5 3-7m-11 4 4-7m-11 2 5-6m-8 3 3-3m4 8 2-3m5 8 2-4m6 7 1-4m8 5v-4m8 4v-4m9 3-1-4m9 1-2-4m9 0-2-4m9-2-3-3m8-4-3-2m8-5-4-2m7-6-4-1m5-8h-4m4-8h-4m3-9-4 1m1-9-4 2m-1-9-3 2m-2-9-3 3m-4-8-2 3m-5-8-2 4m-6-6-1 3m-8-5v4m-8-4v4m-9-2 1 3m-9 0 2 3m-9 1 2 3m-9 2 3 3m-8 4 3 2m-8 5 4 2m-7 6 4 1m-4 25 4-1m-2 5 7-3m-6 7 4-2m-2 6 7-4m-13-21h8m41-41v-8m0 99v-8m49-42h-8&#39; transform=&#39;translate(-65 8)&#39; /%3E%3C/svg%3E&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
    &lt;span aria-hidden=&quot;true&quot;&gt;
      
        18
      
    &lt;/span&gt;
  &lt;/li&gt;
  
  
        &lt;/ul&gt;
        
        &lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/Reference/Properties/content-visibility&quot;&gt;Source&lt;/a&gt;&lt;/p&gt;
        
      &lt;/div&gt;
    
    
    
  &lt;p&gt;&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/content-visibility/image/demo-figures.jpg&quot; alt=&quot;demo with figures representing network results&quot; width=&quot;800&quot; height=&quot;554&quot; srcset=&quot;https://web.dev/static/articles/content-visibility/image/demo-figures_36.jpg 36w,https://web.dev/static/articles/content-visibility/image/demo-figures_48.jpg 48w,https://web.dev/static/articles/content-visibility/image/demo-figures_72.jpg 72w,https://web.dev/static/articles/content-visibility/image/demo-figures_96.jpg 96w,https://web.dev/static/articles/content-visibility/image/demo-figures_480.jpg 480w,https://web.dev/static/articles/content-visibility/image/demo-figures_720.jpg 720w,https://web.dev/static/articles/content-visibility/image/demo-figures_856.jpg 856w,https://web.dev/static/articles/content-visibility/image/demo-figures_960.jpg 960w,https://web.dev/static/articles/content-visibility/image/demo-figures_1440.jpg 1440w,https://web.dev/static/articles/content-visibility/image/demo-figures_1920.jpg 1920w,https://web.dev/static/articles/content-visibility/image/demo-figures_2880.jpg 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption class=&quot;wd-caption&quot;&gt;In our article demo, applying &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;content-visibility: auto&lt;/code&gt; to chunked content areas gives a &lt;b&gt;7x&lt;/b&gt; rendering performance boost on initial load. Read on to learn more.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;containment&quot; data-text=&quot;CSS Containment&quot; tabindex=&quot;-1&quot;&gt;CSS Containment&lt;/h2&gt;

&lt;p&gt;The key and overarching goal of CSS containment is to enable rendering
performance improvements of web content by providing &lt;strong&gt;predictable isolation of
a DOM subtree&lt;/strong&gt; from the rest of the page.&lt;/p&gt;

&lt;p&gt;Basically a developer can tell a browser what parts of the page are encapsulated
as a set of content, allowing the browsers to reason about the content without
needing to consider state outside of the subtree. Knowing which bits of content
(subtrees) contain isolated content means the browser can make optimization
decisions for page rendering.&lt;/p&gt;

&lt;p&gt;There are four types of &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/CSS_containment&quot;&gt;CSS
containment&lt;/a&gt;,
each a potential value for the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;contain&lt;/code&gt; CSS property, which can be combined
in a space-separated list of values:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;size&lt;/code&gt;: Size containment on an element ensures that the element&#39;s box can be
laid out without needing to examine its descendants. This means we can
potentially skip layout of the descendants if all we need is the size of the
element.&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;layout&lt;/code&gt;: Layout containment means that the descendants do not affect the
external layout of other boxes on the page. This allows us to potentially skip
layout of the descendants if all we want to do is lay out other boxes.&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;style&lt;/code&gt;: Style containment ensures that properties which can have effects on
more than just its descendants don&#39;t escape the element (e.g. counters). This
allows us to potentially skip style computation for the descendants if all we
want is to compute styles on other elements.&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;paint&lt;/code&gt;: Paint containment ensures that the descendants of the containing box
don&#39;t display outside its bounds. Nothing can visibly overflow the element,
and if an element is off-screen or otherwise not visible, its descendants will
also not be visible. This allows us to potentially skip painting the
descendants if the element is offscreen.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;skip_rendering_work_with_content-visibility&quot; data-text=&quot;Skip rendering work with content-visibility&quot; tabindex=&quot;-1&quot;&gt;Skip rendering work with &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;content-visibility&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;It may be hard to figure out which containment values to use, since browser
optimizations may only kick in when an appropriate set is specified. You can
play around with the values to see &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/CSS_containment&quot;&gt;what works
best&lt;/a&gt;, or you
can use  &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;content-visibility&lt;/code&gt; to apply the needed
containment automatically. &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;content-visibility&lt;/code&gt; ensures that you get the largest
performance gains the browser can provide with minimal effort from you as a
developer.&lt;/p&gt;

&lt;p&gt;The content-visibility property accepts several values, but &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;auto&lt;/code&gt; is the one
that provides immediate performance improvements. An element that has
&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;content-visibility: auto&lt;/code&gt; gains &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;layout&lt;/code&gt;, &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;style&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;paint&lt;/code&gt; containment. If
the element is off-screen (and not otherwise relevant to the user—relevant
elements would be the ones that have focus or selection in their subtree), it
also gains &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;size&lt;/code&gt; containment (and it stops
&lt;a href=&quot;https://developer.chrome.com/blog/inside-browser-part3#paint&quot;&gt;painting&lt;/a&gt;
and
&lt;a href=&quot;https://developer.chrome.com/blog/inside-browser-part4#finding-the-event-target&quot;&gt;hit-testing&lt;/a&gt;
its contents).&lt;/p&gt;

&lt;p&gt;What does this mean? In short, if the element is off-screen its descendants are
not rendered. The browser determines the size of the element without considering
any of its contents, and it stops there. Most of the rendering, such as styling
and layout of the element&#39;s subtree are skipped.&lt;/p&gt;

&lt;p&gt;As the element approaches the viewport, the browser no longer adds the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;size&lt;/code&gt;
containment and starts painting and hit-testing the element&#39;s content. This
enables the rendering work to be done just in time to be seen by the user.&lt;/p&gt;

&lt;aside class=&quot;caution&quot;&gt;&lt;b&gt;Caution: &lt;/b&gt; The browser is only able to skip rendering work if you are also careful not to call any DOM API that &lt;a href=&quot;https://gist.github.com/paulirish/5d52fb081b3570c81e3a#file-what-forces-layout-md&quot;&gt;forces some of rendering to occur&lt;/a&gt; on one of the skipped subtrees. If you&#39;re using &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;content-visibility&lt;/code&gt; to improve performance, audit your code to make sure these APIs are not getting called. To help find them, Chromium will print console messages if you call one of these APIs for a subtree of an element with &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;content-visibility:hidden&lt;/code&gt;. To see the messages, &lt;a href=&quot;https://developer.chrome.com/docs/devtools/console/log&quot;&gt;turn on verbose logging&lt;/a&gt;. &lt;/aside&gt;

&lt;h2 id=&quot;a_note_on_accessibility&quot; data-text=&quot;A note on accessibility&quot; tabindex=&quot;-1&quot;&gt;A note on accessibility&lt;/h2&gt;

&lt;p&gt;One of the features of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;content-visibility: auto&lt;/code&gt; is that the off-screen content remains available in the document object model and therefore, the accessibility tree (unlike with &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;visibility: hidden&lt;/code&gt;). This means, that content can be searched for on the page, and navigated to, without waiting for it to load or sacrificing rendering performance.&lt;/p&gt;

&lt;p&gt;The flip-side of this, however, is that &lt;a href=&quot;https://www.w3.org/TR/wai-aria-1.1/#landmark_roles&quot;&gt;landmark&lt;/a&gt; elements with style features such as &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;display: none&lt;/code&gt; or &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;visibility: hidden&lt;/code&gt; will also appear in the accessibility tree when off-screen, since the browser won&#39;t render these styles until they enter the viewport. To prevent these from being visible in the accessibility tree, potentially causing clutter, be sure to also add &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;aria-hidden=&quot;true&quot;&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;example&quot; data-text=&quot;Example: a travel blog&quot; tabindex=&quot;-1&quot;&gt;Example: a travel blog&lt;/h2&gt;

&lt;figure&gt;
  &lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;
    &lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/content-visibility/travel_blog.mp4&quot;&gt;
  &lt;/video&gt;
  &lt;figcaption class=&quot;wd-caption&quot;&gt;In this example, we baseline our travel blog on the right, and apply &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;content-visibility: auto&lt;/code&gt; to chunked areas on the left. The results show rendering times going from &lt;b&gt;232ms&lt;/b&gt; to &lt;b&gt;30ms&lt;/b&gt; on initial page load.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;A travel blog typically contains a set of stories with a few pictures, and some
descriptive text. Here is what happens in a typical browser when it navigates to
a travel blog:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A part of the page is downloaded from the network, along with any needed
resources.&lt;/li&gt;
&lt;li&gt;The browser styles and lays out all of the contents of the page, without
considering if the content is visible to the user.&lt;/li&gt;
&lt;li&gt;The browser goes back to step 1 until all of the page and resources are
downloaded.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In step 2, the browser processes all of the contents looking for things that may
have changed. It updates the style and layout of any new elements, along with
the elements that may have shifted as a result of new updates. This is rendering
work. This takes time.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/content-visibility/image/a-screenshot-a-travel-bl-d12bedce7d152.jpg&quot; alt=&quot;A screenshot of a travel blog.&quot; width=&quot;800&quot; height=&quot;563&quot; srcset=&quot;https://web.dev/static/articles/content-visibility/image/a-screenshot-a-travel-bl-d12bedce7d152_36.jpg 36w,https://web.dev/static/articles/content-visibility/image/a-screenshot-a-travel-bl-d12bedce7d152_48.jpg 48w,https://web.dev/static/articles/content-visibility/image/a-screenshot-a-travel-bl-d12bedce7d152_72.jpg 72w,https://web.dev/static/articles/content-visibility/image/a-screenshot-a-travel-bl-d12bedce7d152_96.jpg 96w,https://web.dev/static/articles/content-visibility/image/a-screenshot-a-travel-bl-d12bedce7d152_480.jpg 480w,https://web.dev/static/articles/content-visibility/image/a-screenshot-a-travel-bl-d12bedce7d152_720.jpg 720w,https://web.dev/static/articles/content-visibility/image/a-screenshot-a-travel-bl-d12bedce7d152_856.jpg 856w,https://web.dev/static/articles/content-visibility/image/a-screenshot-a-travel-bl-d12bedce7d152_960.jpg 960w,https://web.dev/static/articles/content-visibility/image/a-screenshot-a-travel-bl-d12bedce7d152_1440.jpg 1440w,https://web.dev/static/articles/content-visibility/image/a-screenshot-a-travel-bl-d12bedce7d152_1920.jpg 1920w,https://web.dev/static/articles/content-visibility/image/a-screenshot-a-travel-bl-d12bedce7d152_2880.jpg 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption class=&quot;wd-caption&quot;&gt;An example of a travel blog. See &lt;a href=&quot;https://codepen.io/una/pen/rNxEWLo&quot;&gt;Demo on Codepen&lt;/a&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Now consider what happens if you put &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;content-visibility: auto&lt;/code&gt; on each of the
individual stories in the blog. The general loop is the same: the browser
downloads and renders chunks of the page. However, the difference is in the
amount of work that it does in step 2.&lt;/p&gt;

&lt;p&gt;With content-visibility, it will style and layout all of the contents that are
currently visible to the user (they are on-screen). However, when processing the
story that is fully off-screen, the browser will skip the rendering work and
only style and layout the element box itself.&lt;/p&gt;

&lt;p&gt;The performance of loading this page would be as if it contained full on-screen
stories and empty boxes for each of the off-screen stories. This performs much
better, with &lt;em&gt;expected reduction of 50% or more&lt;/em&gt; from the rendering cost of
loading. In our example, we see a boost from a &lt;strong&gt;232ms&lt;/strong&gt; rendering time to a
&lt;strong&gt;30ms&lt;/strong&gt; rendering time. That&#39;s a &lt;strong&gt;7x&lt;/strong&gt; performance boost.&lt;/p&gt;

&lt;p&gt;What is the work that you need to do in order to reap these benefits? First, we
chunk the content into sections:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/content-visibility/image/an-annotated-screenshot-9dac5c2e32b01.jpg&quot; alt=&quot;An annotated screenshot of chunking content into sections with a CSS class.&quot; width=&quot;800&quot; height=&quot;563&quot; srcset=&quot;https://web.dev/static/articles/content-visibility/image/an-annotated-screenshot-9dac5c2e32b01_36.jpg 36w,https://web.dev/static/articles/content-visibility/image/an-annotated-screenshot-9dac5c2e32b01_48.jpg 48w,https://web.dev/static/articles/content-visibility/image/an-annotated-screenshot-9dac5c2e32b01_72.jpg 72w,https://web.dev/static/articles/content-visibility/image/an-annotated-screenshot-9dac5c2e32b01_96.jpg 96w,https://web.dev/static/articles/content-visibility/image/an-annotated-screenshot-9dac5c2e32b01_480.jpg 480w,https://web.dev/static/articles/content-visibility/image/an-annotated-screenshot-9dac5c2e32b01_720.jpg 720w,https://web.dev/static/articles/content-visibility/image/an-annotated-screenshot-9dac5c2e32b01_856.jpg 856w,https://web.dev/static/articles/content-visibility/image/an-annotated-screenshot-9dac5c2e32b01_960.jpg 960w,https://web.dev/static/articles/content-visibility/image/an-annotated-screenshot-9dac5c2e32b01_1440.jpg 1440w,https://web.dev/static/articles/content-visibility/image/an-annotated-screenshot-9dac5c2e32b01_1920.jpg 1920w,https://web.dev/static/articles/content-visibility/image/an-annotated-screenshot-9dac5c2e32b01_2880.jpg 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption class=&quot;wd-caption&quot;&gt;Example of chunking content into sections with the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;story&lt;/code&gt; class applied, to receive &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;content-visibility: auto&lt;/code&gt;. See &lt;a href=&quot;https://codepen.io/vmpstr/pen/xxZoyMb&quot;&gt;Demo on Codepen&lt;/a&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Then, we apply the following style rule to the sections:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nc&quot;&gt;story&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;content-visibility&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;auto&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;contain-intrinsic-size&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kt&quot;&gt;px&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-c&quot;&gt;/* Explained in the next section. */&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;aside class=&quot;note&quot;&gt;&lt;b&gt;Note: &lt;/b&gt; Note that as content moves in and out of visibility, it will start and stop being rendered as needed. However, this does not mean that the browser will have to render and re-render the same content over and over again, since the rendering work is saved when possible. &lt;/aside&gt;

&lt;h3 id=&quot;specify_the_natural_size_of_an_element_with_contain-intrinsic-size&quot; data-text=&quot;Specify the natural size of an element with contain-intrinsic-size&quot; tabindex=&quot;-1&quot;&gt;Specify the natural size of an element with &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;contain-intrinsic-size&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;In order to realize the potential benefits of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;content-visibility&lt;/code&gt;, the browser
needs to apply size containment to ensure that the rendering results of contents
don&#39;t affect the size of the element in any way. This means that the element
will lay out as if it was empty. If the element does not have a height specified
in a regular block layout, then it will be of 0 height.&lt;/p&gt;

&lt;p&gt;This might not be ideal, since the size of the scrollbar will shift, being
reliant on each story having a non-zero height.&lt;/p&gt;

&lt;p&gt;Thankfully, CSS provides another property, &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;contain-intrinsic-size&lt;/code&gt;, which
effectively specifies the natural size of the element &lt;em&gt;if the element is
affected by size containment&lt;/em&gt;. In our example, we are setting it to &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;1000px&lt;/code&gt; as
an estimate for the height and width of the sections.&lt;/p&gt;

&lt;p&gt;This means it will lay out as if it had a single child of &quot;intrinsic-size&quot;
dimensions, ensuring that your unsized divs still occupy space.
&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;contain-intrinsic-size&lt;/code&gt; acts as a placeholder size in lieu of rendered content.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://drafts.csswg.org/css-sizing-4/#valdef-contain-intrinsic-width-auto-length&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;auto&lt;/code&gt;&lt;/a&gt;
keyword for &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;contain-intrinsic-size&lt;/code&gt; causes the browser will remember
the last-rendered size, if any, and use that instead of the developer-provided placeholder
size. For example, if you specified &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;contain-intrinsic-size: auto 300px&lt;/code&gt;, the
element will start out with a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;300px&lt;/code&gt; intrinsic sizing in each dimension, but once
the element&#39;s contents are rendered, it will retain the rendered intrinsic size.
Any subsequent rendering size changes will also be remembered. In practice, this means that if you
scroll an element with &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;content-visibility: auto&lt;/code&gt; applied, and then scroll it back
offscreen, it will automatically retain its ideal width and height, and not revert
to the placeholder sizing. This feature is especially useful for infinite scrollers,
which can now automatically improve sizing estimation over time as the user
explores the page.&lt;/p&gt;

&lt;aside class=&quot;note&quot;&gt;&lt;b&gt;Note: &lt;/b&gt; We can use &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;IntersectionObserver&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;MutationObserver&lt;/code&gt; to set the correct sizes inline for each element. &lt;a href=&quot;https://twitter.com/slightlylate&quot;&gt;Alex Russell&lt;/a&gt; explains how this works in &lt;a href=&quot;https://infrequently.org/2020/12/content-visibility-scroll-fix/&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;content-visibility&lt;/code&gt; without jittery scrollbars&lt;/a&gt;, and &lt;a href=&quot;https://infrequently.org/2020/12/resize-resilient-deferred-rendering/&quot;&gt;Resize-Resilient &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;content-visibility&lt;/code&gt; Fixes&lt;/a&gt;. &lt;/aside&gt;

&lt;h2 id=&quot;hide_content_with_content-visibility_hidden&quot; data-text=&quot;Hide content with content-visibility: hidden&quot; tabindex=&quot;-1&quot;&gt;Hide content with &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;content-visibility: hidden&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;What if you want to keep the content unrendered regardless of whether or not it
is on-screen, while leveraging the benefits of cached rendering state? Enter:
&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;content-visibility: hidden&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;content-visibility: hidden&lt;/code&gt; property gives you all of the same benefits of
unrendered content and cached rendering state as &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;content-visibility: auto&lt;/code&gt; does
off-screen. However, unlike with &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;auto&lt;/code&gt;, it does not automatically start to
render on-screen.&lt;/p&gt;

&lt;p&gt;This gives you more control, allowing you to hide an element&#39;s contents and
later unhide them quickly.&lt;/p&gt;

&lt;p&gt;Compare it to other common ways of hiding element&#39;s contents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;display: none&lt;/code&gt;: hides the element and destroys its rendering state. This
means unhiding the element is as expensive as rendering a new element with the
same contents.&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;visibility: hidden&lt;/code&gt;: hides the element and keeps its rendering state. This
doesn&#39;t truly remove the element from the document, as it (and it&#39;s subtree)
still takes up geometric space on the page and can still be clicked on. It
also updates the rendering state any time it is needed even when hidden.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;content-visibility: hidden&lt;/code&gt;, on the other hand, hides the element while
preserving its rendering state, so, if there are any changes that need to
happen, they only happen when the element is shown again (i.e. the
&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;content-visibility: hidden&lt;/code&gt; property is removed).&lt;/p&gt;

&lt;p&gt;Some great use cases for &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;content-visibility: hidden&lt;/code&gt; are when implementing
advanced virtual scrollers, and measuring layout. They&#39;re also great for
single-page applications (SPA&#39;s). Inactive app views can be left in the DOM with
&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;content-visibility: hidden&lt;/code&gt; applied to prevent their display but maintain their
cached state. This makes the view quick to render when it becomes active again.&lt;/p&gt;

&lt;aside class=&quot;note&quot;&gt;&lt;b&gt;Note: &lt;/b&gt; In an experiment, Facebook engineers observed an up to 250ms improvement in navigation times when going back to previously cached views. &lt;/aside&gt;

&lt;h2 id=&quot;effects_on_interaction_to_next_paint_inp&quot; data-text=&quot;Effects on Interaction to Next Paint (INP)&quot; tabindex=&quot;-1&quot;&gt;Effects on Interaction to Next Paint (INP)&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://web.dev/articles/inp&quot;&gt;INP&lt;/a&gt; is a metric that evaluates a page&#39;s ability to be reliably responsive to user input. Responsiveness can be affected by any excessive amount of work that occurs on the main thread, including rendering work.&lt;/p&gt;

&lt;p&gt;Whenever you can reduce rendering work on any given page, you&#39;re giving the main thread an opportunity to respond to user inputs more quickly. This includes rendering work, and using the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;content-visiblity&lt;/code&gt; CSS property where appropriate can reduce rendering work—especially during startup, when most rendering and layout work is done.&lt;/p&gt;

&lt;p&gt;Reducing rendering work has a direct effect on INP. When users attempt to interact with a page that uses the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;content-visibility&lt;/code&gt; property properly to defer layout and rendering of offscreen elements, you&#39;re giving the main thread a chance to respond to critical user-visible work. This can improve your page&#39;s INP in some situations.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot; data-text=&quot;Conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;content-visibility&lt;/code&gt; and the CSS Containment Spec mean some exciting performance
boosts are coming right to your CSS file. For more information on these
properties, check out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://drafts.csswg.org/css-contain/&quot;&gt;The CSS Containment Spec&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/CSS_Containment&quot;&gt;MDN Docs on CSS
Containment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/w3c/csswg-drafts&quot;&gt;CSSWG Drafts&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
  

  
</description><link>https://web.dev/articles/content-visibility?hl=en</link><guid isPermaLink="false">https://web.dev/articles/content-visibility?hl=en</guid><pubDate>Tue, 23 Sep 2025 07:00:00 GMT</pubDate></item><item><title>Use Baseline with Browserslist</title><description>&lt;p&gt;&lt;style&gt;
  .wd-author__links {
    display: flex;
  }
  &lt;/style&gt;

&lt;/p&gt;

&lt;p class=&quot;wd-pubdates&quot; style=&quot;font-size: 90%; border-bottom: 1px solid var(--devsite-ref-palette--grey200);color: var(--devsite-ref-palette--blue-grey700);&quot;&gt;
  Published: May 7, 2025, Last updated: September 17, 2025
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/browserslist/browserslist&quot;&gt;Browserslist&lt;/a&gt; is one of the most popular tools for configuring minimum supported browser versions in frontend code bases. Developers add a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;browserslist&lt;/code&gt; query to their &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;package.json&lt;/code&gt; file (or other configuration point for Browserslist, such as a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;.browserslistrc&lt;/code&gt; file), and Browserslist exposes a list of minimum supported browsers. Browserslist can be used with a wide range of popular linting, polyfilling and packaging tools, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/postcss/autoprefixer&quot;&gt;Autoprefixer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://babeljs.io/&quot;&gt;Babel&lt;/a&gt; using &lt;a href=&quot;https://babeljs.io/docs/babel-preset-env#browserslist-integration&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;@babel/preset-env&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/postcss/postcss&quot;&gt;PostCSS&lt;/a&gt; using &lt;a href=&quot;https://github.com/csstools/postcss-plugins/tree/main/plugin-packs/postcss-preset-env&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;postcss-preset-env&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eslint.org/&quot;&gt;ESLint&lt;/a&gt; using &lt;a href=&quot;https://github.com/amilajack/eslint-plugin-compat&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;eslint-plugin-compat&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stylelint.io/&quot;&gt;Stylelint&lt;/a&gt; using &lt;a href=&quot;https://github.com/RJWadley/stylelint-no-unsupported-browser-features&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;stylelint-no-unsupported-browser-features&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://webpack.js.org/configuration/target/#browserslist&quot;&gt;webpack&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;baseline_targets&quot; data-text=&quot;Baseline targets&quot; tabindex=&quot;-1&quot;&gt;Baseline targets&lt;/h2&gt;

&lt;p&gt;When you decide to use Baseline, you should consider your user base and decide which Baseline feature set you want to target:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Baseline Widely available&lt;/strong&gt; includes all web features that were fully supported by the Baseline core browser set 30 or more months in the past.&lt;/li&gt;
&lt;li&gt;Baseline year feature sets, for example &lt;strong&gt;Baseline 2020&lt;/strong&gt;, include all features that were &lt;strong&gt;Newly available&lt;/strong&gt; at the end of the specified year.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Depending on your user base, you may be able to target Baseline Widely available, or you may need to target an older Baseline year. Consult your analytics or RUM tools to understand which browsers versions your users have.&lt;/p&gt;

&lt;h2 id=&quot;how_to_target_baseline_newly_or_widely_available&quot; data-text=&quot;How to target Baseline Newly or Widely available&quot; tabindex=&quot;-1&quot;&gt;How to target Baseline Newly or Widely available&lt;/h2&gt;

&lt;p&gt;Support for Baseline is built into Browserslist through a few different queries. If you want to target Baseline Newly available browsers, try specifying &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;baseline newly available&lt;/code&gt; in your Browserslist config:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JSON&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;browserslist&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;[&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;baseline newly available&quot;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;   &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;You can specify &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;baseline widely available&lt;/code&gt; as a query, too:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JSON&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;browserslist&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;[&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;baseline widely available&quot;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;   &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;h2 id=&quot;how_to_target_baseline_years&quot; data-text=&quot;How to target Baseline years&quot; tabindex=&quot;-1&quot;&gt;How to target Baseline years&lt;/h2&gt;

&lt;p&gt;If you want to target a Baseline year feature set, you specify the year in a query, such as &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;baseline 2024&lt;/code&gt; if you want to target the 2024 feature set:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JSON&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;browserslist&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;baseline 2024&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;You can specify years from &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;baseline 2015&lt;/code&gt; to the current year.&lt;/p&gt;

&lt;h2 id=&quot;how_to_specify_downstream_browsers&quot; data-text=&quot;How to specify downstream browsers&quot; tabindex=&quot;-1&quot;&gt;How to specify downstream browsers&lt;/h2&gt;

&lt;p&gt;The Baseline core browser set includes Chrome, Edge, Firefox, and Safari. Other browsers are based on the same open source code as Chrome and Edge—Chromium—and should support the same feature set as whichever version of Chromium they implement. To include these browsers in your Baseline configuration, add &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;with downstream&lt;/code&gt; after your Baseline query. For example, to target downstream browsers as part of Baseline Widely available, specify &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;baseline widely available with downstream&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JSON&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;browserslist&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;baseline widely available with downstream&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;baseline newly available with downstream&lt;/code&gt; is another valid query, as is adding &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;with downstream&lt;/code&gt; to yearly targets:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JSON&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;browserslist&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;baseline 2024 with downstream&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;h2 id=&quot;examples_of_baseline_queries_in_action&quot; data-text=&quot;Examples of Baseline queries in action&quot; tabindex=&quot;-1&quot;&gt;Examples of Baseline queries in action&lt;/h2&gt;

&lt;h3 id=&quot;in_packaging_tools&quot; data-text=&quot;In packaging tools&quot; tabindex=&quot;-1&quot;&gt;In packaging tools&lt;/h3&gt;

&lt;p&gt;Using the Baseline queries offered by Browserslist in your project can have an immediate effect. Babel is a popular build tool for packaging Javascript. If you use the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;@babel/preset-env&lt;/code&gt; package defaults, many modern Javascript APIs and methods will be replaced with the more verbose syntax required by older browsers:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://web.dev/static/articles/use-baseline-with-browserslist/image/fig-1.png&quot; alt=&quot;A terminal session showing that the npm run build command has been executed on a Javascript file called test.js.  The output file size is 12 kilobytes.&quot; width=&quot;800&quot; height=&quot;258&quot; srcset=&quot;https://web.dev/static/articles/use-baseline-with-browserslist/image/fig-1_36.png 36w,https://web.dev/static/articles/use-baseline-with-browserslist/image/fig-1_48.png 48w,https://web.dev/static/articles/use-baseline-with-browserslist/image/fig-1_72.png 72w,https://web.dev/static/articles/use-baseline-with-browserslist/image/fig-1_96.png 96w,https://web.dev/static/articles/use-baseline-with-browserslist/image/fig-1_480.png 480w,https://web.dev/static/articles/use-baseline-with-browserslist/image/fig-1_720.png 720w,https://web.dev/static/articles/use-baseline-with-browserslist/image/fig-1_856.png 856w,https://web.dev/static/articles/use-baseline-with-browserslist/image/fig-1_960.png 960w,https://web.dev/static/articles/use-baseline-with-browserslist/image/fig-1_1440.png 1440w,https://web.dev/static/articles/use-baseline-with-browserslist/image/fig-1_1920.png 1920w,https://web.dev/static/articles/use-baseline-with-browserslist/image/fig-1_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;

&lt;p&gt;However, using a query of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;baseline 2020&lt;/code&gt; to target the 2020 feature set on the same example project dramatically decreases the output size of this Javascript, because fewer syntax transforms are required:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://web.dev/static/articles/use-baseline-with-browserslist/image/fig-3.png&quot; alt=&quot;A second terminal session showing that the npm run build command has now produced a 1.5 kilobyte file when babel is set to target Baseline 2020.&quot; width=&quot;800&quot; height=&quot;196&quot; srcset=&quot;https://web.dev/static/articles/use-baseline-with-browserslist/image/fig-3_36.png 36w,https://web.dev/static/articles/use-baseline-with-browserslist/image/fig-3_48.png 48w,https://web.dev/static/articles/use-baseline-with-browserslist/image/fig-3_72.png 72w,https://web.dev/static/articles/use-baseline-with-browserslist/image/fig-3_96.png 96w,https://web.dev/static/articles/use-baseline-with-browserslist/image/fig-3_480.png 480w,https://web.dev/static/articles/use-baseline-with-browserslist/image/fig-3_720.png 720w,https://web.dev/static/articles/use-baseline-with-browserslist/image/fig-3_856.png 856w,https://web.dev/static/articles/use-baseline-with-browserslist/image/fig-3_960.png 960w,https://web.dev/static/articles/use-baseline-with-browserslist/image/fig-3_1440.png 1440w,https://web.dev/static/articles/use-baseline-with-browserslist/image/fig-3_1920.png 1920w,https://web.dev/static/articles/use-baseline-with-browserslist/image/fig-3_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;

&lt;p&gt;Try this for yourself using the &lt;a href=&quot;https://github.com/GoogleChromeLabs/baseline-demos/tree/main/tooling/babel&quot;&gt;example code&lt;/a&gt; in the Google Chrome Labs &lt;a href=&quot;https://github.com/GoogleChromeLabs/baseline-demos/tree/main&quot;&gt;baseline-demos&lt;/a&gt; repository.&lt;/p&gt;

&lt;h3 id=&quot;in_linters&quot; data-text=&quot;In linters&quot; tabindex=&quot;-1&quot;&gt;In linters&lt;/h3&gt;

&lt;p&gt;Some linters already work with Browserslist, or can be made to work with Browserslist using a compatibility module. For example, &lt;a href=&quot;https://stylelint.io/&quot;&gt;stylelint&lt;/a&gt; can consume a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;browserslist&lt;/code&gt; config using the &lt;a href=&quot;https://github.com/3846masa/stylelint-browser-compat&quot;&gt;stylelint-browser-compat&lt;/a&gt; module. Set your &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;stylelint.config.js&lt;/code&gt; file to use the Baseline query of your choice:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JavaScript&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;plugins&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;stylelint-browser-compat&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;],&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;plugin/browser-compat&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;[&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;browserslist&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;baseline widely available&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;],&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;],&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;Stylelint will now flag CSS that isn&#39;t currently part of Baseline Widely available:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://web.dev/static/articles/use-baseline-with-browserslist/image/fig-2.png&quot; alt=&quot;a list of warnings from Stylelint highlighting CSS code that doesn&#39;t work on older browsers.&quot; width=&quot;800&quot; height=&quot;271&quot; srcset=&quot;https://web.dev/static/articles/use-baseline-with-browserslist/image/fig-2_36.png 36w,https://web.dev/static/articles/use-baseline-with-browserslist/image/fig-2_48.png 48w,https://web.dev/static/articles/use-baseline-with-browserslist/image/fig-2_72.png 72w,https://web.dev/static/articles/use-baseline-with-browserslist/image/fig-2_96.png 96w,https://web.dev/static/articles/use-baseline-with-browserslist/image/fig-2_480.png 480w,https://web.dev/static/articles/use-baseline-with-browserslist/image/fig-2_720.png 720w,https://web.dev/static/articles/use-baseline-with-browserslist/image/fig-2_856.png 856w,https://web.dev/static/articles/use-baseline-with-browserslist/image/fig-2_960.png 960w,https://web.dev/static/articles/use-baseline-with-browserslist/image/fig-2_1440.png 1440w,https://web.dev/static/articles/use-baseline-with-browserslist/image/fig-2_1920.png 1920w,https://web.dev/static/articles/use-baseline-with-browserslist/image/fig-2_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;

&lt;p&gt;Stylelint also provides &lt;a href=&quot;https://www.npmjs.com/package/stylelint-plugin-use-baseline&quot;&gt;a plugin&lt;/a&gt; that lets you set Baseline rules directly, but if you are already using Browserslist to handle your config, using the built-in Baseline queries that Browserslist offers is a viable solution.&lt;/p&gt;

  

  
</description><link>https://web.dev/articles/use-baseline-with-browserslist?hl=en</link><guid isPermaLink="false">https://web.dev/articles/use-baseline-with-browserslist?hl=en</guid><pubDate>Wed, 17 Sep 2025 07:00:00 GMT</pubDate></item><item><title>Create Baseline tools with the web-features package</title><description>&lt;p&gt;&lt;style&gt;
  .wd-author__links {
    display: flex;
  }
  &lt;/style&gt;

&lt;/p&gt;

&lt;p class=&quot;wd-pubdates&quot; style=&quot;font-size: 90%; border-bottom: 1px solid var(--devsite-ref-palette--grey200);color: var(--devsite-ref-palette--blue-grey700);&quot;&gt;
  Published: September 12, 2025
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Emerging tooling for &lt;a href=&quot;https://web.dev/baseline&quot;&gt;Baseline&lt;/a&gt; helps you determine which web features are safe to use in your web applications today. These tools, such as linters and IDE features, use a data source of web platform features that contains data about their support in modern browsers.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://webstatus.dev/&quot;&gt;Web Platform Status Dashboard&lt;/a&gt; is one data source for these features and provides a &lt;a href=&quot;https://web.dev/articles/web-platform-dashboard-baseline&quot;&gt;queryable HTTP API&lt;/a&gt;. Querying this data source on demand is not practical for all tools. For many tooling use cases, the &lt;a href=&quot;https://www.npmjs.com/package/web-features&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;web-features&lt;/code&gt; npm package&lt;/a&gt; is a more reliable and practical version of web feature data that you use locally. This guide will show you how to use the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;web-features&lt;/code&gt; package to develop your own Baseline tools.&lt;/p&gt;

&lt;h2 id=&quot;get_started_with_the_web-features_package&quot; data-text=&quot;Get started with the web-features package&quot; tabindex=&quot;-1&quot;&gt;Get started with the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;web-features&lt;/code&gt; package&lt;/h2&gt;

&lt;p&gt;First, install the package as a dependency in your project:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;Bash&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;npm&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;install&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;web-features
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;Once installed, import one or more named exports from the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;web-features&lt;/code&gt; package into your project:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JavaScript&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;features&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;browsers&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;snapshots&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kr&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;web-features&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;Depending on your goals, you might not need all of these exports. These exports provide the following information:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;features&lt;/code&gt;: An array of objects, where each object represents a web feature. This is the export you will most frequently use.&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;browsers&lt;/code&gt;: An array of objects describing browsers.&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;groups&lt;/code&gt;: An array of objects that defines a logical grouping of features. These are useful if your goal is to target a subset of web platform features, such as those for CSS, JavaScript, and more specific groupings.&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;snapshots&lt;/code&gt;: Contains the ECMAScript versions of corresponding JavaScript features, such as ES2023, ES2022, and other ECMAScript versions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The features export provides the following properties for building Baseline tooling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;id&lt;/code&gt;: The unique identifier for the feature. For example, the identifier for &lt;a href=&quot;https://webstatus.dev/features/grid?q=id&quot;&gt;CSS Grid&lt;/a&gt; is &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&quot;grid&quot;&lt;/code&gt;. &lt;strong&gt;Note&lt;/strong&gt;: this is not a property within the data object. It&#39;s the object key.&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;compat_features&lt;/code&gt;: An array of individual &lt;a href=&quot;https://web.dev/articles/baseline-tools-web-features?hl=en#look_up_the_baseline_status_for_a_specific_bcd_key&quot;&gt;BCD keys&lt;/a&gt; included in the feature.&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;name&lt;/code&gt;: The human-readable string of the feature&#39;s name, such as &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&quot;Container Queries&quot;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;description&lt;/code&gt;: A short description of the feature.&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;group&lt;/code&gt;: The group in which the feature belongs. This corresponds to group data found in the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;groups&lt;/code&gt; export.&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;baseline&lt;/code&gt;: The feature&#39;s Baseline status. This can be one of three values:
&lt;ol&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;false&lt;/code&gt;: The feature&#39;s status is Limited availability.&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&quot;low&lt;/code&gt;&quot;: The feature is Baseline Newly available.&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&quot;high&lt;/code&gt;&quot;: The feature is Baseline Widely available.&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;baseline_high_date&lt;/code&gt;: The date the feature became Baseline Widely available. &lt;strong&gt;Note&lt;/strong&gt;: this is not available if the feature is not Baseline Widely available.&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;baseline_low_date&lt;/code&gt;: The date the feature became Baseline Newly available. &lt;strong&gt;Note&lt;/strong&gt;: this is not available for Limited availability features.&lt;/li&gt;
&lt;/ol&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The following example is the full data object for the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/CSS_grid_layout/Subgrid&quot;&gt;CSS subgrid&lt;/a&gt; feature:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JSON&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;subgrid&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;caniuse&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;css-subgrid&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;compat_features&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;[&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;css.properties.grid-template-columns.subgrid&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;css.properties.grid-template-rows.subgrid&quot;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;],&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;The subgrid value for the grid-template-columns and grid-template-rows properties allows a grid item to inherit the grid definition of its parent grid container.&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;description_html&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;The &amp;lt;code&amp;gt;subgrid&amp;lt;/code&amp;gt; value for the &amp;lt;code&amp;gt;grid-template-columns&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;grid-template-rows&amp;lt;/code&amp;gt; properties allows a grid item to inherit the grid definition of its parent grid container.&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;group&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;grid&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;Subgrid&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;spec&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;https://drafts.csswg.org/css-grid-2/#subgrids&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;status&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;baseline&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;low&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;baseline_low_date&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;2023-09-15&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;support&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;chrome&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;117&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;chrome_android&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;117&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;edge&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;117&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;firefox&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;71&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;firefox_android&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;79&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;safari&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;16&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;safari_ios&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;16&quot;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;groups&lt;/code&gt; export provides data on feature groupings with the following structure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;id&lt;/code&gt;: The unique identifier for the group. For example, CSS features have an ID of&quot;css&quot;. &lt;strong&gt;Note:&lt;/strong&gt; this is not a property within the groups object. It&#39;s the object key itself.&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;name&lt;/code&gt;: The human-readable name of the group.&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;parent&lt;/code&gt;: The ID of the group&#39;s parent group. This is not available if the group doesn&#39;t have a parent.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;examples&quot; data-text=&quot;Examples&quot; tabindex=&quot;-1&quot;&gt;Examples&lt;/h2&gt;

&lt;p&gt;The following examples show how to use the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;web-features package&lt;/code&gt; to get the data you need to build Baseline tooling.&lt;/p&gt;

&lt;h3 id=&quot;look_up_the_baseline_status_for_a_specific_feature&quot; data-text=&quot;Look up the Baseline status for a specific feature&quot; tabindex=&quot;-1&quot;&gt;Look up the Baseline status for a specific feature&lt;/h3&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JavaScript&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;features&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kr&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;web-features&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;getBaselineStatus&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;featureId&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;features&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;featureId&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;baseline&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;This example represents the most direct use case for the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;web-features&lt;/code&gt; package, where you specify a feature&#39;s ID to get its Baseline data available in the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;status.baseline&lt;/code&gt; property. This property tells you if a feature is Limited availability, Newly available, or Widely available. In the latter two cases, you also have access to dates that describe when the feature reached a certain Baseline threshold.&lt;/p&gt;

&lt;h3 id=&quot;look_up_the_baseline_status_for_a_specific_bcd_key&quot; data-text=&quot;Look up the Baseline status for a specific BCD key&quot; tabindex=&quot;-1&quot;&gt;Look up the Baseline status for a specific BCD key&lt;/h3&gt;

&lt;p&gt;BCD stands for &lt;a href=&quot;https://github.com/mdn/browser-compat-data/&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;browser-compat-data&lt;/code&gt;&lt;/a&gt;, a project maintained by MDN to catalog support for features across browsers. A BCD key corresponds to a granular feature, like a specific CSS property value or a behavioral subfeature defined by the spec.&lt;/p&gt;

&lt;p&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;web-features&lt;/code&gt; project groups related BCD keys into the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;compat_features&lt;/code&gt; array for each feature. Sometimes you need the Baseline status of one BCD key, rather than the overall Baseline status for the entire feature. For example, if you build a CSS linter and need to know if a property-value pair is compatible across major browsers, the feature-level data is too coarse. This happens because the package aggregates each feature over all its constituent BCD keys, and some keys have better browser support than others.&lt;/p&gt;

&lt;p&gt;To look up the Baseline status of a BCD key, inspect the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;status.by_compat_key&lt;/code&gt; property of the feature object (available in &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;web-features&lt;/code&gt; 3.6.0 and later):&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JavaScript&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;features&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kr&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;web-features&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;getBaselineStatus&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;featureId&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;bcdKey&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;features&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;featureId&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;by_compat_key&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;bcdKey&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;For example, the output of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;getBaselineStatus(&#39;outline&#39;, &#39;css.properties.outline&#39;)&lt;/code&gt; is:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JSON&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;baseline&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;low&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;baseline_low_date&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;2023-03-27&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;support&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;chrome&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;94&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;chrome_android&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;94&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;edge&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;94&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;firefox&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;88&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;firefox_android&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;88&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;safari&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;16.4&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;safari_ios&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;16.4&quot;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;h3 id=&quot;sort_features_by_their_baseline_status&quot; data-text=&quot;Sort features by their Baseline status&quot; tabindex=&quot;-1&quot;&gt;Sort features by their Baseline status&lt;/h3&gt;

&lt;p&gt;This example iterates through every feature in the features object and sorts each one into an array by its Baseline status.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JavaScript&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;features&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kr&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;web-features&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;webFeatures&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;features&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;widelyAvailable&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;webFeatures&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;feature&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&amp;gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;feature&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;baseline&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;===&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;high&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;});&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;newlyAvailable&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;webFeatures&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;feature&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&amp;gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;feature&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;baseline&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;===&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;low&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;});&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;limitedAvailability&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;webFeatures&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;feature&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&amp;gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;feature&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;baseline&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;===&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;This example illustrates how to work with the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;status.baseline&lt;/code&gt; property in your tooling. For example, you could use this approach in a web application to display a list of all web features by their Baseline status.&lt;/p&gt;

&lt;h3 id=&quot;find_all_features_within_a_group&quot; data-text=&quot;Find all features within a group&quot; tabindex=&quot;-1&quot;&gt;Find all features within a group&lt;/h3&gt;

&lt;p&gt;So far, all the examples shown use the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;features&lt;/code&gt; export, but the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;groups&lt;/code&gt; export organizes all features logically within groups. This means you could, for example, find all features that are part of View Transitions:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JavaScript&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;features&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kr&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;web-features&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;groupKeys&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;featuresData&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;features&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;viewTransitionsGroup&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;groupKeys&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;groupKey&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&amp;gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;groupKey&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;===&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;view-transitions&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;});&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;viewTransitionsFeatures&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;featuresData&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;feature&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&amp;gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;feature&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;group&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;===&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;viewTransitionsGroup&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;The intersection between the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;features&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;group&lt;/code&gt; exports lets you narrow your search for web features by the group they belong to.&lt;/p&gt;

&lt;h2 id=&quot;putting_it_all_together&quot; data-text=&quot;Putting it all together&quot; tabindex=&quot;-1&quot;&gt;Putting it all together&lt;/h2&gt;

&lt;p&gt;You can use the lookups to build useful tools, for example to create a CSS linter.&lt;/p&gt;

&lt;p&gt;CSS linters evaluate at-rules, pseudo-elements, properties, and property-value pairs. For your Baseline linter, look up the status of each token by its BCD key, not by its parent feature.&lt;/p&gt;

&lt;p&gt;A parser like CSSTree tokenizes CSS, breaking a stylesheet into an abstract syntax tree (AST). The following example shows a CSS rule that uses a generic class selector and a style declaration:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nc&quot;&gt;foo&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;word-break&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;auto&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;phrase&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;The AST would look something like this:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JSON&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;StyleSheet&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;children&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;[&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;Rule&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;prelude&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;SelectorList&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;children&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;[&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;          &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;Selector&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;children&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;[&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;              &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;                &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;ClassSelector&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;                &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;foo&quot;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;              &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;          &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;block&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;Block&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;children&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;[&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;          &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;Declaration&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;important&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;property&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;word-break&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;              &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;Value&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;              &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;children&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;[&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;                &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;                  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;Identifier&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;                  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;auto-phrase&quot;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;                &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;              &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;            &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;          &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;aside class=&quot;note&quot;&gt;
  &lt;b&gt;Note:&lt;/b&gt; The output has been simplified for readability. You can explore the full AST for this example using ESLint&#39;s &lt;a href=&quot;https://explorer.eslint.org/#eslint-explorer=IntcInN0YXRlXCI6e1widG9vbFwiOlwiYXN0XCIsXCJjb2RlXCI6e1wiamF2YXNjcmlwdFwiOlwiLyoqXFxuICogVHlwZSBvciBwYXN0ZSBzb21lIEphdmFTY3JpcHQgaGVyZSB0byBsZWFybiBtb3JlIGFib3V0XFxuICogdGhlIHN0YXRpYyBhbmFseXNpcyB0aGF0IEVTTGludCBjYW4gZG8gZm9yIHlvdS5cXG4gKiBcXG4gKiBUaGUgdGhyZWUgdGFicyBhcmU6XFxuICogXFxuICogLSBBU1QgLSBUaGUgQWJzdHJhY3QgU3ludGF4IFRyZWUgb2YgdGhlIGNvZGUsIHdoaWNoIGNhblxcbiAqICAgYmUgdXNlZnVsIHRvIHVuZGVyc3RhbmQgdGhlIHN0cnVjdHVyZSBvZiB0aGUgY29kZS4gWW91XFxuICogICBjYW4gdmlldyB0aGlzIHN0cnVjdHVyZSBhcyBKU09OIG9yIGluIGEgdHJlZSBmb3JtYXQuXFxuICogLSBTY29wZSAtIFRoZSBzY29wZSBzdHJ1Y3R1cmUgb2YgdGhlIGNvZGUsIHdoaWNoIGNhbiBiZVxcbiAqICAgdXNlZnVsIHRvIHVuZGVyc3RhbmQgaG93IHZhcmlhYmxlcyBhcmUgZGVmaW5lZCBhbmRcXG4gKiAgIHdoZXJlIHRoZXkgYXJlIHVzZWQuXFxuICogLSBDb2RlIFBhdGggLSBUaGUgY29kZSBwYXRoIHN0cnVjdHVyZSBvZiB0aGUgY29kZSwgd2hpY2hcXG4gKiAgIGNhbiBiZSB1c2VmdWwgdG8gdW5kZXJzdGFuZCBob3cgdGhlIGNvZGUgaXMgZXhlY3V0ZWQuXFxuICogXFxuICogWW91IGNhbiBjaGFuZ2UgdGhlIHdheSB0aGF0IHRoZSBKYXZhU2NyaXB0IGNvZGUgaXMgaW50ZXJwcmV0ZWRcXG4gKiBieSBjbGlja2luZyBcXFwiSmF2YVNjcmlwdFxcXCIgaW4gdGhlIGhlYWRlciBhbmQgc2VsZWN0aW5nIGRpZmZlcmVudFxcbiAqIG9wdGlvbnMuXFxuICovXFxuXFxuaW1wb3J0IGpzIGZyb20gXFxcIkBlc2xpbnQvanNcXFwiO1xcblxcbmZ1bmN0aW9uIGdldENvbmZpZygpIHtcXG4gICAgcmV0dXJuIHtcXG4gICAgICAgIHJ1bGVzOiB7XFxuICAgICAgICAgICAgXFxcInByZWZlci1jb25zdFxcXCI6IFxcXCJ3YXJuXFxcIlxcbiAgICAgICAgfVxcbiAgICB9O1xcbn1cXG5cXG5leHBvcnQgZGVmYXVsdCBbXFxuICAgIC4uLmpzLmNvbmZpZ3MucmVjb21tZW5kZWQsXFxuICAgIGdldENvbmZpZygpXFxuXTtcIixcImpzb25cIjpcIi8qKlxcbiAqIFR5cGUgb3IgcGFzdGUgc29tZSBKU09OIGhlcmUgdG8gbGVhcm4gbW9yZSBhYm91dFxcbiAqIHRoZSBzdGF0aWMgYW5hbHlzaXMgdGhhdCBFU0xpbnQgY2FuIGRvIGZvciB5b3UuXFxuICpcXG4gKiBUaGUgdGFicyBhcmU6XFxuICpcXG4gKiAtIEFTVCAtIFRoZSBBYnN0cmFjdCBTeW50YXggVHJlZSBvZiB0aGUgY29kZSwgd2hpY2ggY2FuXFxuICogICBiZSB1c2VmdWwgdG8gdW5kZXJzdGFuZCB0aGUgc3RydWN0dXJlIG9mIHRoZSBjb2RlLiBZb3VcXG4gKiAgIGNhbiB2aWV3IHRoaXMgc3RydWN0dXJlIGFzIEpTT04gb3IgaW4gYSB0cmVlIGZvcm1hdC5cXG4gKlxcbiAqIFlvdSBjYW4gY2hhbmdlIHRoZSB3YXkgdGhhdCB0aGUgSlNPTiBjb2RlIGlzIGludGVycHJldGVkXFxuICogYnkgY2xpY2tpbmcgXFxcIkpTT05cXFwiIGluIHRoZSBoZWFkZXIgYW5kIHNlbGVjdGluZyBkaWZmZXJlbnRcXG4gKiBvcHRpb25zLlxcbiAqXFxuICogVGhpcyBleGFtcGxlIGlzIGluIEpTT05DIG1vZGUsIHdoaWNoIGFsbG93cyBjb21tZW50cy5cXG4gKi9cXG5cXG57XFxuICAgIFxcXCJrZXkxXFxcIjogW3RydWUsIGZhbHNlLCBudWxsXSxcXG4gICAgXFxcImtleTJcXFwiOiB7XFxuICAgICAgICBcXFwia2V5M1xcXCI6IFsxLCAyLCBcXFwiM1xcXCIsIDFlMTAsIDFlLTNdXFxuICAgIH1cXG59XCIsXCJtYXJrZG93blwiOlwiPCEtLVxcblR5cGUgb3IgcGFzdGUgc29tZSBNYXJrZG93biBoZXJlIHRvIGxlYXJuIG1vcmUgYWJvdXRcXG50aGUgc3RhdGljIGFuYWx5c2lzIHRoYXQgRVNMaW50IGNhbiBkbyBmb3IgeW91LlxcblxcblRoZSB0YWJzIGFyZTpcXG5cXG4tIEFTVCAtIFRoZSBBYnN0cmFjdCBTeW50YXggVHJlZSBvZiB0aGUgY29kZSwgd2hpY2ggY2FuXFxuYmUgdXNlZnVsIHRvIHVuZGVyc3RhbmQgdGhlIHN0cnVjdHVyZSBvZiB0aGUgY29kZS4gWW91XFxuY2FuIHZpZXcgdGhpcyBzdHJ1Y3R1cmUgYXMgSlNPTiBvciBpbiBhIHRyZWUgZm9ybWF0LlxcblxcbllvdSBjYW4gY2hhbmdlIHRoZSB3YXkgdGhhdCB0aGUgTWFya2Rvd24gY29kZSBpcyBpbnRlcnByZXRlZFxcbmJ5IGNsaWNraW5nIFxcXCJNYXJrZG93blxcXCIgaW4gdGhlIGhlYWRlciBhbmQgc2VsZWN0aW5nIGRpZmZlcmVudFxcbm9wdGlvbnMuXFxuXFxuVGhpcyBleGFtcGxlIGlzIGluIENvbW1vbk1hcmsgbW9kZS5cXG4tLT5cXG5cXG4jIEVTTGludCBNYXJrZG93biBFeGFtcGxlXFxuXFxuVGhpcyBpcyBhbiBleGFtcGxlIG9mIGEgTWFya2Rvd24gZmlsZSB0aGF0IGNhbiBiZSBwYXJzZWRcXG5ieSBFU0xpbnQuIE1hcmtkb3duIGlzIGEgc2ltcGxlIG1hcmt1cCBsYW5ndWFnZSB0aGF0IGlzXFxub2Z0ZW4gdXNlZCBmb3IgZG9jdW1lbnRhdGlvbi5cXG5cXG4jIyBGZWF0dXJlc1xcblxcbi0gTWFrZSB0aGluZ3MgKml0YWxpYyosICoqYm9sZCoqLCBvciBgY29kZWBcXG4tIENyZWF0ZSBbbGlua3NdKGh0dHBzOi8vZXNsaW50Lm9yZylcXG4tIFN1cHBvcnRzIEhUTUwgPHNwYW4gc3R5bGU9XFxcImNvbG9yOiByZWQ7XFxcIj5lbGVtZW50czwvc3Bhbj5cXG4tIExpc3RzXFxuICAtIE5lc3RlZCBsaXN0c1wiLFwiY3NzXCI6XCIuZm9vIHtcXG4gIHdvcmQtYnJlYWs6IGF1dG8tcGhyYXNlO1xcbn1cIixcImh0bWxcIjpcIjwhRE9DVFlQRSBodG1sPlxcbjwhLS1cXG5UeXBlIG9yIHBhc3RlIHNvbWUgSFRNTCBoZXJlIHRvIGxlYXJuIG1vcmUgYWJvdXRcXG50aGUgc3RhdGljIGFuYWx5c2lzIHRoYXQgRVNMaW50IGNhbiBkbyBmb3IgeW91LlxcblxcblRoZSB0YWJzIGFyZTpcXG5cXG4tIEFTVCAtIFRoZSBBYnN0cmFjdCBTeW50YXggVHJlZSBvZiB0aGUgY29kZSwgd2hpY2ggY2FuXFxuYmUgdXNlZnVsIHRvIHVuZGVyc3RhbmQgdGhlIHN0cnVjdHVyZSBvZiB0aGUgY29kZS4gWW91XFxuY2FuIHZpZXcgdGhpcyBzdHJ1Y3R1cmUgYXMgSlNPTiBvciBpbiBhIHRyZWUgZm9ybWF0Llxcbi0tPlxcblxcbjxodG1sIGxhbmc9XFxcImVuXFxcIj5cXG4gICAgPGhlYWQ%2BXFxuICAgICAgICA8bWV0YSBjaGFyc2V0PVxcXCJVVEYtOFxcXCI%2BXFxuICAgICAgICA8dGl0bGU%2BSFRNTDwvdGl0bGU%2BXFxuICAgIDwvaGVhZD5cXG4gICAgPGJvZHk%2BXFxuICAgICAgICA8cD5UZXh0PC9wPlxcbiAgICA8L2JvZHk%2BXFxuPC9odG1sPlwifSxcImxhbmd1YWdlXCI6XCJjc3NcIixcImpzT3B0aW9uc1wiOntcInBhcnNlclwiOlwiZXNwcmVlXCIsXCJzb3VyY2VUeXBlXCI6XCJtb2R1bGVcIixcImVzVmVyc2lvblwiOlwibGF0ZXN0XCIsXCJpc0pTWFwiOnRydWV9LFwianNvbk9wdGlvbnNcIjp7XCJqc29uTW9kZVwiOlwianNvbmNcIn0sXCJjc3NPcHRpb25zXCI6e1wiY3NzTW9kZVwiOlwiY3NzXCIsXCJ0b2xlcmFudFwiOnRydWV9LFwibWFya2Rvd25PcHRpb25zXCI6e1wibWFya2Rvd25Nb2RlXCI6XCJjb21tb25tYXJrXCJ9LFwiaHRtbE9wdGlvbnNcIjp7XCJ0ZW1wbGF0ZUVuZ2luZVN5bnRheFwiOlwibm9uZVwiLFwiZnJvbnRtYXR0ZXJcIjpmYWxzZX0sXCJ3cmFwXCI6dHJ1ZSxcInZpZXdNb2Rlc1wiOntcImFzdFZpZXdcIjpcImpzb25cIixcInNjb3BlVmlld1wiOlwiZmxhdFwiLFwicGF0aFZpZXdcIjpcImdyYXBoXCJ9LFwicGF0aEluZGV4XCI6e1wiaW5kZXhcIjowLFwiaW5kZXhlc1wiOjF9LFwiZXNxdWVyeVNlbGVjdG9yXCI6e1wic2VsZWN0b3JcIjpcIlwifX0sXCJ2ZXJzaW9uXCI6MH0i&quot;&gt;Code Explorer&lt;/a&gt;.
&lt;/aside&gt;

&lt;p&gt;When you find a property declaration as you walk through the AST, map that property name to its corresponding BCD key to check its Baseline status. BCD categorizes CSS properties under the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;css.properties&lt;/code&gt; object, so the corresponding BCD key is &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;css.properties.word-break&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then, find the feature associated with that BCD key and look up its status.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JavaScript&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;// Assuming we only know the BCD key and not the feature ID.&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;bcdKey&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;css.properties.word-break&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;featureId&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;feature&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;entries&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;features&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(([&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;feature&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;])&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&amp;gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;feature&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;compat_features&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;bcdKey&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;||&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;[];&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;feature&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;by_compat_key&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;bcdKey&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;The Baseline status for the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;word-break&lt;/code&gt; property is as follows:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JSON&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;baseline&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;high&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;baseline_high_date&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;2018-03-30&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;baseline_low_date&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;2015-09-30&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;support&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;chrome&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;44&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;chrome_android&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;44&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;edge&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;12&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;firefox&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;15&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;firefox_android&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;15&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;safari&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;9&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;safari_ios&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;9&quot;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&quot;high&quot;&lt;/code&gt; Baseline status indicates the property is widely available, so your linter does not need to issue a warning. Next, examine the property&#39;s value.&lt;/p&gt;

&lt;p&gt;The AST shows that the value of this property is &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;auto-phrase&lt;/code&gt;. To get the corresponding BCD key, add the value to the parent property&#39;s BCD key: &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;css.properties.word-break.auto-phrase&lt;/code&gt;.&lt;/p&gt;

&lt;aside class=&quot;note&quot;&gt;
  Note: This method only works for values that the property&#39;s specification names. Generic value types like numbers or color names don&#39;t have their own BCD keys. If &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;web-features&lt;/code&gt; does not return data for a given BCD key, there is nothing to validate, so ignore it.
&lt;/aside&gt;

&lt;p&gt;In this case, &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;web-features&lt;/code&gt; shows that the status of the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;word-break: auto-phrase&lt;/code&gt; property-value pair is not Baseline:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JSON&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;baseline&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;support&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;chrome&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;119&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;chrome_android&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;119&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;edge&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;119&quot;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;Use the information in this linter example to warn the developer that this property-value pair is not Baseline. This warning ensures that the developer knows it won&#39;t work as expected across the major browser engines.&lt;/p&gt;

&lt;h2 id=&quot;real-world_examples&quot; data-text=&quot;Real-world examples&quot; tabindex=&quot;-1&quot;&gt;Real-world examples&lt;/h2&gt;

&lt;p&gt;The previous examples show basic uses of the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;web-features&lt;/code&gt; package. There are already a number of tools taking advantage of this data. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;eslint-css&lt;/code&gt; uses &lt;a href=&quot;https://github.com/eslint/css/blob/11ae50fb404d1620309921ebadec006498db4091/src/rules/use-baseline.js&quot;&gt;the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;web-features&lt;/code&gt; package in its &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;use-baseline&lt;/code&gt; rule&lt;/a&gt; to lint for specific features.&lt;/li&gt;
&lt;li&gt;The &lt;a href=&quot;https://github.com/eslint/css/blob/11ae50fb404d1620309921ebadec006498db4091/src/rules/use-baseline.js&quot;&gt;hovercards in Visual Studio Code&lt;/a&gt; use the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;web-features&lt;/code&gt; package to show Baseline information for &lt;a href=&quot;https://github.com/microsoft/vscode-custom-data/blob/b5ed147e96797eb5ecf0448948d1d21241110296/web-data/css/generateData.mjs#L382&quot;&gt;CSS features&lt;/a&gt; and &lt;a href=&quot;https://github.com/microsoft/vscode-custom-data/blob/b5ed147e96797eb5ecf0448948d1d21241110296/web-data/css/generateData.mjs#L382&quot;&gt;HTML features&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;MDN uses the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;web-features&lt;/code&gt; data in the &lt;a href=&quot;https://github.com/mdn/yari/blob/89e027d23f5fde0e1d6f2c01e3b31f7c35a8f30a/client/src/document/baseline-indicator.tsx&quot;&gt;banners&lt;/a&gt; on most feature pages (for example, &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/abs&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;abs()&lt;/code&gt;&lt;/a&gt;) to communicate their Baseline status.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are more examples out there of how this data source can be used to build Baseline tools—and we hope that your project becomes one of them!&lt;/p&gt;

  

  
</description><link>https://web.dev/articles/baseline-tools-web-features?hl=en</link><guid isPermaLink="false">https://web.dev/articles/baseline-tools-web-features?hl=en</guid><pubDate>Fri, 12 Sep 2025 07:00:00 GMT</pubDate></item><item><title>Getting started with measuring Web Vitals</title><description>&lt;p&gt;&lt;style&gt;
  .wd-author__links {
    display: flex;
  }
  &lt;/style&gt;

&lt;/p&gt;&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Collecting data on your site&#39;s Web Vitals is the first step towards improving them. A well-rounded analysis will collect performance data from both real-world and lab environments. Measuring Web Vitals requires minimal code changes and can be accomplished using free tooling.&lt;/p&gt;

&lt;h2 id=&quot;measure_web_vitals_using_rum_data&quot; data-text=&quot;Measure Web Vitals using RUM data&quot; tabindex=&quot;-1&quot;&gt;Measure Web Vitals using RUM data&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Real_user_monitoring&quot;&gt;Real User Monitoring&lt;/a&gt; (RUM) data, also known as field data, captures the performance experienced by a site&#39;s actual users. RUM data is what Google uses to determine whether a site meets the &lt;a href=&quot;https://web.dev/articles/vitals&quot;&gt;recommended Core Web Vitals thresholds.&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;getting_started&quot; data-text=&quot;Getting started&quot; tabindex=&quot;-1&quot;&gt;Getting started&lt;/h3&gt;

&lt;p&gt;If you don&#39;t have a RUM setup, the following tools will quickly provide you with data about the real-world performance of your site. These tools are all based on the same underlying dataset (the &lt;a href=&quot;https://developer.chrome.com/docs/crux&quot;&gt;Chrome User Experience Report&lt;/a&gt;), but have slightly different use cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://developer.chrome.com/docs/devtools/performance/overview&quot;&gt;Chrome DevTools&lt;/a&gt;&lt;/strong&gt; integrates with the CrUX dataset in the live metrics view of the Performance panel. By comparing your local experience against real-user experiences on the same page, you can make a more informed decision about where to focus your debugging efforts. If you&#39;re looking for a single action to take in order to get started with measuring and improving your site&#39;s Web Vitals, we recommend using the Chrome DevTools Performance panel.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://pagespeed.web.dev/&quot;&gt;PageSpeed Insights (PSI)&lt;/a&gt;&lt;/strong&gt; reports on the aggregate page-level and origin-level performance over the past 28 days. In addition, it provides suggestions on how to improve performance. PSI is available on the &lt;a href=&quot;https://pagespeed.web.dev/&quot;&gt;web&lt;/a&gt; and as an &lt;a href=&quot;https://developers.google.com/speed/docs/insights/v5/get-started&quot;&gt;API&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://search.google.com/search-console/welcome&quot;&gt;Search Console&lt;/a&gt;&lt;/strong&gt; reports performance data on a per-page basis. This makes it well-suited for identifying specific pages that need improvement. Unlike PageSpeed Insights, Search Console reporting includes historical performance data. Search Console can only be used with sites that you own and have verified ownership of.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://developer.chrome.com/docs/crux/vis&quot;&gt;CrUX Vis&lt;/a&gt;&lt;/strong&gt; is a prebuilt dashboard that surfaces CrUX history data for a URL or origin of your choosing (where available in the CrUX dataset). It is built on top of the CrUX History API and the setup process takes about a minute. Compared to PageSpeed Insights and Search Console, CrUX Vis includes more data example, LCP subparts, navigation types, and so forth.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://cruxvis.withgoogle.com/&quot;&gt;CrUX Vis&lt;/a&gt;&lt;/strong&gt; is a historical dashboard that surfaces CrUX data for an origin or URL of your choosing. It is built on top of CrUX History API. Compared to PageSpeed Insights and Search Console, CrUX Vis reporting includes more details—for example, &lt;a href=&quot;https://developer.chrome.com/blog/crux-navigation-types&quot;&gt;navigation types&lt;/a&gt; and &lt;a href=&quot;https://developer.chrome.com/blog/crux-2025-02&quot;&gt;LCP and RTT data&lt;/a&gt; is available in CrUX Vis.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It&#39;s worth noting that although the tools listed previously are well-suited for &quot;getting started&quot; with measuring Web Vitals, they can be useful in other contexts as well. In particular, both CrUX and PSI are available as an API and can be used to build dashboards and other reporting.&lt;/p&gt;

&lt;h3 id=&quot;collect_rum_data&quot; data-text=&quot;Collect RUM data&quot; tabindex=&quot;-1&quot;&gt;Collect RUM data&lt;/h3&gt;

&lt;p&gt;Although CrUX-based tools are a good starting point for investigating Web Vitals performance, we strongly recommend supplementing it with your own RUM. RUM data that you collect yourself can provide more detailed and immediate feedback on your site&#39;s performance. This makes it easier to identify issues and test possible solutions.&lt;/p&gt;

&lt;aside class=&quot;note&quot;&gt;&lt;b&gt;Note:&lt;/b&gt; CrUX-based data sources report data using a granularity of approximately one month—however, the details of this vary slightly by tool. For example, PSI and Search Console report the performance observed over the past 28 days, whereas the CrUX dataset and dashboard is broken down by calendar month.&lt;/aside&gt;

&lt;p&gt;You can collect your own RUM data by using a dedicated RUM provider, or, by setting up your own tooling.&lt;/p&gt;

&lt;p&gt;Dedicated RUM providers specialize in collecting and reporting RUM data. To use Core Web Vitals with these services, ask your RUM provider about enabling Core Web Vitals monitoring for your site.&lt;/p&gt;

&lt;p&gt;If you don&#39;t have a RUM provider, you may be able to augment your existing analytics setup to collect and report on these metrics by using the &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;web-vitals&lt;/code&gt; JavaScript library&lt;/a&gt;. This method is explained in more detail next.&lt;/p&gt;

&lt;h3 id=&quot;the_web-vitals_javascript_library&quot; data-text=&quot;The web-vitals JavaScript library&quot; tabindex=&quot;-1&quot;&gt;The web-vitals JavaScript library&lt;/h3&gt;

&lt;p&gt;If you&#39;re implementing your own RUM setup for Web Vitals, the easiest way to collect Web Vitals measurements is to use the &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;web-vitals&lt;/code&gt;&lt;/a&gt; JavaScript library. &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;web-vitals&lt;/code&gt; is a small, modular library (~2KB) that provides a convenient API for collecting and reporting each of the &lt;a href=&quot;https://web.dev/articles/user-centric-performance-metrics#in_the_field&quot;&gt;field-measurable&lt;/a&gt; Web Vitals metrics.&lt;/p&gt;

&lt;p&gt;The metrics that make up Web Vitals are not all directly exposed by the browser&#39;s built-in performance APIs—but rather built on top of them. For example, &lt;a href=&quot;https://web.dev/articles/cls&quot;&gt;Cumulative Layout Shift (CLS)&lt;/a&gt; is implemented using the &lt;a href=&quot;https://wicg.github.io/layout-instability/&quot;&gt;Layout Instability API&lt;/a&gt;. By using &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;web-vitals&lt;/code&gt;, you don&#39;t need to worry about implementing these metrics yourself; it also ensures that the data you collect matches the methodology and best practices for each metric.&lt;/p&gt;

&lt;p&gt;For more information on implementing &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;web-vitals&lt;/code&gt;, refer to the &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals&quot;&gt;documentation&lt;/a&gt; and the &lt;a href=&quot;https://web.dev/articles/vitals-field-measurement-best-practices&quot;&gt;Best practices for measuring Web Vitals in the field&lt;/a&gt; guide.&lt;/p&gt;

&lt;h3 id=&quot;data_aggregation&quot; data-text=&quot;Data aggregation&quot; tabindex=&quot;-1&quot;&gt;Data aggregation&lt;/h3&gt;

&lt;p&gt;It is essential that you report the measurements collected by &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;web-vitals&lt;/code&gt;. If this data is measured but not reported, you&#39;ll never see it. The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;web-vitals&lt;/code&gt; documentation includes examples showing how to send the data to &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals#send-the-results-to-an-analytics-endpoint&quot;&gt;a generic API endpoint&lt;/a&gt;, &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals#send-the-results-to-google-analytics&quot;&gt;Google Analytics&lt;/a&gt;, or &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals#send-the-results-to-google-tag-manager&quot;&gt;Google Tag Manager&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you already have a favorite reporting tool, consider using that. If not, Google Analytics is free and can be used for this purpose.&lt;/p&gt;

&lt;p&gt;When considering which tool to use, it is helpful to think about who will need to have access to the data. Businesses typically achieve the biggest performance wins when the whole company, rather than a single department, is interested in improving performance. See &lt;a href=&quot;https://web.dev/articles/fixing-website-speed-cross-functionally&quot;&gt;Fixing website speed cross-functionally&lt;/a&gt; to learn how to get buy-in from different departments.&lt;/p&gt;

&lt;h3 id=&quot;data_interpretation&quot; data-text=&quot;Data interpretation&quot; tabindex=&quot;-1&quot;&gt;Data interpretation&lt;/h3&gt;

&lt;p&gt;When analyzing performance data, it&#39;s important to pay attention to the tails of the distribution. RUM data often reveals that performance varies widely—some users have fast experiences, others have slow experiences. However, using the median to summarize data can mask this behavior.&lt;/p&gt;

&lt;p&gt;With regards to Web Vitals, Google uses the percentage of &quot;good&quot; experiences, rather than statistics like medians or averages, to determine whether a site or page meets the recommended thresholds. Specifically, for a site or page to be considered as meeting the Core Web Vitals thresholds, 75% of page visits should meet the &quot;good&quot; threshold for each metric.&lt;/p&gt;

&lt;h2 id=&quot;measure_web_vitals_using_lab_data&quot; data-text=&quot;Measure Web Vitals using lab data&quot; tabindex=&quot;-1&quot;&gt;Measure Web Vitals using lab data&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://web.dev/articles/user-centric-performance-metrics#in_the_lab&quot;&gt;Lab data&lt;/a&gt;, also known as synthetic data, is collected from a controlled environment, rather than actual users. Unlike RUM data, lab data can be collected from pre-production environments and therefore incorporated into developer workflows and continuous integration processes. Examples of tools that collect synthetic data are Lighthouse and WebPageTest.&lt;/p&gt;

&lt;h3 id=&quot;considerations&quot; data-text=&quot;Considerations&quot; tabindex=&quot;-1&quot;&gt;Considerations&lt;/h3&gt;

&lt;p&gt;There will always be discrepancies between RUM data and lab data—particularly if the network conditions, device type, or location of the lab environment differs significantly from that of users. However, when it comes to collecting lab data on Web Vitals metrics in particular, there are a couple specific considerations that are important to note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/articles/lcp&quot;&gt;&lt;strong&gt;Largest Contentful Paint (LCP)&lt;/strong&gt;&lt;/a&gt; measured in lab environments can be different to those measured in the field with RUM data due to delays in loading the page (through redirects, latency connecting to the server, or uncached data), different content shown to different users depending on screen, or due to other reasons (including, cookie banners, personalization).&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/articles/cls&quot;&gt;&lt;strong&gt;Cumulative Layout Shift (CLS)&lt;/strong&gt;&lt;/a&gt; measured in lab environments can be artificially lower than the CLS observed in RUM data. Many lab tools only load the page—they don&#39;t interact with it. As a result, they only capture layout shifts that occur during initial page load. By contrast, the CLS measured by RUM tools captures &lt;a href=&quot;https://web.dev/articles/cls#expected_vs_unexpected_layout_shifts&quot;&gt;unexpected layout shifts&lt;/a&gt; that occur throughout the entire lifespan of the page.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/articles/inp&quot;&gt;&lt;strong&gt;Interaction to Next Paint (INP)&lt;/strong&gt;&lt;/a&gt; can&#39;t be measured in lab environments because it requires user interactions with the page. As a result, &lt;a href=&quot;https://web.dev/articles/tbt&quot;&gt;Total Blocking Time&lt;/a&gt; (TBT) is the recommended lab proxy for INP. TBT measures the &quot;total amount of time between First Contentful Paint and Time to Interactive during which the page is blocked from responding to user input&quot;. Although INP and TBT are calculated differently, they are both reflections of a blocked main thread during the bootstrap process. When the main thread is blocked, the browser is delayed in responding to user interactions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;tooling&quot; data-text=&quot;Tooling&quot; tabindex=&quot;-1&quot;&gt;Tooling&lt;/h3&gt;

&lt;p&gt;These tools can be used to gather lab measurements of Web Vitals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/devtools/performance/overview&quot;&gt;&lt;strong&gt;Chrome DevTools&lt;/strong&gt;&lt;/a&gt; measures and reports the Core Web Vitals for a given page in the live metrics view of the Performance panel. This view provides developers with real-time performance feedback as they make code changes.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/performance-scoring&quot;&gt;&lt;strong&gt;Lighthouse&lt;/strong&gt;&lt;/a&gt; Lighthouse reports on LCP, CLS, and TBT, and also highlights possible performance improvements. Lighthouse is available in Chrome DevTools, as an &lt;a href=&quot;https://www.npmjs.com/package/lighthouse&quot;&gt;npm package&lt;/a&gt;, and can also be incorporated into continuous integration workflows using &lt;a href=&quot;https://github.com/GoogleChrome/lighthouse-ci&quot;&gt;Lighthouse CI&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://webpagetest.org/&quot;&gt;&lt;strong&gt;WebPageTest&lt;/strong&gt;&lt;/a&gt; includes Web Vitals as a part of its standard reporting. WebPageTest is useful for gathering information on Web Vitals under particular device and network conditions.&lt;/li&gt;
&lt;/ul&gt;

  

  
</description><link>https://web.dev/articles/vitals-measurement-getting-started?hl=en</link><guid isPermaLink="false">https://web.dev/articles/vitals-measurement-getting-started?hl=en</guid><pubDate>Tue, 09 Sep 2025 07:00:00 GMT</pubDate></item><item><title>Largest Contentful Paint (LCP)</title><description>&lt;p&gt;&lt;style&gt;
  .wd-author__links {
    display: flex;
  }
  &lt;/style&gt;

&lt;/p&gt;&lt;p&gt;&lt;/p&gt;

&lt;p&gt;
&lt;/p&gt;&lt;p class=&quot;wd-pubdates&quot; style=&quot;font-size: 90%; border-bottom: 1px solid var(--devsite-ref-palette--grey200);color: var(--devsite-ref-palette--blue-grey700);&quot;&gt;
  Published: August 8, 2019, Last updated: September 4, 2025
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;devsite-iframe&gt;&lt;iframe src=&quot;https://web.dev/frame/articles/lcp/index_fc174ece9295892c986c1d0e5ea272f470d00359d878fb28dccac92a0ee20118.frame&quot; class=&quot;framebox inherit-locale &quot; allow=&quot;clipboard-write https://web-dot-devsite-v2-prod-3p.appspot.com&quot; allowfullscreen=&quot;&quot; is-upgraded=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;&lt;/devsite-iframe&gt;&lt;/p&gt;

&lt;p&gt;Largest Contentful Paint (LCP) is an important, &lt;a href=&quot;https://web.dev/articles/vitals#lifecycle&quot;&gt;stable&lt;/a&gt; Core Web Vital metric for measuring &lt;a href=&quot;https://web.dev/articles/user-centric-performance-metrics#types_of_metrics&quot;&gt;perceived load speed&lt;/a&gt; because it marks the point in the page load timeline when the page&#39;s main content has likely loaded—a fast LCP helps reassure the user that the page is &lt;a href=&quot;https://web.dev/articles/user-centric-performance-metrics#defining_metrics%22&quot;&gt;useful&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Historically, it&#39;s been a challenge for web developers to measure how quickly the main content of a web page loads and is visible to users. Older metrics like &lt;a href=&quot;https://developer.mozilla.org/docs/Web/Events/load&quot;&gt;load&lt;/a&gt; or &lt;a href=&quot;https://developer.mozilla.org/docs/Web/Events/DOMContentLoaded&quot;&gt;DOMContentLoaded&lt;/a&gt; don&#39;t work well because they don&#39;t necessarily correspond to what the user sees on their screen. And newer, user-centric performance metrics like &lt;a href=&quot;https://web.dev/articles/fcp&quot;&gt;First Contentful Paint (FCP)&lt;/a&gt; only capture the very beginning of the loading experience. If a page shows a splash screen or displays a loading indicator, this moment isn&#39;t very relevant to the user.&lt;/p&gt;

&lt;p&gt;In the past, we&#39;ve recommended performance metrics like &lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/first-meaningful-paint&quot;&gt;First Meaningful Paint (FMP)&lt;/a&gt; and &lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/speed-index&quot;&gt;Speed Index (SI)&lt;/a&gt; (both available in Lighthouse) to help capture more of the loading experience after the initial paint, but these metrics are complex, hard to explain, and often wrong—meaning they still don&#39;t identify when the main content of the page has loaded.&lt;/p&gt;

&lt;p&gt;Based on discussions in the &lt;a href=&quot;https://www.w3.org/webperf/&quot;&gt;W3C Web Performance Working Group&lt;/a&gt; and research done at Google, we&#39;ve found that a more accurate way to measure when the main content of a page is loaded is to look at when the largest element is rendered.&lt;/p&gt;

&lt;h2 id=&quot;what-is-lcp&quot; data-text=&quot;What is LCP?&quot; tabindex=&quot;-1&quot;&gt;What is LCP?&lt;/h2&gt;

&lt;p&gt;LCP reports the render time of the largest &lt;a href=&quot;https://web.dev/articles/lcp?hl=en#what-elements-are-considered&quot;&gt;image, text block, or video&lt;/a&gt; visible in the viewport, relative to when the user first navigated to the page.&lt;/p&gt;

&lt;aside class=&quot;key-point&quot;&gt;
  &lt;b&gt;Key Point&lt;/b&gt;: It is important to note that LCP includes any unload time from the previous page, connection set up time, redirect time, and other &lt;a href=&quot;https://web.dev/articles/ttfb&quot;&gt;Time To First Byte (TTFB)&lt;/a&gt; delays which can be significant when measured in the field and can lead to differences between field and lab measurements.
&lt;/aside&gt;

&lt;h3 id=&quot;what-is-a-good-lcp-score&quot; data-text=&quot;What is a good LCP score?&quot; tabindex=&quot;-1&quot;&gt;What is a good LCP score?&lt;/h3&gt;

&lt;p&gt;To provide a good user experience, sites should strive to have Largest Contentful Paint of &lt;strong&gt;2.5 seconds&lt;/strong&gt; or less. To ensure you&#39;re hitting this target for most of your users, a good threshold to measure is the &lt;strong&gt;75th percentile&lt;/strong&gt; of page loads, segmented across mobile and desktop devices.&lt;/p&gt;

&lt;figure&gt;
  &lt;picture&gt;
    &lt;source srcset=&quot;https://web.dev/static/articles/lcp/image/good-lcp-values.svg&quot; media=&quot;(min-width: 640px)&quot; width=&quot;800&quot; height=&quot;200&quot;&gt;
    &lt;img src=&quot;https://web.dev/static/articles/lcp/image/good-lcp-values-are-25-s-28836be83d1aa.svg&quot; alt=&quot;Good LCP values are 2.5 seconds or less, poor values are greater than 4.0 seconds, and anything in between needs improvement&quot; width=&quot;640&quot; height=&quot;480&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;/picture&gt;
  &lt;figcaption class=&quot;wd-caption&quot;&gt;
    A good LCP value is 2.5 seconds or less.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;To learn more about the research and methodology behind this recommendation, see: &lt;a href=&quot;https://web.dev/articles/defining-core-web-vitals-thresholds&quot;&gt;Defining the Core Web Vitals metrics thresholds&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;what-elements-are-considered&quot; data-text=&quot;What elements are considered?&quot; tabindex=&quot;-1&quot;&gt;What elements are considered?&lt;/h3&gt;

&lt;p&gt;As specified in the &lt;a href=&quot;https://wicg.github.io/largest-contentful-paint/&quot;&gt;Largest Contentful Paint
API&lt;/a&gt;, the types of elements
considered for Largest Contentful Paint are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;img&amp;gt;&lt;/code&gt; elements (the &lt;a href=&quot;https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/speed/metrics_changelog/2023_08_lcp.md&quot;&gt;first frame presentation time&lt;/a&gt; is used for animated content such as GIFs or animated PNGs)&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;image&amp;gt;&lt;/code&gt; elements inside an &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;svg&amp;gt;&lt;/code&gt; element&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;video&amp;gt;&lt;/code&gt; elements (the poster image load time or &lt;a href=&quot;https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/speed/metrics_changelog/2023_08_lcp.md&quot;&gt;first frame presentation time&lt;/a&gt; for videos is used—whichever is earlier)&lt;/li&gt;
&lt;li&gt;An element with a background image loaded using the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/url()&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;url()&lt;/code&gt;&lt;/a&gt; function, (as opposed to a &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/CSS_Images/Using_CSS_gradients&quot;&gt;CSS gradient&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTML/Block-level_elements&quot;&gt;Block-level&lt;/a&gt; elements containing text nodes or other inline-level text element children.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that restricting the elements to this limited set was intentional in order to reduce complexity. Additional elements (like the full &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;svg&amp;gt;&lt;/code&gt; support) may be added in the future as more research is conducted.&lt;/p&gt;

&lt;div class=&quot;nl-signup attempt-right&quot;&gt;&lt;a href=&quot;https://web.dev/newsletter?nl_cta=true&quot;&gt;&lt;img src=&quot;https://web.dev/static/images/newsletter/opt2_square.png&quot; height=&quot;480&quot; width=&quot;480&quot; alt=&quot;Sign up for the web.dev newsletter.&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/a&gt;&lt;/div&gt;

&lt;p&gt;As well as only considering some elements, LCP measurements use heuristics to exclude certain elements that users are likely to see as &quot;non-contentful&quot;. For Chromium-based browsers, these include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Elements with an opacity of 0, that are invisible to the user&lt;/li&gt;
&lt;li&gt;Elements that cover the full viewport, that are likely considered as background rather than content&lt;/li&gt;
&lt;li&gt;Placeholder images or other images with a low entropy, that likely don&#39;t reflect the true content of the page&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Browsers are likely to continue to improve these heuristics to ensure we match user expectations of what the largest &lt;em&gt;contentful&lt;/em&gt; element is.&lt;/p&gt;

&lt;p&gt;These &quot;contentful&quot; heuristics may differ from those used by &lt;a href=&quot;https://web.dev/articles/fcp&quot;&gt;First Contentful Paint (FCP)&lt;/a&gt;, which may consider some of these elements, such as placeholder images or full viewport images, even if they are ineligible to be LCP candidates. Despite both using &quot;contentful&quot; in their name, the aim of these metrics is different. FCP measures when &lt;em&gt;any content&lt;/em&gt; is painted to screen and LCP when the &lt;em&gt;main content&lt;/em&gt; is painted so LCP is intented to be more selective.&lt;/p&gt;

&lt;h3 id=&quot;how-is-element-size-determined&quot; data-text=&quot;How is an element&#39;s size determined?&quot; tabindex=&quot;-1&quot;&gt;How is an element&#39;s size determined?&lt;/h3&gt;

&lt;p&gt;The size of the element reported for LCP is typically the size that&#39;s visible to the user within the viewport. If the element extends outside of the viewport, or if any of the element is clipped or has non-visible &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/overflow&quot;&gt;overflow&lt;/a&gt;, those portions don&#39;t count toward the element&#39;s size.&lt;/p&gt;

&lt;p&gt;For image elements that have been resized from their &lt;a href=&quot;https://developer.mozilla.org/docs/Glossary/Intrinsic_Size&quot;&gt;intrinsic size&lt;/a&gt;, the size that gets reported is either the visible size or the intrinsic size, whichever is smaller.&lt;/p&gt;

&lt;p&gt;For text elements, LCP considers only the smallest rectangle that can contain all text nodes.&lt;/p&gt;

&lt;p&gt;For all elements, LCP doesn&#39;t consider margins, paddings, or borders applied using CSS.&lt;/p&gt;

&lt;aside class=&quot;note&quot;&gt;
  &lt;b&gt;Note:&lt;/b&gt; Determining which text nodes belong to which elements can sometimes be tricky, especially for elements whose children includes inline elements and plain text nodes but also block-level elements. The key point is that every text node belongs to (and only to) its closest block-level ancestor element. In &lt;a href=&quot;https://wicg.github.io/element-timing/#set-of-owned-text-nodes&quot;&gt;spec terms&lt;/a&gt;: each text node belongs to the element that generates its &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/Containing_block&quot;&gt;containing block&lt;/a&gt;.
&lt;/aside&gt;

&lt;h3 id=&quot;when-is-lcp-reported&quot; data-text=&quot;When is LCP reported?&quot; tabindex=&quot;-1&quot;&gt;When is LCP reported?&lt;/h3&gt;

&lt;p&gt;Web pages often load in stages, and as a result, it&#39;s possible that the largest element on the page might change.&lt;/p&gt;

&lt;p&gt;To handle this potential for change, the browser dispatches a &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/PerformanceEntry&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;PerformanceEntry&lt;/code&gt;&lt;/a&gt; of type &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;largest-contentful-paint&lt;/code&gt; identifying the largest contentful element as soon as the browser has painted the first frame. But then, after rendering subsequent frames, it will dispatch another &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/PerformanceEntry&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;PerformanceEntry&lt;/code&gt;&lt;/a&gt; any time the largest contentful element changes.&lt;/p&gt;

&lt;p&gt;For example, on a page with text and a hero image the browser may initially just render the text—at which point the browser would dispatch a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;largest-contentful-paint&lt;/code&gt; entry whose &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;element&lt;/code&gt; property would likely reference a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;p&amp;gt;&lt;/code&gt; or &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;h1&amp;gt;&lt;/code&gt;. Later, once the hero image finishes loading, a second &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;largest-contentful-paint&lt;/code&gt; entry would be dispatched and its &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;element&lt;/code&gt; property would reference the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;img&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;An element can only be considered the largest contentful element after it has rendered and is visible to the user. Images that haven&#39;t yet loaded aren&#39;t considered &quot;rendered&quot;. Neither are text nodes using web fonts during the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/@font-face/font-display#The_font_display_timeline&quot;&gt;font block period&lt;/a&gt;. In such cases, a smaller element might be reported as the largest contentful element, but as soon as the larger element finishes rendering, another &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;PerformanceEntry&lt;/code&gt; is created.&lt;/p&gt;

&lt;p&gt;In addition to late-loading images and fonts, a page may add new elements to the DOM as new content becomes available. If any of these new elements is larger than the previous largest contentful element, a new &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;PerformanceEntry&lt;/code&gt; will also be reported.&lt;/p&gt;

&lt;p&gt;If the largest contentful element is removed from the viewport, or even from the DOM, it remains the largest contentful element unless a larger element is rendered.&lt;/p&gt;

&lt;aside class=&quot;note&quot; id=&quot;stop-on-interaction&quot;&gt;
  &lt;b&gt;Note:&lt;/b&gt; The browser will stop reporting new entries as soon as the user interacts with the page (via a tap, scroll, or keypress), as user interaction often changes what&#39;s visible to the user (which is especially true with scrolling).
&lt;/aside&gt;

&lt;p&gt;For analysis purposes, you should only report the most recently dispatched &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;PerformanceEntry&lt;/code&gt; to your analytics service.&lt;/p&gt;

&lt;aside class=&quot;caution&quot;&gt;
  &lt;b&gt;Caution:&lt;/b&gt; Since users can open pages in a background tab, it&#39;s possible that &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;largest-contentful-paint&lt;/code&gt; entries won&#39;t be dispatched until the user focuses the tab, which can be much later than when they first loaded it.  Google tools that measure LCP don&#39;t report this metric if the page was loaded in the background, as it does not reflect the user-perceived load time.
&lt;/aside&gt;

&lt;h4 id=&quot;load-render-time&quot; data-text=&quot;Load time versus render time&quot; tabindex=&quot;-1&quot;&gt;Load time versus render time&lt;/h4&gt;

&lt;p&gt;For security reasons, the render timestamp of images was not originally exposed for cross-origin images that lack the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Headers/Timing-Allow-Origin&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;Timing-Allow-Origin&lt;/code&gt;&lt;/a&gt; header. Instead, only their load time was exposed (since this is already exposed through many other web APIs).&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;load time&lt;/em&gt; will usually be slightly after the resource download ends (&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/responseEnd&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;responseEnd&lt;/code&gt; in Resource Timing&lt;/a&gt;) as it takes time for the browser to process the resource after the download completes, even before it starts to render it. However, where the LCP resource is preloaded, or the rendering is delayed, there can be a larger gap between the load time and the render time.&lt;/p&gt;

&lt;p&gt;This can lead to the seemingly impossible situation where LCP is reported by web APIs as earlier than FCP. This is not the case but only appears so due to this security restriction.&lt;/p&gt;

&lt;p&gt;This was &lt;a href=&quot;https://github.com/w3c/paint-timing/issues/104&quot;&gt;resolved in late 2024&lt;/a&gt; and a slightly coarsened render time &lt;a href=&quot;https://chromestatus.com/feature/5128261284397056&quot;&gt;is available from Chrome 133&lt;/a&gt; even when &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;Timing-Allow-Origin&lt;/code&gt; is not provided.&lt;/p&gt;

&lt;p&gt;When possible, it&#39;s still recommended to set the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;Timing-Allow-Origin&lt;/code&gt; header, so your metrics will be more accurate, particular for browsers that don&#39;t include this recent change.&lt;/p&gt;

&lt;h3 id=&quot;how-are-element-layout-size-changes-handled&quot; data-text=&quot;How are element layout and size changes handled?&quot; tabindex=&quot;-1&quot;&gt;How are element layout and size changes handled?&lt;/h3&gt;

&lt;p&gt;To keep the performance overhead of calculating and dispatching new performance entries low, changes to an element&#39;s size or position don&#39;t generate new LCP candidates. Only the element&#39;s initial size and position in the viewport is considered.&lt;/p&gt;

&lt;p&gt;This means images that are initially rendered off-screen and then transition on-screen may not be reported. It also means elements initially rendered in the viewport that then get pushed down, out of view will still report their initial, in-viewport size.&lt;/p&gt;

&lt;h3 id=&quot;examples&quot; data-text=&quot;Examples&quot; tabindex=&quot;-1&quot;&gt;Examples&lt;/h3&gt;

&lt;p&gt;Here are some examples of when the Largest Contentful Paint occurs on a few popular websites:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/lcp/image/largest-contentful-paint-fc43128e011aa.png&quot; alt=&quot;Largest Contentful Paint timeline from cnn.com&quot; width=&quot;800&quot; height=&quot;311&quot; srcset=&quot;https://web.dev/static/articles/lcp/image/largest-contentful-paint-fc43128e011aa_36.png 36w,https://web.dev/static/articles/lcp/image/largest-contentful-paint-fc43128e011aa_48.png 48w,https://web.dev/static/articles/lcp/image/largest-contentful-paint-fc43128e011aa_72.png 72w,https://web.dev/static/articles/lcp/image/largest-contentful-paint-fc43128e011aa_96.png 96w,https://web.dev/static/articles/lcp/image/largest-contentful-paint-fc43128e011aa_480.png 480w,https://web.dev/static/articles/lcp/image/largest-contentful-paint-fc43128e011aa_720.png 720w,https://web.dev/static/articles/lcp/image/largest-contentful-paint-fc43128e011aa_856.png 856w,https://web.dev/static/articles/lcp/image/largest-contentful-paint-fc43128e011aa_960.png 960w,https://web.dev/static/articles/lcp/image/largest-contentful-paint-fc43128e011aa_1440.png 1440w,https://web.dev/static/articles/lcp/image/largest-contentful-paint-fc43128e011aa_1920.png 1920w,https://web.dev/static/articles/lcp/image/largest-contentful-paint-fc43128e011aa_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption class=&quot;wd-caption&quot;&gt;
    An LCP timeline from cnn.com.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/lcp/image/largest-contentful-paint-3713e2f14970a.png&quot; alt=&quot;Largest Contentful Paint timeline from techcrunch.com&quot; width=&quot;800&quot; height=&quot;311&quot; srcset=&quot;https://web.dev/static/articles/lcp/image/largest-contentful-paint-3713e2f14970a_36.png 36w,https://web.dev/static/articles/lcp/image/largest-contentful-paint-3713e2f14970a_48.png 48w,https://web.dev/static/articles/lcp/image/largest-contentful-paint-3713e2f14970a_72.png 72w,https://web.dev/static/articles/lcp/image/largest-contentful-paint-3713e2f14970a_96.png 96w,https://web.dev/static/articles/lcp/image/largest-contentful-paint-3713e2f14970a_480.png 480w,https://web.dev/static/articles/lcp/image/largest-contentful-paint-3713e2f14970a_720.png 720w,https://web.dev/static/articles/lcp/image/largest-contentful-paint-3713e2f14970a_856.png 856w,https://web.dev/static/articles/lcp/image/largest-contentful-paint-3713e2f14970a_960.png 960w,https://web.dev/static/articles/lcp/image/largest-contentful-paint-3713e2f14970a_1440.png 1440w,https://web.dev/static/articles/lcp/image/largest-contentful-paint-3713e2f14970a_1920.png 1920w,https://web.dev/static/articles/lcp/image/largest-contentful-paint-3713e2f14970a_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption class=&quot;wd-caption&quot;&gt;
    An LCP timeline from techcrunch.com.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;In both of the timelines, the largest element changes as content loads. In the first example, new content is added to the DOM and that changes what element is the largest. In the second example, the layout changes and content that was previously the largest is removed from the viewport.&lt;/p&gt;

&lt;p&gt;While it&#39;s often the case that late-loading content is larger than content already on the page, that&#39;s not necessarily the case. The next two examples show the LCP occurring before the page fully loads.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/lcp/image/largest-contentful-paint-9bc403e812154.png&quot; alt=&quot;Largest Contentful Paint timeline from instagram.com&quot; width=&quot;800&quot; height=&quot;311&quot; srcset=&quot;https://web.dev/static/articles/lcp/image/largest-contentful-paint-9bc403e812154_36.png 36w,https://web.dev/static/articles/lcp/image/largest-contentful-paint-9bc403e812154_48.png 48w,https://web.dev/static/articles/lcp/image/largest-contentful-paint-9bc403e812154_72.png 72w,https://web.dev/static/articles/lcp/image/largest-contentful-paint-9bc403e812154_96.png 96w,https://web.dev/static/articles/lcp/image/largest-contentful-paint-9bc403e812154_480.png 480w,https://web.dev/static/articles/lcp/image/largest-contentful-paint-9bc403e812154_720.png 720w,https://web.dev/static/articles/lcp/image/largest-contentful-paint-9bc403e812154_856.png 856w,https://web.dev/static/articles/lcp/image/largest-contentful-paint-9bc403e812154_960.png 960w,https://web.dev/static/articles/lcp/image/largest-contentful-paint-9bc403e812154_1440.png 1440w,https://web.dev/static/articles/lcp/image/largest-contentful-paint-9bc403e812154_1920.png 1920w,https://web.dev/static/articles/lcp/image/largest-contentful-paint-9bc403e812154_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption class=&quot;wd-caption&quot;&gt;
    An LCP timeline from instagram.com.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;In this example, the Instagram logo is loaded relatively early and it remains the largest element even as other content is progressively shown.&lt;/p&gt;

&lt;aside class=&quot;note&quot;&gt;
  &lt;b&gt;Note:&lt;/b&gt; In the first frame of the Instagram timeline, you may notice the camera logo does not have a green box around it. That&#39;s because it&#39;s an &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;svg&amp;gt;&lt;/code&gt; element, and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;svg&amp;gt;&lt;/code&gt; elements are not considered LCP candidates at this time (though &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;img src=&quot;...svg&quot;&amp;gt;&lt;/code&gt; elements are, as are individual &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;image&amp;gt;&lt;/code&gt; elements within &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;svg&amp;gt;&lt;/code&gt; elements). The first LCP candidate is the text in the second frame.
&lt;/aside&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/lcp/image/largest-contentful-paint-6c5554de0eac7.png&quot; alt=&quot;Largest Contentful Paint timeline from google.com&quot; width=&quot;800&quot; height=&quot;311&quot; srcset=&quot;https://web.dev/static/articles/lcp/image/largest-contentful-paint-6c5554de0eac7_36.png 36w,https://web.dev/static/articles/lcp/image/largest-contentful-paint-6c5554de0eac7_48.png 48w,https://web.dev/static/articles/lcp/image/largest-contentful-paint-6c5554de0eac7_72.png 72w,https://web.dev/static/articles/lcp/image/largest-contentful-paint-6c5554de0eac7_96.png 96w,https://web.dev/static/articles/lcp/image/largest-contentful-paint-6c5554de0eac7_480.png 480w,https://web.dev/static/articles/lcp/image/largest-contentful-paint-6c5554de0eac7_720.png 720w,https://web.dev/static/articles/lcp/image/largest-contentful-paint-6c5554de0eac7_856.png 856w,https://web.dev/static/articles/lcp/image/largest-contentful-paint-6c5554de0eac7_960.png 960w,https://web.dev/static/articles/lcp/image/largest-contentful-paint-6c5554de0eac7_1440.png 1440w,https://web.dev/static/articles/lcp/image/largest-contentful-paint-6c5554de0eac7_1920.png 1920w,https://web.dev/static/articles/lcp/image/largest-contentful-paint-6c5554de0eac7_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption class=&quot;wd-caption&quot;&gt;
    An LCP timeline from google.com.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;In this Google Search results page example, the largest element is a paragraph of text that is displayed before any of the images or logo finish loading. Since all the individual images are smaller than this paragraph, it remains the largest element throughout the load process.&lt;/p&gt;

&lt;h2 id=&quot;how-to-measure-lcp&quot; data-text=&quot;How to measure LCP&quot; tabindex=&quot;-1&quot;&gt;How to measure LCP&lt;/h2&gt;

&lt;p&gt;LCP can be measured &lt;a href=&quot;https://web.dev/articles/user-centric-performance-metrics#in_the_lab&quot;&gt;in the lab&lt;/a&gt; or &lt;a href=&quot;https://web.dev/articles/user-centric-performance-metrics#in_the_field&quot;&gt;in the field&lt;/a&gt;, and it&#39;s available in the following tools:&lt;/p&gt;

&lt;h3 id=&quot;field-tools&quot; data-text=&quot;Field tools&quot; tabindex=&quot;-1&quot;&gt;Field tools&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/crux&quot;&gt;Chrome User Experience Report&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pagespeed.web.dev/&quot;&gt;PageSpeed Insights&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://support.google.com/webmasters/answer/9205520&quot;&gt;Search Console (Core Web Vitals report)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/GoogleChrome/web-vitals&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;web-vitals&lt;/code&gt; JavaScript library&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;lab-tools&quot; data-text=&quot;Lab tools&quot; tabindex=&quot;-1&quot;&gt;Lab tools&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/devtools&quot;&gt;Chrome DevTools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/overview&quot;&gt;Lighthouse&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pagespeed.web.dev/&quot;&gt;PageSpeed Insights&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://webpagetest.org/&quot;&gt;WebPageTest&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;measure-in-javascript&quot; data-text=&quot;Measure LCP in JavaScript&quot; tabindex=&quot;-1&quot;&gt;Measure LCP in JavaScript&lt;/h3&gt;

&lt;p&gt;To measure LCP in JavaScript, you can use the &lt;a href=&quot;https://wicg.github.io/largest-contentful-paint/&quot;&gt;Largest Contentful Paint API&lt;/a&gt;. The following example shows how to create a &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/PerformanceObserver&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;PerformanceObserver&lt;/code&gt;&lt;/a&gt; that listens for &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;largest-contentful-paint&lt;/code&gt; entries and logs them to the console.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JavaScript&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-ow&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;PerformanceObserver&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;entryList&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&amp;gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;entryList&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;getEntries&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;())&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;LCP candidate:&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;startTime&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}).&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;largest-contentful-paint&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;buffered&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;aside class=&quot;warning&quot;&gt;
  &lt;b&gt;Warning:&lt;/b&gt; This code shows how to log &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;largest-contentful-paint&lt;/code&gt; entries to the console, but measuring LCP in JavaScript is more complicated. For details, see &lt;a href=&quot;https://web.dev/articles/lcp?hl=en#differences-metric-api&quot;&gt;Differences between the Metric and the API&lt;/a&gt;.
&lt;/aside&gt;

&lt;p&gt;In the previous example, each logged &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;largest-contentful-paint&lt;/code&gt; entry represents the current LCP candidate. In general, the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;startTime&lt;/code&gt; value of the last entry emitted is the LCP value—however, that is not always the case. Not all &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;largest-contentful-paint&lt;/code&gt; entries are valid for measuring LCP.&lt;/p&gt;

&lt;p&gt;The following section lists the differences between what the API reports and how the metric is calculated.&lt;/p&gt;

&lt;h4 id=&quot;differences-metric-api&quot; data-text=&quot;Differences between the metric and the API&quot; tabindex=&quot;-1&quot;&gt;Differences between the metric and the API&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;The API will dispatch &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;largest-contentful-paint&lt;/code&gt; entries for pages loaded in a background tab, but those pages should be ignored when calculating LCP.&lt;/li&gt;
&lt;li&gt;The API will continue to dispatch &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;largest-contentful-paint&lt;/code&gt; entries after a page has been backgrounded, but those entries should be ignored when calculating LCP (elements may only be considered if the page was in the foreground the entire time).&lt;/li&gt;
&lt;li&gt;The API does not report &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;largest-contentful-paint&lt;/code&gt; entries when the page is restored from the &lt;a href=&quot;https://web.dev/articles/bfcache#impact_on_core_web_vitals&quot;&gt;back/forward cache&lt;/a&gt;, but LCP should be measured in these cases since users experience them as distinct page visits.&lt;/li&gt;
&lt;li&gt;The API does not consider elements within iframes but the metric does as they are part of the user experience of the page. In pages with an LCP within an iframe—for example a poster image on an embedded video—this will &lt;a href=&quot;https://web.dev/articles/crux-and-rum-differences#iframes&quot;&gt;show as a difference between CrUX and RUM&lt;/a&gt;. To properly measure LCP you should consider them. Sub-frames can use the API to report their &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;largest-contentful-paint&lt;/code&gt; entries to the parent frame for aggregation.&lt;/li&gt;
&lt;li&gt;The API measures LCP from navigation start, but for &lt;a href=&quot;https://developer.chrome.com/docs/web-platform/prerender-pages&quot;&gt;prerendered pages&lt;/a&gt; LCP should be measured from &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/PerformanceNavigationTiming/activationStart&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;activationStart&lt;/code&gt;&lt;/a&gt; since that corresponds to the LCP time as experienced by the user.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rather than memorizing all these subtle differences, developers can use the &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;web-vitals&lt;/code&gt; JavaScript library&lt;/a&gt; to measure LCP, which handles these differences for you (where possible—note the iframe issue is not covered):&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JavaScript&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;onLCP&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kr&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;web-vitals&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-c1&quot;&gt;// Measure and log LCP as soon as it&#39;s available.&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-nx&quot;&gt;onLCP&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;Refer to &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals/blob/main/src/onLCP.ts&quot;&gt;the source code for &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;onLCP()&lt;/code&gt;&lt;/a&gt; for a complete example of how to measure LCP in JavaScript.&lt;/p&gt;

&lt;aside class=&quot;note&quot;&gt;
  &lt;b&gt;Note:&lt;/b&gt; In some cases (such as cross-origin iframes) it&#39;s not possible to measure LCP in JavaScript. See the &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals#limitations&quot;&gt;limitations&lt;/a&gt; section of the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;web-vitals&lt;/code&gt; library for details.
&lt;/aside&gt;

&lt;h3 id=&quot;what-if-largest-element-isnt-most-important&quot; data-text=&quot;What if the largest element isn&#39;t the most important?&quot; tabindex=&quot;-1&quot;&gt;What if the largest element isn&#39;t the most important?&lt;/h3&gt;

&lt;p&gt;In some cases the most important element (or elements) on the page is not the same as the largest element, and developers may be more interested in measuring the render times of these other elements instead. This is possible using the &lt;a href=&quot;https://wicg.github.io/element-timing/&quot;&gt;Element Timing API&lt;/a&gt;, as described in the article on &lt;a href=&quot;https://web.dev/articles/custom-metrics#element_timing_api&quot;&gt;custom metrics&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;improve-lcp&quot; data-text=&quot;How to improve LCP&quot; tabindex=&quot;-1&quot;&gt;How to improve LCP&lt;/h2&gt;

&lt;p&gt;A full guide on &lt;a href=&quot;https://web.dev/articles/optimize-lcp&quot;&gt;optimizing LCP&lt;/a&gt; is available to guide you through the process of identifying LCP timings in the field and using lab data to drill down and optimize them.&lt;/p&gt;

&lt;h2 id=&quot;resources&quot; data-text=&quot;Additional resources&quot; tabindex=&quot;-1&quot;&gt;Additional resources&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://youtu.be/ctavZT87syI&quot;&gt;Lessons learned from performance monitoring in Chrome&lt;/a&gt; by &lt;a href=&quot;https://anniesullie.com/&quot;&gt;Annie Sullivan&lt;/a&gt; at &lt;a href=&quot;https://perfnow.nl/&quot;&gt;performance.now()&lt;/a&gt; (2019)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;changelog&quot; data-text=&quot;Changelog&quot; tabindex=&quot;-1&quot;&gt;Changelog&lt;/h2&gt;

&lt;p&gt;Occasionally, bugs are discovered in the APIs used to measure metrics, and sometimes in the definitions of the metrics themselves. As a result, changes must sometimes be made, and these changes can show up as improvements or regressions in your internal reports and dashboards.&lt;/p&gt;

&lt;p&gt;To help you manage this, all changes to either the implementation or definition of these metrics will be surfaced in this &lt;a href=&quot;https://chromium.googlesource.com/chromium/src/+/main/docs/speed/metrics_changelog/lcp.md&quot;&gt;Changelog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you have feedback for these metrics, you can provide it in the &lt;a href=&quot;https://groups.google.com/g/web-vitals-feedback&quot;&gt;web-vitals-feedback Google group&lt;/a&gt;.&lt;/p&gt;
  

  
</description><link>https://web.dev/articles/lcp?hl=en</link><guid isPermaLink="false">https://web.dev/articles/lcp?hl=en</guid><pubDate>Thu, 04 Sep 2025 07:00:00 GMT</pubDate></item><item><title>How to choose your Baseline target</title><description>&lt;p&gt;&lt;style&gt;
  .wd-author__links {
    display: flex;
  }
  &lt;/style&gt;

&lt;/p&gt;

&lt;p class=&quot;wd-pubdates&quot; style=&quot;font-size: 90%; border-bottom: 1px solid var(--devsite-ref-palette--grey200);color: var(--devsite-ref-palette--blue-grey700);&quot;&gt;
  Published: May 20, 2025
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;When a web platform feature is implemented in every browser, it becomes Baseline Newly available. After 30 months, that feature then becomes Baseline Widely available, which is a threshold in which most websites can adopt features without compatibility concerns. This guide explains how to use Baseline, and by using data available from your website&#39;s users, how to select a Baseline target.&lt;/p&gt;

&lt;h2 id=&quot;what_is_a_baseline_target&quot; data-text=&quot;What is a Baseline target?&quot; tabindex=&quot;-1&quot;&gt;What is a Baseline target?&lt;/h2&gt;

&lt;p&gt;A Baseline target is a grouping of web features that developers can choose to support, based on their Baseline status. There are two types of Baseline targets: moving targets and fixed targets.&lt;/p&gt;

&lt;p&gt;Moving targets, such as Baseline Widely available or Baseline Newly available, are targets where the set of features contained may change over time. Moving targets make sense in cases where you want the set of supported features to evolve automatically as new browser versions are released.&lt;/p&gt;

&lt;aside class=&quot;note&quot;&gt;
  &lt;b&gt;Note:&lt;/b&gt; For example, if you choose to adopt Baseline Widely available as your target when developing your site in May 2025, then a feature such as container queries wouldn&#39;t be available within your target. However, if you come back to do some more work in November of 2025, container queries would be a feature within the target and available to use.
&lt;/aside&gt;

&lt;p&gt;Fixed targets are those where the set of features does not change over time. In general, fixed targets are based on calendar years. For example, &lt;a href=&quot;https://web.dev/baseline/2023&quot;&gt;Baseline 2023&lt;/a&gt; is a fixed target that contains the set of web features that became Baseline Newly available in 2023. Baseline 2023 wouldn&#39;t include features that became Baseline after 2023, which means the set of Baseline 2023 features never changes.&lt;/p&gt;

&lt;p&gt;Fixed targets make sense in cases where predictability and determinism are paramount, but they can become out of date over time, so when using fixed targets it&#39;s a good idea to re-evaluate your target regularly.&lt;/p&gt;

&lt;h2 id=&quot;why_choose_a_target&quot; data-text=&quot;Why choose a target?&quot; tabindex=&quot;-1&quot;&gt;Why choose a target?&lt;/h2&gt;

&lt;p&gt;Adoption of features on the web is held back because of compatibility concerns—and that holds the web back from being as good as it could be. Baseline not only brings clarity to the question of feature support in browsers, but it can be helpful in how it clarifies the question of &lt;em&gt;when&lt;/em&gt; you can use certain features. By choosing a target that reflects your audience and requirements you can feel confident in using features within that target group—without needing to check individual features one by one.&lt;/p&gt;

&lt;h2 id=&quot;use_data_to_select_your_baseline_target&quot; data-text=&quot;Use data to select your Baseline target&quot; tabindex=&quot;-1&quot;&gt;Use data to select your Baseline target&lt;/h2&gt;

&lt;p&gt;Knowing the right Baseline target to select should be, when possible, a data-driven decision. When you have the data in front of you, which target to select becomes an easier and much more informed decision.&lt;/p&gt;

&lt;p&gt;If you have &lt;a href=&quot;https://en.wikipedia.org/wiki/Real_user_monitoring&quot;&gt;Real User Monitoring&lt;/a&gt; data for your site, you can learn how Baseline targets map to your users. For example, if you use Google Analytics, a free way to get this information is by using the &lt;a href=&quot;https://baseline-checker.chrome.dev/&quot;&gt;Google Analytics Baseline Checker&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To use this tool, you&#39;ll need to authenticate using your Google Account and allow the tool to have read-only permission to view your Google Analytics data. From there, select the Google Analytics property you want Baseline target data generated for. After completing these steps, you&#39;ll receive output similar to the following:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/how-to-choose-your-baseline-target/image/fig-1.v2.png&quot; width=&quot;800&quot; height=&quot;711&quot; alt=&quot;The Google Analytics Baseline Checker tool showing a breakdown of support for various Baseline targets by percentage. Support increases from newer to older targets.&quot; srcset=&quot;https://web.dev/static/articles/how-to-choose-your-baseline-target/image/fig-1.v2_36.png 36w,https://web.dev/static/articles/how-to-choose-your-baseline-target/image/fig-1.v2_48.png 48w,https://web.dev/static/articles/how-to-choose-your-baseline-target/image/fig-1.v2_72.png 72w,https://web.dev/static/articles/how-to-choose-your-baseline-target/image/fig-1.v2_96.png 96w,https://web.dev/static/articles/how-to-choose-your-baseline-target/image/fig-1.v2_480.png 480w,https://web.dev/static/articles/how-to-choose-your-baseline-target/image/fig-1.v2_720.png 720w,https://web.dev/static/articles/how-to-choose-your-baseline-target/image/fig-1.v2_856.png 856w,https://web.dev/static/articles/how-to-choose-your-baseline-target/image/fig-1.v2_960.png 960w,https://web.dev/static/articles/how-to-choose-your-baseline-target/image/fig-1.v2_1440.png 1440w,https://web.dev/static/articles/how-to-choose-your-baseline-target/image/fig-1.v2_1920.png 1920w,https://web.dev/static/articles/how-to-choose-your-baseline-target/image/fig-1.v2_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption&gt;
    The output of the Google Analytics Baseline Checker tool. The tool segments real user support for each Baseline target. Note how older Baseline targets have greater support amongst real users.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;We&#39;re starting to see other tools implement support for Baseline, that can give you a dynamic view of how much of your audience supports a given target. For example, RUMvision includes a dashboard that shows how much of your audience has support for each Baseline year.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://web.dev/static/articles/how-to-choose-your-baseline-target/image/fig-2.png&quot; width=&quot;800&quot; height=&quot;749&quot; alt=&quot;RUMvision&#39;s Baseline data shows support data for each Baseline target, including a breakdown of feature-level support data.&quot; srcset=&quot;https://web.dev/static/articles/how-to-choose-your-baseline-target/image/fig-2_36.png 36w,https://web.dev/static/articles/how-to-choose-your-baseline-target/image/fig-2_48.png 48w,https://web.dev/static/articles/how-to-choose-your-baseline-target/image/fig-2_72.png 72w,https://web.dev/static/articles/how-to-choose-your-baseline-target/image/fig-2_96.png 96w,https://web.dev/static/articles/how-to-choose-your-baseline-target/image/fig-2_480.png 480w,https://web.dev/static/articles/how-to-choose-your-baseline-target/image/fig-2_720.png 720w,https://web.dev/static/articles/how-to-choose-your-baseline-target/image/fig-2_856.png 856w,https://web.dev/static/articles/how-to-choose-your-baseline-target/image/fig-2_960.png 960w,https://web.dev/static/articles/how-to-choose-your-baseline-target/image/fig-2_1440.png 1440w,https://web.dev/static/articles/how-to-choose-your-baseline-target/image/fig-2_1920.png 1920w,https://web.dev/static/articles/how-to-choose-your-baseline-target/image/fig-2_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;

&lt;h3 id=&quot;what_if_my_analytics_or_rum_provider_doesnt_have_a_baseline_target_report_yet&quot; data-text=&quot;What if my analytics or RUM provider doesn&#39;t have a Baseline target report yet?&quot; tabindex=&quot;-1&quot;&gt;What if my analytics or RUM provider doesn&#39;t have a Baseline target report yet?&lt;/h3&gt;

&lt;p&gt;If you are using an analytics or RUM tool that doesn&#39;t provide a Baseline target report yet but does have data on browser versions, you can join your real-world data with browser version mappings from the &lt;a href=&quot;https://github.com/web-platform-dx/baseline-browser-mapping&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;baseline-browser-mapping&lt;/code&gt;&lt;/a&gt; module. The module provides a Javascript function - &lt;a href=&quot;https://github.com/web-platform-dx/baseline-browser-mapping?tab=readme-ov-file#get-data-for-all-browser-versions&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;getAllVersions()&lt;/code&gt;&lt;/a&gt; - that maps browsers by name and version to their Baseline year and support status for Widely available. These mappings can be provided as arrays, keyed objects or as a CSV. For example, the &lt;a href=&quot;https://github.com/GoogleChromeLabs/baseline-demos/blob/main/tooling/google-analytics-baseline-checker/public/script.js#L270&quot;&gt;Google Analytics Baseline Checker uses this module to join analytics data with Baseline targets&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The outputs of this function are also available as &lt;a href=&quot;https://github.com/web-platform-dx/baseline-browser-mapping?tab=readme-ov-file#static-resources&quot;&gt;hosted JSON or CSV files&lt;/a&gt; which are updated on a daily basis. The &lt;a href=&quot;https://web-platform-dx.github.io/baseline-browser-mapping/with_downstream/all_versions_with_supports.csv&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;all_versions_with_supports.csv&lt;/code&gt;&lt;/a&gt; file contains data that you can match to your analytics providers browser version data with the following fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;browser&lt;/code&gt;: the name of the browser as used in &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;baseline-browser-mapping&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;version&lt;/code&gt;: the version of the browser. Some browsers use only a major version number, others use a &lt;em&gt;major.minor&lt;/em&gt; version number.&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;year&lt;/code&gt;: the Baseline year feature set that this browser version supports. If a browser version was released before Baseline support can be determined in July 2015, this field will contain &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;pre_baseline&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;supports&lt;/code&gt;: this field contains &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;widely&lt;/code&gt; or &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;newly&lt;/code&gt; for browser versions that support those feature sets, and is empty for versions that don&#39;t support either of those feature sets. All browser versions that support Newly available also support Widely available.&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;release_date&lt;/code&gt;: the date this browser version was released, where available.&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;engine&lt;/code&gt;: The engine name for browsers that are downstream of a core Baseline browser. Only Blink-based browsers are included, but other browser engines may be represented in future.&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;engine_version&lt;/code&gt;: the Chromium version that this browser version implements. This is used to determine which Baseline feature set the downstream version supports.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This file is updated frequently as new browser versions are released and the support status of different browsers change. Make sure you refresh the data on a daily basis.&lt;/p&gt;

&lt;h2 id=&quot;what_if_i_dont_have_support_data_from_real_users&quot; data-text=&quot;What if I don&#39;t have support data from real users?&quot; tabindex=&quot;-1&quot;&gt;What if I don&#39;t have support data from real users?&lt;/h2&gt;

&lt;p&gt;You might find yourself in a situation where you can&#39;t get real user data when it comes to features that are Baseline. The good news is that you can get a general idea of support for different Baseline targets through &lt;a href=&quot;https://rumarchive.com/insights/#baseline&quot;&gt;RUM Archive Insights&lt;/a&gt;, even allowing you to filter down to the country level. While this data won&#39;t be specific to your website&#39;s users. It&#39;s a general informational tool that demonstrates the following assumptions are &lt;em&gt;generally&lt;/em&gt; safe:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Newer Baseline targets—such as the current year or the year before—are likely to have the least amount of support among your users. Like any Baseline target, however, they will have better support as time passes.&lt;/li&gt;
&lt;li&gt;Older Baseline targets—particularly Baseline Widely available—will be well-supported. When in doubt, Widely available is an excellent target which evolves as the 30 month window progresses through time.&lt;/li&gt;
&lt;li&gt;Even older Baseline targets—those well beyond the 30 month Widely available window—will have the best support. While Widely available is a good default target, special use cases that require strict SLAs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It&#39;s likely that, even if you&#39;re selecting a Baseline target that is over five years old, you&#39;re able to adopt features that you aren&#39;t using right now. In the best case scenario, you might already be using these features, &lt;a href=&quot;https://web.dev/articles/baseline-and-polyfills&quot;&gt;but with polyfills that you might not need&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;how_do_i_enforce_a_chosen_baseline_target_in_my_project&quot; data-text=&quot;How do I enforce a chosen Baseline target in my project?&quot; tabindex=&quot;-1&quot;&gt;How do I enforce a chosen Baseline target in my project?&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://browsersl.ist/&quot;&gt;Browserslist&lt;/a&gt; is a commonly used method of targeting the browsers you want to support. It&#39;s used in bundlers and other associated tools such as &lt;a href=&quot;https://babeljs.io/&quot;&gt;Babel&lt;/a&gt; and &lt;a href=&quot;https://postcss.org/&quot;&gt;PostCSS&lt;/a&gt; to decide whether certain bits of code need to be transformed or even polyfilled.&lt;/p&gt;

&lt;p&gt;It&#39;s now possible to use Baseline with Browserslist so that, when you select a Baseline target, you can specify it as a valid Browserslist query. This ensures that the tools in your project transform code according to the target you&#39;ve selected. For more information, read &lt;a href=&quot;https://web.dev/articles/use-baseline-with-browserslist&quot;&gt;Use Baseline with Browserslist&lt;/a&gt; for more information.&lt;/p&gt;

&lt;h2 id=&quot;what_about_features_that_dont_meet_my_baseline_target&quot; data-text=&quot;What about features that don&#39;t meet my Baseline target?&quot; tabindex=&quot;-1&quot;&gt;What about features that don&#39;t meet my Baseline target?&lt;/h2&gt;

&lt;p&gt;After selecting a Baseline target, you might have features that you want to use, but don&#39;t fall inside that target. Baseline doesn&#39;t tell you what you should do here, and whether you want to consider using these features depends on the type of website you are building and the expected audience.&lt;/p&gt;

&lt;p&gt;For example, ecommerce or B2B websites may be willing to have a lower support threshold and handle issues as their users support them, whereas government websites may require a high support threshold.
One important rule of thumb here is that &lt;strong&gt;not all web features fail the same way&lt;/strong&gt;. There&#39;s a lot of ways to categorize features on how they fail, but &lt;a href=&quot;https://web.dev/articles/baseline-and-polyfills#when_and_when_not_polyfill_a_missing_feature&quot;&gt;one way&lt;/a&gt; to bucket features that might be useful goes something like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Enhancement:&lt;/strong&gt; If the feature is used in an unsupported browser, the experience is not broken. The experience could possibly be degraded, but may not likely be noticeable to the user. Example: &lt;a href=&quot;https://web.dev/articles/browser-level-image-lazy-loading&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;loading=&quot;lazy&quot;&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Additive:&lt;/strong&gt; The feature provides some additive benefits that may be noticeable—such as changes in page styling or some functionality. The difference may not be noticeable to users if the feature is unsupported, barring comparison in a browser that does support it. Example: &lt;a href=&quot;https://web.dev/articles/css-subgrid&quot;&gt;Subgrid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Critical:&lt;/strong&gt; If the feature is unsupported, the user will have a negative user experience—possibly even one that&#39;s broken altogether. Example: &lt;a href=&quot;https://developer.chrome.com/docs/capabilities/web-apis/file-system-access&quot;&gt;File System Access API&lt;/a&gt; used as a central and necessary feature.&lt;/li&gt;
&lt;/ul&gt;

&lt;aside class=&quot;note&quot;&gt;
  &lt;b&gt;Note:&lt;/b&gt; UK agency &lt;a href=&quot;https://browsersupport.clearleft.com/&quot;&gt;Clearleft use Widely available as their target&lt;/a&gt;, however they consider whether newer features can be used as a progressive enhancement before ruling them out completely.
&lt;/aside&gt;

&lt;p&gt;You might also find that particular features outside of your target have better support than you think. It&#39;s possible to understand how many of your users have support for a particular feature. Can I Use has the ability to check support for individual features against your analytics data. RUMvision also has the ability to drill down and explore feature level data if that&#39;s helpful to you.&lt;/p&gt;

&lt;p&gt;In this way, you can use your Baseline target to reduce the number of features that you need to carefully consider. Everything inside your target you don&#39;t need to worry about. If there are one or two features outside your target that would be especially useful—you have the tools to explore further and decide whether to polyfill or use as a progressive enhancement.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot; data-text=&quot;Conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Every web application has different requirements—from an ecommerce site that can tolerate more incompatibility concerns, to a government website that absolutely must be available and operational to as many users as possible. These are calculations you have to make on your own, and it&#39;s not a goal of Baseline to tell you &lt;em&gt;what&lt;/em&gt; decisions to make when it comes to adopting new web features, but more a matter of &lt;em&gt;how&lt;/em&gt;.&lt;/p&gt;

  

  
</description><link>https://web.dev/articles/how-to-choose-your-baseline-target?hl=en</link><guid isPermaLink="false">https://web.dev/articles/how-to-choose-your-baseline-target?hl=en</guid><pubDate>Tue, 20 May 2025 07:00:00 GMT</pubDate></item><item><title>Help users manage passkeys effectively</title><description>&lt;p&gt;&lt;style&gt;
  .wd-author__links {
    display: flex;
  }
  &lt;/style&gt;

&lt;/p&gt;&lt;p&gt;&lt;/p&gt;

&lt;p&gt;
&lt;/p&gt;&lt;p class=&quot;wd-pubdates&quot; style=&quot;font-size: 90%; border-bottom: 1px solid var(--devsite-ref-palette--grey200);color: var(--devsite-ref-palette--blue-grey700);&quot;&gt;
  Published: May 15, 2025
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Passkeys are rapidly emerging as a more secure, easier, and faster alternative
to passwords, offering enhanced security and user convenience. To
fully realize the potential of passkeys, careful consideration must be given to
the user experience surrounding their management. This document outlines
guidelines and optional features for designing an intuitive, secure, and robust
passkey management system.&lt;/p&gt;

&lt;h2 id=&quot;manage_multiple_passkeys&quot; data-text=&quot;Manage multiple passkeys&quot; tabindex=&quot;-1&quot;&gt;Manage multiple passkeys&lt;/h2&gt;

&lt;p&gt;Let users add multiple passkeys and use more than one provider.
But &lt;a href=&quot;https://web.dev/articles/webauthn-exclude-credentials&quot;&gt;don&#39;t let them add more than one passkey for the same account with the same provider&lt;/a&gt;.
If a user loses access to one provider, such as when the platform doesn&#39;t
support it, or the user loses access to it, they can still sign in with another
passkey from a different provider. This setup lowers the risk of account
lockouts. Make sure your database supports storing multiple passkeys per user.&lt;/p&gt;

&lt;h2 id=&quot;display_a_list_of_registered_passkeys&quot; data-text=&quot;Display a list of registered passkeys&quot; tabindex=&quot;-1&quot;&gt;Display a list of registered passkeys&lt;/h2&gt;

&lt;p&gt;Your website or app should display registered passkeys in a list with key
details to help users manage them effectively. This screenshot illustrates how
such a dedicated passkey management page might look. It shows how a user can
create passkeys across multiple platforms, and provides a centralized place to
manage them.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/passkey-management/image/passkey-management.png&quot; alt=&quot;Example of a passkey management page showing good practices.&quot; width=&quot;60%&quot; srcset=&quot;https://web.dev/static/articles/passkey-management/image/passkey-management_36.png 36w,https://web.dev/static/articles/passkey-management/image/passkey-management_48.png 48w,https://web.dev/static/articles/passkey-management/image/passkey-management_72.png 72w,https://web.dev/static/articles/passkey-management/image/passkey-management_96.png 96w,https://web.dev/static/articles/passkey-management/image/passkey-management_480.png 480w,https://web.dev/static/articles/passkey-management/image/passkey-management_720.png 720w,https://web.dev/static/articles/passkey-management/image/passkey-management_856.png 856w,https://web.dev/static/articles/passkey-management/image/passkey-management_960.png 960w,https://web.dev/static/articles/passkey-management/image/passkey-management_1440.png 1440w,https://web.dev/static/articles/passkey-management/image/passkey-management_1920.png 1920w,https://web.dev/static/articles/passkey-management/image/passkey-management_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption&gt;Example of a passkey management page showing good practices.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Here are some of the common details and features websites and apps can display
about a passkey:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Passkey name&lt;/strong&gt;: Display the passkey name which was given at the time
of registration. Ideally this name matches the passkey provider it was
created on &lt;a href=&quot;https://web.dev/articles/webauthn-aaguid&quot;&gt;based on the AAGUID&lt;/a&gt;.
If no matching passkey provider is found, naming it after the device
information based on the user agent string should be fine.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Passkey provider logo&lt;/strong&gt;: Display the passkey provider logo. This helps
the user identify the passkey they want to manage.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Timestamp of when the passkey was created and used the last time&lt;/strong&gt;:
Recording and displaying the passkey creation timestamp and last usage
timestamp can also help the user identify the passkey they want to manage.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Non-sync indicator&lt;/strong&gt;: Passkeys are synced by default, but the passkey
providers sync capability is still evolving. It&#39;s a common confusion when
a passkey doesn&#39;t sync despite the user&#39;s expectation. Showing a passkey&#39;s
incapability of sync can help users clarify this confusion.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Delete button&lt;/strong&gt;: Allow users to delete the passkey. See &lt;a href=&quot;https://web.dev/articles/passkey-management?hl=en#deleting-a-passkey&quot;&gt;Allow
deleting a passkey&lt;/a&gt; for more
details.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Edit button&lt;/strong&gt;: Many users appreciate being able to rename a passkey.
For example, when there are multiple passkeys from the same passkey
provider but with different provider accounts. Imagine saving multiple
passkeys to different Google Accounts. By allowing the user to rename the
passkey, they can change it to a name they like.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Last sign-in browser, OS or IP address&lt;/strong&gt;: Optionally providing details
about last sign-in helps the user identify suspicious sign-ins. The
browser, OS or the IP address (or location) used to sign-in can be great
information.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;deleting-a-passkey&quot; data-text=&quot;Allow deleting a passkey&quot; tabindex=&quot;-1&quot;&gt;Allow deleting a passkey&lt;/h2&gt;

&lt;p&gt;Allow users to delete a passkey. This helps them tidy up the list, for example,
when a user switches to a new device but the associated passkey is bound to the
older device. It&#39;s also helpful when an attacker hijacks a user account and
creates a passkey for future use.&lt;/p&gt;

&lt;h3 id=&quot;signal_the_updated_list_of_passkeys&quot; data-text=&quot;Signal the updated list of passkeys&quot; tabindex=&quot;-1&quot;&gt;Signal the updated list of passkeys&lt;/h3&gt;

&lt;p&gt;Deleting a passkey removes its credential entry and public key from the server
database. This way, the passkey will disappear from the registered passkey list
and it will appear to the user that the passkey is deleted. However, in reality,
it&#39;s only removed from the server, and the passkey stored to the passkey
provider still remains, which can cause confusion. The next time the user tries
to sign in, the removed passkey will still appear as a sign in option. But,
authenticating with it will fail, because the matching public key is already
deleted from the server.&lt;/p&gt;

&lt;p&gt;To avoid confusion, it&#39;s important to keep the passkey on a passkey provider
and the public key on the server consistent. You can achieve this by
&lt;a href=&quot;https://developer.chrome.com/docs/identity/webauthn-signal-api#signal-a-list-of-saved-credentials&quot;&gt;signaling the updated list of passkeys to the passkey provider&lt;/a&gt;.
If the browser and the passkey provider support the Signal API, they can update
the list of passkeys and delete unnecessary passkeys. If they don&#39;t support
the API, encourage the user to delete the passkey manually.&lt;/p&gt;

&lt;h3 id=&quot;delete_the_last_passkey&quot; data-text=&quot;Delete the last passkey&quot; tabindex=&quot;-1&quot;&gt;Delete the last passkey&lt;/h3&gt;

&lt;p&gt;If a user attempts to delete their last remaining passkey for a given account, make sure they
understand that they will have to sign in with another option with more friction
and potentially lesser protection. If this is their only sign-in method for your site, they
won&#39;t be able to sign in again. Inform users how they can sign in next time,
such as using a backup method if available or prompting them to register another
passkey before proceeding. It&#39;s a good chance to collect feedback why they chose
not to use a passkey.&lt;/p&gt;

&lt;h2 id=&quot;allow_creation_of_new_passkeys&quot; data-text=&quot;Allow creation of new passkeys&quot; tabindex=&quot;-1&quot;&gt;Allow creation of new passkeys&lt;/h2&gt;

&lt;p&gt;While there are opportunities to create passkeys throughout a user&#39;s journey
(like right after sign in), it&#39;s crucial to have a central hub where users can
always go to create new passkeys, delete passkeys and manage passkeys. A passkey
management screen is the best place for that.&lt;/p&gt;

&lt;p&gt;To create a passkey user flow, follow the
&lt;a href=&quot;https://web.dev/articles/passkey-registration&quot;&gt;Create a passkey for passwordless logins&lt;/a&gt;
Developer&#39;s Guide. For advanced security, consider allowing users to create a
passkey on a hardware security token. You can expect users who are willing to
manage passkeys to be more knowledgeable or experienced, so allowing them to
create a passkey on their security key provides improved flexibility.&lt;/p&gt;

&lt;p&gt;To allow saving passkeys to a hardware security token, leave
&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;authenticatorSelection.authenticatorAttachment&lt;/code&gt; unset instead of setting it to
&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&quot;platform&quot;&lt;/code&gt; on a passkey creation request. This way, the browser accepts both
platform (device) and roaming authenticators (a security key) without the user
experience being significantly different from only allowing a platform
authenticator. The option to create a passkey on a security key appears as a
secondary option.&lt;/p&gt;

&lt;h2 id=&quot;checklist&quot; data-text=&quot;Checklist&quot; tabindex=&quot;-1&quot;&gt;Checklist&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Allow users to manage passkeys in a passkey management page.&lt;/li&gt;
&lt;li&gt;Support registering multiple passkeys.&lt;/li&gt;
&lt;li&gt;Allow users to add new and flexible types of passkeys on the management page.&lt;/li&gt;
&lt;li&gt;Display the passkey name.&lt;/li&gt;
&lt;li&gt;Indicate whether a passkey is syncable or non-syncable.&lt;/li&gt;
&lt;li&gt;Allow users to remove a public key from the server.&lt;/li&gt;
&lt;li&gt;Signal the list of passkeys when an associated public key is removed
from the server.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;other_ux_guides&quot; data-text=&quot;Other UX guides&quot; tabindex=&quot;-1&quot;&gt;Other UX guides&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;General passkey UX guides
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.google.com/identity/passkeys/ux/user-journeys&quot;&gt;Passkeys user journeys&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.google.com/identity/passkeys/ux/communicating-passkeys&quot;&gt;Communicating passkeys to users&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.google.com/identity/passkeys/ux/user-interface-design&quot;&gt;Passkeys user interface design&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Android guides
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/design/ui/mobile/guides/patterns/passkeys&quot;&gt;User authentication with passkeys&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

  

  
</description><link>https://web.dev/articles/passkey-management?hl=en</link><guid isPermaLink="false">https://web.dev/articles/passkey-management?hl=en</guid><pubDate>Thu, 15 May 2025 07:00:00 GMT</pubDate></item><item><title>How the Core Web Vitals metrics thresholds were defined</title><description>&lt;p&gt;The research and methodology behind Core Web Vitals thresholds&lt;/p&gt;

&lt;p&gt;&lt;style&gt;
  .wd-author__links {
    display: flex;
  }
  &lt;/style&gt;

&lt;/p&gt;&lt;p&gt;&lt;/p&gt;

&lt;p&gt;
&lt;/p&gt;&lt;p class=&quot;wd-pubdates&quot; style=&quot;font-size: 90%; border-bottom: 1px solid var(--devsite-ref-palette--grey200);color: var(--devsite-ref-palette--blue-grey700);&quot;&gt;
  Published: May 21, 2020, Last updated: May 7, 2025
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://web.dev/articles/vitals#core_web_vitals&quot;&gt;Core Web Vitals&lt;/a&gt; are a set of field metrics that
measure important aspects of real-world user experience on the web. Core Web
Vitals includes metrics, as well as target thresholds for each metric, which
help developers qualitatively understand whether the experience of their site is
&quot;good&quot;, &quot;needs improvement&quot;, or is &quot;poor&quot;. This post will explain the approach used to
choose thresholds for Core Web Vitals metrics generally, as well as how the
thresholds for each specific Core Web Vitals metric were chosen.&lt;/p&gt;

&lt;h2 id=&quot;refresher_core_web_vitals_metrics_and_thresholds&quot; data-text=&quot;Refresher: Core Web Vitals metrics and thresholds&quot; tabindex=&quot;-1&quot;&gt;Refresher: Core Web Vitals metrics and thresholds&lt;/h2&gt;

&lt;p&gt;The Core Web Vitals are three metrics: Largest Contentful Paint (LCP),
Interaction to Next Paint (INP), and Cumulative Layout Shift (CLS). Each metric measures
a different aspect of user experience: LCP measures perceived load speed and
marks the point in the page load timeline when the page&#39;s main content has
likely loaded; INP measures responsiveness and quantifies the experience users
feel when trying to interact with the page; and CLS measures visual
stability and quantifies the amount of unexpected layout shift of visible page
content.&lt;/p&gt;

&lt;p&gt;Each Core Web Vitals metric has associated thresholds, which categorize
performance as either &quot;good&quot;, &quot;needs improvement&quot;, or &quot;poor&quot;:&lt;/p&gt;

&lt;style&gt;
  .cluster &gt; img {
    max-width: 30%;
  }
&lt;/style&gt;

&lt;div class=&quot;cluster&quot;&gt;
  &lt;img src=&quot;https://web.dev/static/articles/defining-core-web-vitals-thresholds/image/largest-contentful-paint-b5777556bded9.svg&quot; alt=&quot;Largest Contentful Paint threshold recommendations&quot; width=&quot;400&quot; height=&quot;350&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;img src=&quot;https://web.dev/static/articles/defining-core-web-vitals-thresholds/image/inp-thresholds.svg&quot; alt=&quot;Interaction to Next Paint threshold recommendations&quot; width=&quot;400&quot; height=&quot;350&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;img src=&quot;https://web.dev/static/articles/defining-core-web-vitals-thresholds/image/cumulative-layout-shift-t-6a49779396c8.svg&quot; alt=&quot;Cumulative Layout Shift threshold recommendations&quot; width=&quot;400&quot; height=&quot;350&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
&lt;/div&gt;

&lt;figure&gt;
  &lt;table&gt;
    &lt;tbody&gt;&lt;tr&gt;
      &lt;th&gt;&amp;nbsp;&lt;/th&gt;
      &lt;th&gt;Good&lt;/th&gt;
      &lt;th&gt;Poor&lt;/th&gt;
      &lt;th&gt;Percentile&lt;/th&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Largest Contentful Paint&lt;/td&gt;
      &lt;td&gt;≤2500 ms&lt;/td&gt;
      &lt;td&gt;&amp;gt;4000 ms&lt;/td&gt;
      &lt;td&gt;75&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Interaction to Next Paint&lt;/td&gt;
      &lt;td&gt;≤200 ms&lt;/td&gt;
      &lt;td&gt;&amp;gt;500 ms&lt;/td&gt;
      &lt;td&gt;75&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; Cumulative Layout Shift&lt;/td&gt;
      &lt;td&gt;≤0.1&lt;/td&gt;
      &lt;td&gt;&amp;gt;0.25&lt;/td&gt;
      &lt;td&gt;75&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;&lt;/table&gt;
  &lt;figcaption&gt;Core Web Vitals thresholds&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Additionally, to classify the overall performance of a page or site, we use the
75th percentile value of all page views to that page or site. In other words, if
at least 75 percent of page views to a site meet the &quot;good&quot; threshold, the site
is classified as having &quot;good&quot; performance for that metric. Conversely, if at
least 25 percent of page views meet the &quot;poor&quot; threshold, the site is classified
as having &quot;poor&quot; performance. So, for example, a 75th percentile LCP of 2
seconds is classified as &quot;good&quot;, while a 75th percentile LCP of 5 seconds is
classified as &quot;poor&quot;.&lt;/p&gt;

&lt;h2 id=&quot;criteria&quot; data-text=&quot;Criteria for the Core Web Vitals metric thresholds&quot; tabindex=&quot;-1&quot;&gt;Criteria for the Core Web Vitals metric thresholds&lt;/h2&gt;

&lt;p&gt;In this section, we&#39;ll look at the criteria for evaluating Core Web Vitals metric thresholds. The subsequent sections will go into more detail on how these criteria were applied to select the thresholds for each metric. In future years we anticipate making improvements and additions to the criteria and thresholds to further improve our ability to measure great user experiences on the web.&lt;/p&gt;

&lt;h3 id=&quot;high-quality_user_experience&quot; data-text=&quot;High-quality user experience&quot; tabindex=&quot;-1&quot;&gt;High-quality user experience&lt;/h3&gt;

&lt;p&gt;Our primary goal is to optimize for the user and their quality of experience.
Given this, we aim to ensure that pages that meet the Core Web Vitals &quot;good&quot;
thresholds deliver a high quality user experience.&lt;/p&gt;

&lt;p&gt;To identify a threshold associated with high-quality user experience, we look to
human perception and HCI research. While this research is sometimes summarized
using a single fixed threshold, we find that the underlying research is
typically expressed as a range of values. For example, research on the amount of
time users typically wait before losing focus is sometimes described as 1
second, while the underlying research is actually expressed as a range, from
hundreds of milliseconds to multiple seconds. The fact that perception
thresholds vary depending on user and context is further supported by aggregated
and anonymized Chrome metrics data, which shows that there is not a single
amount of time users wait for a web page to display content before aborting the
page load. Rather, this data shows a smooth and continuous distribution. For a
more in depth look at human perception thresholds and relevant HCI research, see
&lt;a href=&quot;https://blog.chromium.org/2020/05/the-science-behind-web-vitals.html&quot;&gt;The Science Behind Web
Vitals&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In cases where relevant user experience research is available for a given metric
and there is reasonable consensus on the range of values in the literature, we
use this range as an input to guide our threshold selection process. In cases
where relevant user experience research is unavailable, such as for a new metric
like Cumulative Layout Shift, we instead evaluate real-world pages that meet
different candidate thresholds for a metric, to identify a threshold that
results in a good user experience.&lt;/p&gt;

&lt;h3 id=&quot;achievable_by_existing_web_content&quot; data-text=&quot;Achievable by existing web content&quot; tabindex=&quot;-1&quot;&gt;Achievable by existing web content&lt;/h3&gt;

&lt;p&gt;Additionally, to ensure that site owners can succeed in optimizing their sites
to meet the &quot;good&quot; thresholds, we require that these thresholds are achievable
for existing content on the web. For example, while zero milliseconds is an
ideal LCP &quot;good&quot; threshold, resulting in instant loading experiences, a zero
millisecond threshold is not practically achievable in most cases, due to
network and device processing latencies. Thus, zero milliseconds is not a
reasonable LCP &quot;good&quot; threshold for Core Web Vitals.&lt;/p&gt;

&lt;p&gt;When evaluating candidate Core Web Vitals &quot;good&quot; thresholds, we verify that
those thresholds are achievable, based on data from the &lt;a href=&quot;https://developer.chrome.com/docs/crux&quot;&gt;Chrome User Experience
Report&lt;/a&gt;
(CrUX). To confirm that a threshold is achievable, we require that at least 10%
of &lt;a href=&quot;https://web.dev/articles/same-site-same-origin#origin&quot;&gt;origins&lt;/a&gt; meet the &quot;good&quot;
threshold. Additionally, to ensure that well-optimized sites are not
misclassified due to variability in field data, we also verify that
well-optimized content consistently meets the &quot;good&quot; threshold.&lt;/p&gt;

&lt;p&gt;Conversely, we establish the &quot;poor&quot; threshold by identifying a level of
performance that only a minority of origins are not meeting. Unless
there is research available relevant to defining a &quot;poor&quot; threshold, by default
the worst-performing 10-30% of origins are classified as &quot;poor&quot;.&lt;/p&gt;

&lt;h3 id=&quot;device&quot; data-text=&quot;Whether to have the same or different criteria per device&quot; tabindex=&quot;-1&quot;&gt;Whether to have the same or different criteria per device&lt;/h3&gt;

&lt;p&gt;Mobile and desktop usage typically have very different characteristics as to device capabilities and network reliability. This heavily impacts the &quot;achievability&quot; criteria and so suggests we should consider separate thresholds for each.&lt;/p&gt;

&lt;p&gt;However, users&#39; expectations of a good or poor experience is not dependent on device, even if the achievability criteria is. For this reason the Core Web Vitals recommended thresholds are not segregated by device and the same threshold is used for both. This also has the added benefit of making the thresholds simpler to understand.&lt;/p&gt;

&lt;p&gt;Additionally, devices don&#39;t always fit nicely into one category. Should this be based on device form factor, processing power, or network conditions? Having the same thresholds has the side benefit of avoiding that complexity.&lt;/p&gt;

&lt;p&gt;The more constrained nature of mobile devices means that most of the thresholds are therefore set based on mobile achievability. They more likely represent mobile thresholds—rather than a true joint threshold across all device types. However, given that &lt;a href=&quot;https://almanac.httparchive.org/en/2022/mobile-web#traffic-from-mobile-versus-desktop&quot;&gt;mobile is often the majority of traffic for most sites&lt;/a&gt;, this is less of a concern.&lt;/p&gt;

&lt;h3 id=&quot;final_thoughts_on_criteria&quot; data-text=&quot;Final thoughts on criteria&quot; tabindex=&quot;-1&quot;&gt;Final thoughts on criteria&lt;/h3&gt;

&lt;p&gt;When evaluating candidate thresholds, we found that the criteria were sometimes
in conflict with one another. For example, there can be a tension between a
threshold being consistently achievable and it ensuring consistently good user
experiences. Additionally, given that human perception research typically
provides a range of values, and user behavior metrics show gradual changes in
behavior, we found there is often no single &quot;correct&quot; threshold for a metric.&lt;/p&gt;

&lt;p&gt;Thus, our approach for the Core Web Vitals has been to choose thresholds
that best meet the criteria, while recognizing that there is no one
perfect threshold and that we may sometimes need to choose from multiple
reasonable candidate thresholds. Rather than asking &quot;what is the perfect
threshold?&quot; we instead focused on asking &quot;which candidate threshold best
achieves our criteria?&quot;&lt;/p&gt;

&lt;p&gt;These are also broad thresholds that we can apply across the whole of the web.
Many sites would benefit from optimizing even beyond the &quot;good&quot; thresholds and
and should seek to correlate with their individual business metrics.&lt;/p&gt;

&lt;h2 id=&quot;choice-of-percentile&quot; data-text=&quot;Choice of percentile&quot; tabindex=&quot;-1&quot;&gt;Choice of percentile&lt;/h2&gt;

&lt;p&gt;As noted earlier, to classify the overall performance of a page or site, we use
the 75th percentile value of all visits to that page or site. The 75th
percentile was chosen based on two criteria. First, the percentile should ensure
that a majority of visits to a page or site experienced the target level of
performance. Second, the value at the chosen percentile shouldn&#39;t be overly
impacted by outliers.&lt;/p&gt;

&lt;p&gt;These goals are somewhat at odds with one another. To satisfy the first goal, a
higher percentile is typically a better choice. However, with higher
percentiles, the likelihood of the resulting value being impacted by outliers
also increases. If a few visits to a site happen to be on flaky network
connections which result in excessively large LCP samples, we don&#39;t want our
site classification to be decided by these outlier samples. For example, if we
were evaluating the performance of a site with 100 visits using a high
percentile such as the 95th, it would take just 5 outlier samples for the 95th
percentile value to be affected by the outliers.&lt;/p&gt;

&lt;p&gt;Given these goals are a bit at odds, after analysis, we concluded that the 75th
percentile strikes a reasonable balance. By using the 75th percentile, we know
that most visits to the site (3 of 4) experienced the target level of
performance or better. Additionally, the 75th percentile value is less likely to
be affected by outliers. Returning to our example, for a site with 100 visits,
25 of those visits would need to report large outlier samples for the value at
the 75th percentile to be affected by outliers. While 25 of 100 samples being
outliers is possible, it is much less likely than for the 95th percentile case.&lt;/p&gt;

&lt;h2 id=&quot;lcp&quot; data-text=&quot;Largest Contentful Paint&quot; tabindex=&quot;-1&quot;&gt;Largest Contentful Paint&lt;/h2&gt;

&lt;p&gt;The LCP thresholds were set with the following quality of experience and achievability considerations.&lt;/p&gt;

&lt;h3 id=&quot;lcp-quality&quot; data-text=&quot;Quality of experience&quot; tabindex=&quot;-1&quot;&gt;Quality of experience&lt;/h3&gt;

&lt;p&gt;1 second is often cited as the amount of time a user will wait before they begin
to lose focus on a task. On closer inspection of relevant research, we found
that 1 second is an approximation to describe a range of values, from roughly
several hundred milliseconds to several seconds.&lt;/p&gt;

&lt;p&gt;Two commonly cited sources for the 1 second threshold are &lt;a href=&quot;https://dl.acm.org/doi/10.1145/108844.108874&quot;&gt;Card and others&lt;/a&gt; and
&lt;a href=&quot;https://dl.acm.org/doi/10.1145/1476589.1476628&quot;&gt;Miller&lt;/a&gt;. Card defines a
1-second &quot;immediate response&quot; threshold, citing Newell&#39;s &lt;a href=&quot;https://dl.acm.org/doi/book/10.5555/86564&quot;&gt;Unified Theories of
Cognition&lt;/a&gt;. Newell explains immediate
responses as &quot;responses that must be made to some stimulus within &lt;em&gt;very
approximately one second&lt;/em&gt; (that is, roughly from ~0.3sec to ~3sec).&quot; This
follows Newell&#39;s discussion on &quot;real-time constraints on cognition&quot;, where it is
noted that &quot;interactions with the environment which evoke cognitive
considerations take place on the order of seconds&quot; which range from roughly 0.5
to 2-3 seconds. Miller, another commonly cited source for the 1 second
threshold, notes &quot;tasks which humans can and will perform with machine
communications will seriously change their character if response delays are
greater than two seconds, with some possible extension of another second or so.&quot;&lt;/p&gt;

&lt;p&gt;Miller and Card&#39;s research describes the amount of time a user will wait before
losing focus as a range, from roughly 0.3 to 3 seconds, which suggests our LCP
&quot;good&quot; threshold should be in this range. Additionally, given that the existing
First Contentful Paint &quot;good&quot; threshold is 1 second, and that the Largest
Contentful Paint typically occurs after First Contentful Paint, we further
constrain our range of candidate LCP thresholds, from 1 second to 3 seconds. To
choose the threshold in this range that best meets our criteria, we look at the
achievability of these candidate thresholds next.&lt;/p&gt;

&lt;h3 id=&quot;lcp-achievability&quot; data-text=&quot;Achievability&quot; tabindex=&quot;-1&quot;&gt;Achievability&lt;/h3&gt;

&lt;p&gt;Using data from CrUX, we can determine the percentage of origins on the web that
meet our candidate LCP &quot;good&quot; thresholds.&lt;/p&gt;

&lt;figure&gt;
  &lt;table&gt;
    &lt;tbody&gt;&lt;tr&gt;
      &lt;th&gt;&amp;nbsp;&lt;/th&gt;
      &lt;th&gt;1 second&lt;/th&gt;
      &lt;th&gt;1.5 seconds&lt;/th&gt;
      &lt;th&gt;2 seconds&lt;/th&gt;
      &lt;th&gt;2.5 seconds&lt;/th&gt;
      &lt;th&gt;3 seconds&lt;/th&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;phone&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;3.5%&lt;/td&gt;
      &lt;td&gt;13%&lt;/td&gt;
      &lt;td&gt;27%&lt;/td&gt;
      &lt;td&gt;42%&lt;/td&gt;
      &lt;td&gt;55%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;desktop&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;6.9%&lt;/td&gt;
      &lt;td&gt;19%&lt;/td&gt;
      &lt;td&gt;36%&lt;/td&gt;
      &lt;td&gt;51%&lt;/td&gt;
      &lt;td&gt;64%&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;&lt;/table&gt;
  &lt;figcaption&gt;% of CrUX origins classified as &quot;good&quot; for candidate LCP thresholds as of April 2020&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;While less than 10% of origins meet the 1 second threshold, all other thresholds
from 1.5 to 3 seconds satisfy our requirement that at least 10% of origins meet
the &quot;good&quot; threshold, and are thus still valid candidates.&lt;/p&gt;

&lt;p&gt;In addition, to ensure the chosen threshold is consistently achievable for
well-optimized sites, we analyze LCP performance for top-performing sites across
the web, to determine which thresholds are consistently achievable for these
sites. Specifically, we aim to identify a threshold that is consistently
achievable at the 75th percentile for top performing sites. We find that the 1.5
and 2 second thresholds are not consistently achievable, while 2.5 seconds is
consistently achievable.&lt;/p&gt;

&lt;p&gt;To identify a &quot;poor&quot; threshold for LCP, we use CrUX data to identify a threshold
met by most origins:&lt;/p&gt;

&lt;figure&gt;
  &lt;table&gt;
    &lt;tbody&gt;&lt;tr&gt;
      &lt;th&gt;&amp;nbsp;&lt;/th&gt;
      &lt;th&gt;3 seconds&lt;/th&gt;
      &lt;th&gt;3.5 seconds&lt;/th&gt;
      &lt;th&gt;4 seconds&lt;/th&gt;
      &lt;th&gt;4.5 seconds&lt;/th&gt;
      &lt;th&gt;5 seconds&lt;/th&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;phone&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;45%&lt;/td&gt;
      &lt;td&gt;35%&lt;/td&gt;
      &lt;td&gt;26%&lt;/td&gt;
      &lt;td&gt;20%&lt;/td&gt;
      &lt;td&gt;15%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;desktop&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;36%&lt;/td&gt;
      &lt;td&gt;26%&lt;/td&gt;
      &lt;td&gt;19%&lt;/td&gt;
      &lt;td&gt;14%&lt;/td&gt;
      &lt;td&gt;10%&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;&lt;/table&gt;
  &lt;figcaption&gt;% of CrUX origins classified as &quot;poor&quot; for candidate LCP thresholds as of April 2020&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;For a 4 second threshold, roughly 26% of phone origins, and 21% of desktop
origins, would be classified as poor. This falls in our target range of 10-30%,
so we conclude that 4 seconds is an acceptable &quot;poor&quot; threshold.&lt;/p&gt;

&lt;p&gt;Thus, we conclude that 2.5 seconds is a reasonable &quot;good&quot; threshold, and 4
seconds is a reasonable &quot;poor&quot; threshold for Largest Contentful Paint.&lt;/p&gt;

&lt;h2 id=&quot;inp&quot; data-text=&quot;Interaction to Next Paint&quot; tabindex=&quot;-1&quot;&gt;Interaction to Next Paint&lt;/h2&gt;

&lt;p&gt;The INP thresholds were set with the following quality of experience and achievability considerations.&lt;/p&gt;

&lt;h3 id=&quot;inp-quality&quot; data-text=&quot;Quality of experience&quot; tabindex=&quot;-1&quot;&gt;Quality of experience&lt;/h3&gt;

&lt;p&gt;Research is reasonably consistent in concluding that delays in visual feedback
of up to around 100 ms are perceived as being caused by an associated source,
such as a user input. This suggests that an ideal Interaction to Next Paint &quot;good&quot;
threshold would be close to this.&lt;/p&gt;

&lt;p&gt;Jakob Nielsen&#39;s commonly cited &lt;a href=&quot;https://www.nngroup.com/articles/response-times-3-important-limits/&quot;&gt;Response Times: The 3 Important
Limits&lt;/a&gt;
defines 0.1 second as the limit for having the user feel that the system is
reacting instantaneously. Nielsen cites Miller and Card, who cites Michotte&#39;s
1962 &lt;a href=&quot;https://psycnet.apa.org/record/1964-05029-000&quot;&gt;The Perception of
Causality&lt;/a&gt;. In Michotte&#39;s
research, experiment participants are shown &quot;two objects on a screen. Object A
sets off and moves towards B. It stops at the moment when it comes into contact
with B, while the latter then starts and moves away from A.&quot; Michotte varies the
time interval between when Object A stops and when Object B starts to move.
Michotte finds that, for delays up to roughly 100 ms, participants have the
impression that Object A causes the motion of Object B. For delays from roughly
100 ms to 200 ms, the perception of causality is mixed, and for delays over 200 ms,
the motion of Object B is no longer seen as having been caused by Object A.&lt;/p&gt;

&lt;p&gt;Similarly, Miller defines a response threshold for &quot;Response to control
activation&quot; as &quot;the indication of action given, ordinarily, by the movement of a
key, switch or other control member that signals it has been physically
actIvated. This response should be…perceived as a part of the mechanical
action induced by the operator. Time delay: No more than 0.1 second&quot; and later
&quot;the delay between depressing a key and visual feedback should be no more than
0.1 to 0.2 seconds&quot;.&lt;/p&gt;

&lt;p&gt;More recently, in &lt;a href=&quot;https://dl.acm.org/doi/10.1145/2611387&quot;&gt;Towards the Temporally Perfect Virtual
Button&lt;/a&gt;, Kaaresoja and others investigated
the perception of simultaneity between touching a virtual button on a
touchscreen and subsequent visual feedback indicating the button was touched,
for various delays. When the delay between button press and visual feedback was
85ms or less, participants reported the visual feedback appeared simultaneously
with the button press 75% of the time. Additionally, for delays of 100 ms or
less, participants reported a consistently high perceived quality of the button
press, with perceived quality falling off for delays of 100 ms to 150 ms, and
reaching very low levels for delays of 300 ms.&lt;/p&gt;

&lt;p&gt;Given this, we conclude that research points to 100 ms as a &quot;good&quot; Interaction to Next Paint threshold for Web Vitals. Additionally, given users reported low quality levels for delays of 300 ms or more, ideally this would be the &quot;poor&quot; threshold.&lt;/p&gt;

&lt;h3 id=&quot;inp-achievability&quot; data-text=&quot;Achievability&quot; tabindex=&quot;-1&quot;&gt;Achievability&lt;/h3&gt;

&lt;p&gt;Using data from CrUX, we determine that the majority of origins on the web meet
the 200 ms INP &quot;good&quot; threshold at the 75th percentile:&lt;/p&gt;

&lt;figure&gt;
  &lt;table&gt;
    &lt;tbody&gt;&lt;tr&gt;
      &lt;th&gt;&amp;nbsp;&lt;/th&gt;
      &lt;th&gt;100 ms&lt;/th&gt;
      &lt;th&gt;200 ms&lt;/th&gt;
      &lt;th&gt;300 ms&lt;/th&gt;
      &lt;th&gt;400 ms&lt;/th&gt;
      &lt;th&gt;500 ms&lt;/th&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;phone&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;12%&lt;/td&gt;
      &lt;td&gt;56%&lt;/td&gt;
      &lt;td&gt;76%&lt;/td&gt;
      &lt;td&gt;88%&lt;/td&gt;
      &lt;td&gt;92%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;desktop&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;83%&lt;/td&gt;
      &lt;td&gt;96%&lt;/td&gt;
      &lt;td&gt;98%&lt;/td&gt;
      &lt;td&gt;99%&lt;/td&gt;
      &lt;td&gt;99%&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;&lt;/table&gt;
  &lt;figcaption&gt;% of CrUX origins classified as &quot;good&quot; for candidate INP thresholds as of May 2022&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;We also spent extra attention looking at achievability of passing INP for lower-end mobile devices, where those formed a high proportion of visits to sites. This further confirmed the suitability of a 200 ms threshold.&lt;/p&gt;

&lt;p&gt;Taking into consideration the 100 ms threshold supported by research into the quality of experience and the achievability criteria, we conclude that 200 ms is a reasonable threshold for good experiences&lt;/p&gt;

&lt;p&gt;To identify a &quot;poor&quot; threshold for LCP, we use CrUX data to identify a threshold met by most origins:&lt;/p&gt;

&lt;figure&gt;
  &lt;table&gt;
    &lt;tbody&gt;&lt;tr&gt;
      &lt;th&gt;&amp;nbsp;&lt;/th&gt;
      &lt;th&gt;100 ms&lt;/th&gt;
      &lt;th&gt;200 ms&lt;/th&gt;
      &lt;th&gt;300 ms&lt;/th&gt;
      &lt;th&gt;400 ms&lt;/th&gt;
      &lt;th&gt;500 ms&lt;/th&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;phone&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;88%&lt;/td&gt;
      &lt;td&gt;44%&lt;/td&gt;
      &lt;td&gt;24%&lt;/td&gt;
      &lt;td&gt;12%&lt;/td&gt;
      &lt;td&gt;8%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;desktop&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;17%&lt;/td&gt;
      &lt;td&gt;4%&lt;/td&gt;
      &lt;td&gt;2%&lt;/td&gt;
      &lt;td&gt;1%&lt;/td&gt;
      &lt;td&gt;1%&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;&lt;/table&gt;
  &lt;figcaption&gt;% of CrUX origins classified as &quot;poor&quot; for candidate INP thresholds as of May 2022&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;This suggests we can have a &quot;poor&quot; threshold of 300 ms.&lt;/p&gt;

&lt;p&gt;However, unlike LCP and CLS, INP has an inverse correlation with popularity—the more popular sites are often more complex, resulting in more likelihood of higher INP. When we look at the top 10,000 sites—which form the vast majority of internet browsing—we see a more complex picture emerge:&lt;/p&gt;

&lt;figure&gt;
  &lt;table&gt;
    &lt;tbody&gt;&lt;tr&gt;
      &lt;th&gt;&amp;nbsp;&lt;/th&gt;
      &lt;th&gt;100 ms&lt;/th&gt;
      &lt;th&gt;200 ms&lt;/th&gt;
      &lt;th&gt;300 ms&lt;/th&gt;
      &lt;th&gt;400 ms&lt;/th&gt;
      &lt;th&gt;500 ms&lt;/th&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;phone&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;97%&lt;/td&gt;
      &lt;td&gt;77%&lt;/td&gt;
      &lt;td&gt;55%&lt;/td&gt;
      &lt;td&gt;37%&lt;/td&gt;
      &lt;td&gt;24%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;desktop&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;48%&lt;/td&gt;
      &lt;td&gt;17%&lt;/td&gt;
      &lt;td&gt;8%&lt;/td&gt;
      &lt;td&gt;4%&lt;/td&gt;
      &lt;td&gt;2%&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;&lt;/table&gt;
  &lt;figcaption&gt;% of CrUX origins in top 10,000 classified as &quot;poor&quot; for candidate INP thresholds as of May 2022&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;On mobile, a 300 ms &quot;poor&quot; threshold would classify the majority of popular sites as &quot;poor&quot; stretching our achievability criteria, while 500 ms fits better in the range of 10-30% of sites. It should also be noted that the 200 ms &quot;good&quot; threshold is also tougher for these sites, but with 23% of sites still passing this on mobile this still passes our 10% minimum pass rate criteria.&lt;/p&gt;

&lt;p&gt;For this reason we conclude a 200 ms is a reasonable &quot;good&quot; threshold for most sites, and greater than 500 ms is a reasonable &quot;poor&quot; threshold.&lt;/p&gt;

&lt;h2 id=&quot;cls&quot; data-text=&quot;Cumulative Layout Shift&quot; tabindex=&quot;-1&quot;&gt;Cumulative Layout Shift&lt;/h2&gt;

&lt;p&gt;The CLS thresholds were set with the following quality of experience and achievability considerations.&lt;/p&gt;

&lt;h3 id=&quot;cls-quality&quot; data-text=&quot;Quality of experience&quot; tabindex=&quot;-1&quot;&gt;Quality of experience&lt;/h3&gt;

&lt;p&gt;Cumulative Layout Shift (CLS) is a new metric that measures how much the visible
content of a page shifts around. Given CLS is new, we are not aware of research
that can directly inform the thresholds for this metric. Thus, to identify a
threshold that is aligned with user expectations, we evaluated real-world pages
with different amounts of layout shift, to determine the maximum amount of shift
that is perceived as acceptable before causing significant disruptions when
consuming page content. In our internal testing, we found that levels of shift
from 0.15 and higher were consistently perceived as disruptive, while shifts of
0.1 and lower were noticeable but not excessively disruptive. Thus, while zero
layout shift is ideal, we concluded that values up to 0.1 are candidate &quot;good&quot;
CLS thresholds.&lt;/p&gt;

&lt;h3 id=&quot;cls-achievability&quot; data-text=&quot;Achievability&quot; tabindex=&quot;-1&quot;&gt;Achievability&lt;/h3&gt;

&lt;p&gt;Based on CrUX data, we can see that nearly 50% of origins have CLS of 0.05 or
lower.&lt;/p&gt;

&lt;figure&gt;
  &lt;table&gt;
    &lt;tbody&gt;&lt;tr&gt;
      &lt;th&gt;&amp;nbsp;&lt;/th&gt;
      &lt;th&gt;0.05&lt;/th&gt;
      &lt;th&gt;0.1&lt;/th&gt;
      &lt;th&gt;0.15&lt;/th&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;phone&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;49%&lt;/td&gt;
      &lt;td&gt;60%&lt;/td&gt;
      &lt;td&gt;69%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;desktop&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;42%&lt;/td&gt;
      &lt;td&gt;59%&lt;/td&gt;
      &lt;td&gt;69%&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;&lt;/table&gt;
  &lt;figcaption&gt;% of CrUX origins classified as &quot;good&quot; for candidate CLS thresholds as of April 2020&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;While the CrUX data suggests that 0.05 might be a reasonable CLS &quot;good&quot;
threshold, we recognize that there are some use cases where it is
difficult to avoid disruptive layout shifts. For example, for third-party
embedded content, such as social media embeds, the height of the embedded
content is sometimes not known until it finishes loading, which can lead to a
layout shift greater than 0.05. Thus, we conclude that, while many origins meet
the 0.05 threshold, the slightly less stringent CLS threshold of 0.1 strikes a
better balance between quality of experience and achievability. It is our hope
that, going forward, the web ecosystem will identify solutions to address layout
shifts caused by third party embeds, which would allow for using a more
stringent CLS &quot;good&quot; threshold of 0.05 or 0 in a future iteration of Core Web
Vitals.&lt;/p&gt;

&lt;p&gt;Additionally, to determine a &quot;poor&quot; threshold for CLS, we used CrUX data to
identify a threshold met by most origins:&lt;/p&gt;

&lt;figure&gt;
  &lt;table&gt;
    &lt;tbody&gt;&lt;tr&gt;
      &lt;th&gt;&amp;nbsp;&lt;/th&gt;
      &lt;th&gt;0.15&lt;/th&gt;
      &lt;th&gt;0.2&lt;/th&gt;
      &lt;th&gt;0.25&lt;/th&gt;
      &lt;th&gt;0.3&lt;/th&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;phone&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;31%&lt;/td&gt;
      &lt;td&gt;25%&lt;/td&gt;
      &lt;td&gt;20%&lt;/td&gt;
      &lt;td&gt;18%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;desktop&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;31%&lt;/td&gt;
      &lt;td&gt;23%&lt;/td&gt;
      &lt;td&gt;18%&lt;/td&gt;
      &lt;td&gt;16%&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;&lt;/table&gt;
  &lt;figcaption&gt;% of CrUX origins classified as &quot;poor&quot; for candidate CLS thresholds as of April 2020&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;For a 0.25 threshold, roughly 20% of phone origins, and 18% of desktop origins,
would be classified as &quot;poor&quot;. This falls in our target range of 10-30%, so we
concluded that 0.25 is an acceptable &quot;poor&quot; threshold.&lt;/p&gt;

  

  
</description><link>https://web.dev/articles/defining-core-web-vitals-thresholds?hl=en</link><guid isPermaLink="false">https://web.dev/articles/defining-core-web-vitals-thresholds?hl=en</guid><pubDate>Wed, 07 May 2025 07:00:00 GMT</pubDate></item><item><title>High DPI images for variable pixel densities</title><description>&lt;p&gt;&lt;style&gt;
  .wd-author__links {
    display: flex;
  }
  &lt;/style&gt;

&lt;/p&gt;&lt;p&gt;&lt;/p&gt;

&lt;p&gt;
&lt;/p&gt;&lt;p class=&quot;wd-pubdates&quot; style=&quot;font-size: 90%; border-bottom: 1px solid var(--devsite-ref-palette--grey200);color: var(--devsite-ref-palette--blue-grey700);&quot;&gt;
  Published: August 22, 2012, Last updated: April 14, 2025
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;aside class=&quot;note&quot;&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;span&gt; Since writing this document in 2012, &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;image-set&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;srcset&lt;/code&gt; have
become &lt;a href=&quot;https://web.dev/baseline&quot;&gt;Baseline&lt;/a&gt;. The updates for this document are still
incomplete, but you may still find this overview useful.&lt;/span&gt;&lt;/aside&gt;
&lt;p&gt;With so many devices on the market, there&#39;s a
&lt;a href=&quot;http://en.wikipedia.org/wiki/List_of_displays_by_pixel_density&quot;&gt;very wide range of screen pixel densities&lt;/a&gt; available.
Application developers need to support a range of
pixel densities, which can be quite challenging. On the mobile web, the
challenges are compounded by several factors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Large variety of devices with different form factors.&lt;/li&gt;
&lt;li&gt;Constrained network bandwidth and battery life.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In terms of images, the goal for web developers is to serve the
best quality images as efficiently as possible. Here, we cover some
useful techniques to do this now and in the future.&lt;/p&gt;

&lt;h2 id=&quot;avoid_images_when_possible&quot; data-text=&quot;Avoid images when possible&quot; tabindex=&quot;-1&quot;&gt;Avoid images when possible&lt;/h2&gt;

&lt;p&gt;Before assuming you need to include an image, remember that the web has numerous
powerful technologies that are largely resolution- and DPI-independent.
Specifically, text, SVG, and much of CSS &quot;just works&quot; because of the
automatic pixel scaling feature of the web with
&lt;a href=&quot;http://www.quirksmode.org/blog/archives/2012/06/devicepixelrati.html&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;devicePixelRatio&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That said, you can&#39;t always avoid raster images. For example, you may be
given assets that would be quite hard to replicate in pure SVGs or CSS. You
may be dealing with a photograph. While you could convert an image
into an SVG automatically, vectorizing photographs makes little sense
because scaled-up versions usually don&#39;t look good.&lt;/p&gt;

&lt;h2 id=&quot;history_of_pixel_density&quot; data-text=&quot;History of pixel density&quot; tabindex=&quot;-1&quot;&gt;History of pixel density&lt;/h2&gt;

&lt;p&gt;In the early days, computer displays had a pixel density of 72 or 96
&lt;a href=&quot;http://en.wikipedia.org/wiki/Dots_per_inch&quot;&gt;dots per inch (DPI)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Displays gradually improved in pixel density, largely driven by the advance of
mobile devices, as users generally hold their phones closer to
their faces making pixels more visible. By 2008, 150dpi phones were the
new norm. Increased display density continued, and today&#39;s phones have
300dpi displays.&lt;/p&gt;

&lt;p&gt;In practice, low density images should look the same on new screens as
they did on old ones, but compared to the crisp imagery high density
users are used to seeing, the low density images look jarring and
pixelated. The following is a rough simulation of how a 1x image
looks on a 2x display. In contrast, the 2x image looks quite good.&lt;/p&gt;

&lt;table class=&quot;columns&quot;&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;1x pixels&lt;/td&gt;
&lt;td&gt;2x pixels&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src=&quot;https://web.dev/static/articles/high-dpi/image/baboon-1x.jpeg&quot; alt=&quot;Baboon 1x pixel density.&quot; width=&quot;200&quot; height=&quot;200&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;https://web.dev/static/articles/high-dpi/image/baboon-2x.jpg&quot; alt=&quot;Baboon 2x pixel density.&quot; width=&quot;400&quot; height=&quot;400&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;caption class=&quot;wd-caption&quot;&gt;Baboons at differing pixel densities.&lt;/caption&gt;
&lt;/table&gt;

&lt;h3 id=&quot;pixels_on_the_web&quot; data-text=&quot;Pixels on the web&quot; tabindex=&quot;-1&quot;&gt;Pixels on the web&lt;/h3&gt;

&lt;p&gt;When the web was designed, 99% of displays were 96dpi and few provisions
were made for variations. Now that we have a large variation in screen sizes
and densities, we need a standard way to make images look good across them all.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;http://inamidst.com/stuff/notes/csspx&quot;&gt;HTML specification&lt;/a&gt; tackled this problem by
defining a reference pixel that manufacturers use to determine the size
of a CSS pixel.&lt;/p&gt;
&lt;aside class=&quot;note&quot;&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;span&gt; We recommended that the reference pixel is the visual angle of one pixel
on a device with a pixel density of 96dpi and a distance from the reader of an
arm&#39;s length. Therefore, for a nominal arm&#39;s length of 28 inches, the visual
angle is about 0.0213 degrees.&lt;/span&gt;&lt;/aside&gt;
&lt;p&gt;Using the reference pixel, a manufacturer can determine the size of the
device&#39;s physical pixel relative to the standard or ideal pixel. This
ratio is called the device pixel ratio.&lt;/p&gt;

&lt;h3 id=&quot;calculate_the_device_pixel_ratio&quot; data-text=&quot;Calculate the device pixel ratio&quot; tabindex=&quot;-1&quot;&gt;Calculate the device pixel ratio&lt;/h3&gt;

&lt;p&gt;Suppose a mobile phone has a screen with a physical pixel size of 180
pixels per inch (ppi). Calculating the device pixel ratio takes three
steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Compare the actual distance at which the device is held to the
distance for the reference pixel.&lt;/p&gt;

&lt;p&gt;Per the spec, we know that at 28 inches, the ideal is 96 pixels per
inch. With a mobile phone, we know people&#39;s faces are closer to their
devices than with laptops and desktop computers. For the following
equations, we estimate that distance to be 18 inches.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Multiply the distance ratio against the standard density (96ppi) to
get the ideal pixel density for the given distance.&lt;/p&gt;

&lt;p&gt;idealPixelDensity = (28/18) * 96 = 150 pixels per inch (approximately)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Take the ratio of the physical pixel density to the ideal pixel
density to get the device pixel ratio.&lt;/p&gt;

&lt;p&gt;devicePixelRatio = 180/150 = 1.2&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;figure class=&quot;screenshot&quot;&gt;
&lt;img src=&quot;https://web.dev/static/articles/high-dpi/image/device-pixel-ratio-angle.png&quot; alt=&quot;&quot; width=&quot;465&quot; height=&quot;282&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
&lt;figcaption class=&quot;wd-caption&quot;&gt;One reference angular pixel, to
illustrate how the device pixel ratio is calculated.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;So now when a browser needs to know how to resize an image to fit the
screen, according to the ideal or standard resolution, the browser refers
to the device pixel ratio of 1.2. This means for every ideal pixel,
this device has 1.2 physical pixels. The formula to go between ideal
(as defined by the web spec) and physical (dots on device screen) pixels
is the following:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JavaScript&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;physicalPixels&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;devicePixelRatio&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;idealPixels&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;Historically, device vendors have tended to round &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;devicePixelRatios&lt;/code&gt;
(DPRs). Apple&#39;s iPhone and iPad report DPR of 1, and their Retina
equivalents report 2. The &lt;a href=&quot;http://www.w3.org/TR/CSS21/syndata.html#length-units&quot;&gt;CSS specification&lt;/a&gt; recommends that&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;the pixel unit refer to the whole number of device pixels that best
approximates the reference pixel.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One reason why round ratios can be better is because they may lead to
fewer &lt;a href=&quot;http://ejohn.org/blog/sub-pixel-problems-in-css/&quot;&gt;sub-pixel artifacts&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;However, the reality of the device landscape is much more varied, and
Android phones often have DPRs of 1.5. The Nexus 7 tablet has a DPR of
~1.33, which was arrived at by a calculation similar to the previous example.
Expect to see more devices with variable DPRs in the future. Because of
this, you should never assume that your clients have integer DPRs.&lt;/p&gt;

&lt;h2 id=&quot;hidpi_image_techniques&quot; data-text=&quot;HiDPI image techniques&quot; tabindex=&quot;-1&quot;&gt;HiDPI image techniques&lt;/h2&gt;

&lt;p&gt;There are many techniques for solving the problem of showing the best
quality images as fast as possible, broadly falling into two categories:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Optimizing single images.&lt;/li&gt;
&lt;li&gt;Optimizing selection between multiple images.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Single image approaches: use one image, but do something clever with it.
These approaches have the drawback that you must inevitably sacrifice
performance, since you have to download HiDPI images, even on older
devices with lower DPI. Here are some approaches for the single image
case:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Heavily compressed HiDPI image&lt;/li&gt;
&lt;li&gt;Totally awesome image format&lt;/li&gt;
&lt;li&gt;Progressive image format&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Multiple image approaches: use multiple images, but do something clever
to pick which to load. These approaches have inherent overhead for the
developer to create multiple versions of the same asset and then figure
out a decision strategy. Here are the options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JavaScript&lt;/li&gt;
&lt;li&gt;Server side delivery&lt;/li&gt;
&lt;li&gt;CSS media queries&lt;/li&gt;
&lt;li&gt;Built-in browser features (&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;image-set()&lt;/code&gt;, &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;img srcset&amp;gt;&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;heavily_compressed_hidpi_image&quot; data-text=&quot;Heavily compressed HiDPI image&quot; tabindex=&quot;-1&quot;&gt;Heavily compressed HiDPI image&lt;/h3&gt;

&lt;p&gt;Images already &lt;a href=&quot;http://httparchive.org/interesting.php#bytesperpage&quot;&gt;comprise a whopping 60% of bandwidth&lt;/a&gt; spent downloading an
average website. By serving HiDPI images to all clients, we
increase this number. How much bigger can it grow?&lt;/p&gt;

&lt;p&gt;I ran some tests which generated 1x and 2x image fragments with JPEG
quality at 90, 50 and 20.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://web.dev/static/articles/high-dpi/image/tiles-example-1-96a91faef342e.jpg&quot; alt=&quot;Six versions of one image, varied in compression and pixel density.&quot; width=&quot;800&quot; height=&quot;453&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
&lt;img src=&quot;https://web.dev/static/articles/high-dpi/image/tiles-example-2-bcd65cbbb9261.jpg&quot; alt=&quot;Six versions of one image, varied in compression and pixel density.&quot; width=&quot;800&quot; height=&quot;453&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
&lt;img src=&quot;https://web.dev/static/articles/high-dpi/image/tiles-example-3-92bb904b4cb26.jpg&quot; alt=&quot;Six versions of one image, varied in compression and pixel density.&quot; width=&quot;800&quot; height=&quot;453&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;

&lt;p&gt;From this small, unscientific sampling, it seems that compressing large
images provides a good quality-to-size tradeoff. To my eye, heavily
compressed 2x imagery actually looks better than uncompressed 1x
pictures.&lt;/p&gt;

&lt;p&gt;That said, serving low-quality, highly compressed 2x imagery to 2x
devices is worse than serving higher quality ones, and that approach would
incur image quality penalties. If you compare &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;quality: 90&lt;/code&gt;
images to &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;quality: 20&lt;/code&gt; images, there&#39;s a drop in how crisp the image is
and increased graininess. Artifacts with &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;quality:20&lt;/code&gt; may not be acceptable
where high quality images are important (for example, a photo viewer
application) or for app developers that are unwilling to compromise.&lt;/p&gt;

&lt;p&gt;This comparison was made entirely with compressed JPEGs. It&#39;s worth
noting that there are &lt;a href=&quot;http://www.labnol.org/software/tutorials/jpeg-vs-png-image-quality-or-bandwidth/5385/&quot;&gt;many tradeoffs&lt;/a&gt; between the widely
implemented image formats (JPEG, PNG, GIF), which brings us to…&lt;/p&gt;

&lt;h3 id=&quot;webp_totally_awesome_image_format&quot; data-text=&quot;WebP: Totally awesome image format&quot; tabindex=&quot;-1&quot;&gt;WebP: Totally awesome image format&lt;/h3&gt;

&lt;p&gt;&lt;devsite-iframe&gt;&lt;iframe src=&quot;https://web.dev/frame/articles/high-dpi/index_02129f88eeaea28bceffc7876d915c05b76707f537505f29cde590b1153997b5.frame&quot; class=&quot;framebox inherit-locale &quot; allow=&quot;clipboard-write https://web-dot-devsite-v2-prod-3p.appspot.com&quot; allowfullscreen=&quot;&quot; is-upgraded=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;&lt;/devsite-iframe&gt;&lt;/p&gt;

&lt;p&gt;WebP is a pretty &lt;a href=&quot;https://developers.google.com/speed/webp/docs/webp_lossless_alpha_study&quot;&gt;compelling image format&lt;/a&gt; that compresses
very well while keeping high image fidelity.&lt;/p&gt;

&lt;p&gt;One way is to check for WebP support is using JavaScript. Load a 1px
image with &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;data-uri&lt;/code&gt;, wait for either loaded or error events fired, and
then verify that the size is correct. &lt;a href=&quot;https://github.com/Modernizr/Modernizr&quot;&gt;Modernizr&lt;/a&gt; ships with
such a &lt;a href=&quot;https://github.com/Modernizr/Modernizr/commit/d1fa2a62d7912d5f253ac1ab9ae2ce9430c3ef92&quot;&gt;feature detection script&lt;/a&gt;, which is available
with &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;Modernizr.webp&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A better way of doing this, however, is directly
in CSS using the &lt;a href=&quot;http://www.w3.org/TR/css3-images/#image-notation&quot;&gt;image() function&lt;/a&gt;. So if you have a WebP
image and JPEG fallback, you can write the following:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nn&quot;&gt;pic&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;foo.webp&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;foo.jpg&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;There are a few problems with this approach. First, &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;image()&lt;/code&gt; is not
at all widely implemented. Secondly, while WebP compression blows JPEG
out of the water, it&#39;s still a relatively incremental improvement –
about 30% smaller based on this &lt;a href=&quot;https://developers.google.com/speed/webp/gallery1&quot;&gt;WebP gallery&lt;/a&gt;. Thus, WebP
alone isn&#39;t enough to address the high DPI problem.&lt;/p&gt;

&lt;h3 id=&quot;progressive_image_formats&quot; data-text=&quot;Progressive image formats&quot; tabindex=&quot;-1&quot;&gt;Progressive image formats&lt;/h3&gt;

&lt;p&gt;Progressive image formats like JPEG 2000, Progressive JPEG, Progressive
PNG and GIF have the (somewhat debated) benefit of seeing the image come
into place before it&#39;s fully loaded. They may incur some size overhead,
though there is conflicting evidence about this. &lt;a href=&quot;https://blog.codinghorror.com/progressive-image-rendering/&quot;&gt;Jeff Atwood claimed&lt;/a&gt;
that progressive mode &quot;adds about 20% to the size of PNG images, and
about 10% to the size of JPEG and GIF images&quot;. However, &lt;a href=&quot;https://www.smashingmagazine.com/2019/08/faster-image-loading-embedded-previews/&quot;&gt;Stoyan Stefanov
claimed&lt;/a&gt; that for large files, progressive mode is more efficient (in
most cases).&lt;/p&gt;

&lt;p&gt;At first glance, progressive images look very promising in the context
of serving the best quality images as fast as possible. The idea is
that the browser can stop downloading and decoding an image once it
knows that additional data won&#39;t increase the image quality (ie. all of
the fidelity improvements are sub-pixel).&lt;/p&gt;

&lt;p&gt;While connections are quick to terminate, they are often expensive to
restart. For a site with many images, the most efficient approach is to
keep a single HTTP connection alive, reusing it for as long as possible.
If the connection is terminated prematurely because one image has been
downloaded enough, the browser then needs to create a new connection,
which can be really &lt;a href=&quot;http://serverfault.com/questions/387627/why-do-mobile-networks-have-high-latencies-how-can-they-be-reduced&quot;&gt;slow in low latency&lt;/a&gt; environments.&lt;/p&gt;

&lt;p&gt;One workaround to this is to use the &lt;a href=&quot;http://stackoverflow.com/questions/1434647/using-the-http-range-header-with-a-range-specifier-other-than-bytes&quot;&gt;HTTP Range&lt;/a&gt; request, which lets
browsers specify a range of bytes to fetch. A smart browser could make a
HEAD request to get at the header, process it, decide how much of the
image is actually needed, and then fetch. Unfortunately HTTP Range is
poorly supported in web servers, making this approach impractical.&lt;/p&gt;

&lt;p&gt;Finally, an obvious limitation of this approach is that you don&#39;t get to
choose which image to load, only varying fidelities of the same image.
As a result, this doesn&#39;t address the &quot;&lt;a href=&quot;https://cloudfour.com/thinks/a-framework-for-discussing-responsive-images-solutions/&quot;&gt;art direction&lt;/a&gt;&quot; use case.&lt;/p&gt;

&lt;h3 id=&quot;use_javascript_to_decide_which_image_to_load&quot; data-text=&quot;Use JavaScript to decide which image to load&quot; tabindex=&quot;-1&quot;&gt;Use JavaScript to decide which image to load&lt;/h3&gt;

&lt;p&gt;The first and most obvious approach to deciding which image to load is
to use JavaScript in the client. This approach lets you find out
everything about your user agent and do the right thing. You can
determine device pixel ratio with &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;window.devicePixelRatio&lt;/code&gt;, get screen
width and height, and even potentially do some network connection
sniffing with navigator.connection or issuing a fake request, like the
&lt;a href=&quot;https://github.com/adamdbradley/foresight.js&quot;&gt;foresight.js library&lt;/a&gt; does. Once you&#39;ve collected all of
this information, you can decide which image to load.&lt;/p&gt;

&lt;p&gt;There are approximately &lt;a href=&quot;https://docs.google.com/a/google.com/spreadsheet/ccc?key=0Al0lI17fOl9DdDgxTFVoRzFpV3VCdHk2NTBmdVI2OXc#gid=0&quot;&gt;one million JavaScript libraries&lt;/a&gt; that
use this technique. Unfortunately, none of them are
particularly outstanding.&lt;/p&gt;

&lt;p&gt;One big drawback is that you delay image loading until after the look-ahead
parser has finished. This essentially means that images won&#39;t even start
downloading until after the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;pageload&lt;/code&gt; event fires. More on this in
&lt;a href=&quot;http://blog.cloudfour.com/the-real-conflict-behind-picture-and-srcset/&quot;&gt;Jason Grigsby&#39;s article&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;decide_what_image_to_load_on_the_server&quot; data-text=&quot;Decide what image to load on the server&quot; tabindex=&quot;-1&quot;&gt;Decide what image to load on the server&lt;/h3&gt;

&lt;p&gt;You can defer the decision to the server-side by writing custom request
handlers for each image you serve. Such a handler would check for Retina
support based on User-Agent (the only piece of information shared with
the server). Then, based on whether the server-side logic wants to serve
HiDPI assets, you load the appropriate asset (named according to some
known convention).&lt;/p&gt;

&lt;p&gt;Unfortunately, the User-Agent doesn&#39;t necessarily provide enough
information to decide whether a device should receive high or low
quality images. And, any solution that uses the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;User-Agent&lt;/code&gt; to make style
decisions should be avoided.&lt;/p&gt;

&lt;h3 id=&quot;use_css_media_queries&quot; data-text=&quot;Use CSS media queries&quot; tabindex=&quot;-1&quot;&gt;Use CSS media queries&lt;/h3&gt;

&lt;p&gt;Being declarative, CSS media queries let you state your intention, and
let the browser do the right thing on your behalf. In addition to the most
common use of media queries—matching device size—you can
also match &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;devicePixelRatio&lt;/code&gt;. The associated media query is
device-pixel-ratio, and has associated min and max variants, as you
might expect.&lt;/p&gt;

&lt;p&gt;If you want to load high DPI images and the device pixel
ratio exceeds a threshold, here&#39;s what you might do:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nn&quot;&gt;my-image&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;low&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;png&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;media&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;only&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;screen&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;and&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;min-device-pixel-ratio&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nc&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nn&quot;&gt;my-image&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;high&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-n&quot;&gt;png&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;It gets a little more complicated with all of the vendor prefixes mixed
in, especially because of large &lt;a href=&quot;https://developer.mozilla.org/en/CSS/Media_queries#-moz-device-pixel-ratio&quot;&gt;differences in placement&lt;/a&gt; of
&quot;min&quot; and &quot;max&quot; prefixes:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;media&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;only&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;screen&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;and&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;min--moz-device-pixel-ratio&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nc&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;),&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;-o-min-device-pixel-ratio&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;),&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;-webkit-min-device-pixel-ratio&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nc&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;),&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;min-device-pixel-ratio&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nc&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nn&quot;&gt;my-image&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-sx&quot;&gt;high.png&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;With this approach, you regain the benefits of look-ahead parsing, which
was lost with the JavaScript solution. You also gain the flexibility of choosing
your responsive breakpoints (for example, you can have low, mid and high
DPI images), which was lost with the server-side approach.&lt;/p&gt;

&lt;p&gt;Unfortunately it&#39;s still a little unwieldy, and leads to strange looking
CSS or requires preprocessing. Also, this approach is restricted to
CSS properties, so there&#39;s no way to set an &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;img src&amp;gt;&lt;/code&gt;, and your images
must all be elements with a background. Finally, by relying strictly on
device pixel ratio, you can end up in situations where your High-DPI
mobile phone ends up downloading a massive 2x image asset while on an
&lt;a href=&quot;http://en.wikipedia.org/wiki/Enhanced_Data_Rates_for_GSM_Evolution&quot;&gt;EDGE connection&lt;/a&gt;. This isn&#39;t the best user experience.&lt;/p&gt;

&lt;p&gt;As &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;image-set()&lt;/code&gt; is a CSS function, it doesn&#39;t address the problem for
&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tags. Enter &lt;a href=&quot;https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-srcset&quot;&gt;@srcset&lt;/a&gt;, which addresses this issue.
The next section goes deeper into &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;image-set&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;srcset&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;browser_features_for_high_dpi_support&quot; data-text=&quot;Browser features for high DPI support&quot; tabindex=&quot;-1&quot;&gt;Browser features for high DPI support&lt;/h2&gt;

&lt;p&gt;&lt;devsite-iframe&gt;&lt;iframe src=&quot;https://web.dev/frame/articles/high-dpi/index_89f70bacfed2004c7d2264ac6becb40f089c40413c82b3978ab7d40eaaa1eeaa.frame&quot; class=&quot;framebox inherit-locale &quot; allow=&quot;clipboard-write https://web-dot-devsite-v2-prod-3p.appspot.com&quot; allowfullscreen=&quot;&quot; is-upgraded=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;&lt;/devsite-iframe&gt;&lt;/p&gt;

&lt;p&gt;&lt;devsite-iframe&gt;&lt;iframe src=&quot;https://web.dev/frame/articles/high-dpi/index_b01b87951bbf8a6eb11c9c7afd2be43cecbd020fbb1cb0a77e1088f3f8a03d1a.frame&quot; class=&quot;framebox inherit-locale &quot; allow=&quot;clipboard-write https://web-dot-devsite-v2-prod-3p.appspot.com&quot; allowfullscreen=&quot;&quot; is-upgraded=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;&lt;/devsite-iframe&gt;&lt;/p&gt;

&lt;p&gt;Ultimately, how you approach high DPI support depends on your particular
requirements. All of the aforementioned approaches have drawbacks.&lt;/p&gt;

&lt;p&gt;Now that &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;image-set&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;srcset&lt;/code&gt; are widely supported, they&#39;re the best
solutions. There are additional best practices that can bring us closer
for older browsers.&lt;/p&gt;

&lt;p&gt;How are these two different? Well, &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;image-set()&lt;/code&gt; is a CSS
function, appropriate for use as a value of the background CSS property.
&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;srcset&lt;/code&gt; is an attribute specific to &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;img&amp;gt;&lt;/code&gt; elements, with similar syntax.
Both of these tags let you specify image declarations, but the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;srcset&lt;/code&gt;
attribute lets you also configure which image to load based on viewport
size.&lt;/p&gt;

&lt;h3 id=&quot;best_practices_for_image-set&quot; data-text=&quot;Best practices for image-set&quot; tabindex=&quot;-1&quot;&gt;Best practices for image-set&lt;/h3&gt;

&lt;p&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;image-set()&lt;/code&gt; syntax takes one or more comma separated image declarations,
which consist of a URL string or &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;url()&lt;/code&gt; function followed by the appropriate
resolution. For example:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;image-set&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;(&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;image1.jpg&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;1x&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;image2.jpg&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;2x&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-o&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-c&quot;&gt;/* You can also include image-set without `url()` */&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-nt&quot;&gt;image-set&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;(&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;image1.jpg&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;1x&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;image2.jpg&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;2x&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-o&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;This tells the browser is that there are two images to choose from.
One image is optimized for 1x displays and the other for 2x displays.
The browser then gets to choose which one to load, based on a variety of
factors, which might even include network speed, if the browser is smart
enough.&lt;/p&gt;

&lt;p&gt;In addition to loading the correct image, the browser scales it
accordingly. In other words, the browser assumes that 2 images are
twice as large as 1x images, and so scales the 2x image down by a
factor of 2, so that the image appears to be the same size on the page.&lt;/p&gt;

&lt;p&gt;Instead of specifying 1x, 1.5x or Nx, you can also specify a certain
device pixel density in DPI.&lt;/p&gt;

&lt;p&gt;If you&#39;re concerned about older browsers that don&#39;t support the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;image-set&lt;/code&gt;
property, you can add a fallback to ensure an image is displayed. For example:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-c&quot;&gt;/* Fallback support. */&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-nt&quot;&gt;background-image&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;icon1x&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nc&quot;&gt;jpg&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-nt&quot;&gt;background-image&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;image-set&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;(&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;icon1x&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nc&quot;&gt;jpg&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;1x&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;icon2x&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nc&quot;&gt;jpg&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;2x&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-o&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-nt&quot;&gt;image-set&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;(&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;icon1x&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nc&quot;&gt;jpg&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;1x&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;icon2x&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nc&quot;&gt;jpg&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;2x&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-o&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;This sample code loads the appropriate asset in browsers that support
image-set and falls back to the 1x asset otherwise.&lt;/p&gt;

&lt;p&gt;At this point, you may be wondering why not just polyfill (that is,
build a JavaScript shim for) &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;image-set()&lt;/code&gt; and call it a day? As it
turns out, it&#39;s quite difficult to implement efficient polyfills for CSS
functions. (For a detailed explanation why, see this &lt;a href=&quot;http://lists.w3.org/Archives/Public/www-style/2012Jul/0023.html&quot;&gt;www-style
discussion&lt;/a&gt;).&lt;/p&gt;

&lt;h3 id=&quot;image_srcset&quot; data-text=&quot;Image srcset&quot; tabindex=&quot;-1&quot;&gt;Image srcset&lt;/h3&gt;

&lt;p&gt;In addition to the declarations that &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;image-set&lt;/code&gt; provides,
the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;srcset&lt;/code&gt; element also takes width and height values which correspond to the
size of the viewport, attempting to serve the most relevant version.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;HTML&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;img alt=&quot;my awesome image&quot;
  src=&quot;banner.jpeg&quot;
  srcset=&quot;banner-HD.jpeg 2x, banner-phone.jpeg 640w, banner-phone-HD.jpeg 640w 2x&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;This example serves &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;banner-phone.jpeg&lt;/code&gt; to devices with viewport width under
640px, &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;banner-phone-HD.jpeg&lt;/code&gt; to small screen high DPI devices,
&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;banner-HD.jpeg&lt;/code&gt; to high DPI devices with screens greater than 640px, and
&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;banner.jpeg&lt;/code&gt; to everything else.&lt;/p&gt;

&lt;h3 id=&quot;use_image-set_for_image_elements&quot; data-text=&quot;Use image-set for image elements&quot; tabindex=&quot;-1&quot;&gt;Use image-set for image elements&lt;/h3&gt;

&lt;p&gt;It may be tempting to replace your img elements with &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;div&amp;gt;&lt;/code&gt;s
with backgrounds and use the image-set approach. This does work, with
caveats. The drawback here is that the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tag has long-time
semantic value. In practice, this is important for accessibility and
web crawlers.&lt;/p&gt;

&lt;p&gt;You could use the content CSS property, which automatically scales the image
based on &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;devicePixelRation&lt;/code&gt;. For example:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;HTML&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;div id=&quot;my-content-image&quot;
  style=&quot;content: -webkit-image-set(
    url(icon1x.jpg) 1x,
    url(icon2x.jpg) 2x);&quot;&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;h3 id=&quot;polyfill_srcset&quot; data-text=&quot;Polyfill srcset&quot; tabindex=&quot;-1&quot;&gt;Polyfill srcset&lt;/h3&gt;

&lt;p&gt;One handy feature of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;srcset&lt;/code&gt; is that it comes with a natural fallback.
In the case where the srcset attribute is not implemented, all browsers
know to process the src attribute. Also, since it&#39;s just an HTML
attribute, it&#39;s possible to create &lt;a href=&quot;https://github.com/borismus/srcset-polyfill&quot;&gt;polyfills with
JavaScript&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This polyfill comes with &lt;a href=&quot;https://github.com/borismus/srcset-polyfill/blob/master/tests/srcset-tests.js&quot;&gt;unit tests&lt;/a&gt; to ensure that it&#39;s
as close to the &lt;a href=&quot;https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-srcset&quot;&gt;specification&lt;/a&gt; as possible. In addition, there
are checks in place that prevent the polyfill from executing any code if
srcset is implemented natively.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot; data-text=&quot;Conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;The best solution for high-DPI images is to opt for SVGs and CSS. However,
this isn&#39;t always a realistic solution, especially for image-dense websites.&lt;/p&gt;

&lt;p&gt;Approaches in JavaScript, CSS, and server-side solutions have their strengths
and weaknesses. The most promising approach is to use &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;image-set&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;srcset&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To summarize, my recommendations are as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For background images, use &lt;a href=&quot;https://web.dev/articles/high-dpi?hl=en#best_practices_for_image-set&quot;&gt;image-set&lt;/a&gt; with the
appropriate fallbacks for browsers that don&#39;t support it.&lt;/li&gt;
&lt;li&gt;For content images, use a &lt;a href=&quot;https://web.dev/articles/high-dpi?hl=en#polyfilling_srcset&quot;&gt;srcset polyfill&lt;/a&gt;, or
fallback to &lt;a href=&quot;https://web.dev/articles/high-dpi?hl=en#best_practices_for_image-set&quot;&gt;using image-set&lt;/a&gt; (see above).&lt;/li&gt;
&lt;li&gt;For situations where you&#39;re willing to sacrifice image quality,
consider using heavily &lt;a href=&quot;https://web.dev/articles/high-dpi?hl=en#heavily_compressed_hidpi_image&quot;&gt;compressed 2x images&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
  

  
</description><link>https://web.dev/articles/high-dpi?hl=en</link><guid isPermaLink="false">https://web.dev/articles/high-dpi?hl=en</guid><pubDate>Mon, 14 Apr 2025 07:00:00 GMT</pubDate></item><item><title>Optimize Largest Contentful Paint</title><description>&lt;p&gt;A step-by-step guide on how to break down LCP and identify key areas to improve.&lt;/p&gt;

&lt;p&gt;&lt;style&gt;
  .wd-author__links {
    display: flex;
  }
  &lt;/style&gt;

&lt;/p&gt;&lt;p&gt;&lt;/p&gt;

&lt;p&gt;
&lt;/p&gt;&lt;p class=&quot;wd-pubdates&quot; style=&quot;font-size: 90%; border-bottom: 1px solid var(--devsite-ref-palette--grey200);color: var(--devsite-ref-palette--blue-grey700);&quot;&gt;
  Published: April 30, 2020, Last updated: March 31, 2025
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://web.dev/articles/lcp&quot;&gt;Largest Contentful Paint (LCP)&lt;/a&gt; is one of the three &lt;a href=&quot;https://web.dev/articles/vitals#core_web_vitals&quot;&gt;Core Web Vitals&lt;/a&gt; metrics, and it represents how quickly the main content of a web page is loaded. Specifically, LCP measures the time from when the user initiates loading the page until the largest image or text block is rendered within the viewport.&lt;/p&gt;

&lt;p&gt;To provide a good user experience, &lt;strong&gt;sites should strive to have an LCP of 2.5 seconds or less for at least 75% of page visits.&lt;/strong&gt;&lt;/p&gt;

&lt;figure&gt;
  &lt;picture&gt;
    &lt;source srcset=&quot;https://web.dev/static/articles/optimize-lcp/image/good-lcp-values.svg&quot; media=&quot;(min-width: 640px)&quot; width=&quot;800&quot; height=&quot;200&quot;&gt;
    &lt;img src=&quot;https://web.dev/static/articles/optimize-lcp/image/good-lcp-values-are-25-s-1b759cf7034c7.svg&quot; alt=&quot;Good LCP values are 2.5 seconds or less, poor values are greater than 4.0 seconds, and anything in between needs improvement&quot; width=&quot;640&quot; height=&quot;480&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;/picture&gt;
  &lt;figcaption class=&quot;wd-caption&quot;&gt;
    A good LCP value is 2.5 seconds or less.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;A number of factors can affect how quickly the browser can load and render a web page, and delays across any of them can have a significant impact on LCP.&lt;/p&gt;

&lt;p&gt;It&#39;s rare that a quick fix to a single part of a page will result in a meaningful improvement to LCP. To improve LCP, you have to look at the entire loading process and make sure every step along the way is optimized.&lt;/p&gt;

&lt;h2 id=&quot;understanding_your_lcp_metric&quot; data-text=&quot;Understanding your LCP metric&quot; tabindex=&quot;-1&quot;&gt;Understanding your LCP metric&lt;/h2&gt;

&lt;p&gt;Before optimizing LCP, developers should seek to understand if they even have an LCP issue, and the extent of any such issue.&lt;/p&gt;

&lt;p&gt;LCP can be measured in a number of tools and not all of these measure LCP in the same way. To understand LCP of real users, we should look at what real users are experiencing, rather than what a lab-based tool like &lt;a href=&quot;https://developer.chrome.com/docs/lighthouse&quot;&gt;Lighthouse&lt;/a&gt; or local testing shows. These lab-based tools can give a wealth of information to explain and help you improve LCP, but be aware that lab tests alone may not be entirely representative of what your actual users experience.&lt;/p&gt;

&lt;p&gt;LCP data based on real users can be surfaced from Real User Monitoring (RUM) tools installed on a site, or by using the &lt;a href=&quot;https://developer.chrome.com/docs/crux&quot;&gt;Chrome User Experience Report (CrUX)&lt;/a&gt; which collect anonymous data from real Chrome users for millions of websites.&lt;/p&gt;

&lt;h3 id=&quot;using_chrome_devtools_crux_lcp_data&quot; data-text=&quot;Using Chrome DevTools CrUX LCP data&quot; tabindex=&quot;-1&quot;&gt;Using Chrome DevTools CrUX LCP data&lt;/h3&gt;

&lt;p&gt;The Performance panel of Chrome DevTools shows your local LCP experience next to the page or origin&#39;s CrUX LCP in the &lt;a href=&quot;https://developer.chrome.com/docs/devtools/performance/overview#live-metrics&quot;&gt;live metrics view&lt;/a&gt;, and in the &lt;a href=&quot;https://developer.chrome.com/docs/devtools/performance/reference#insights&quot;&gt;Insights&lt;/a&gt; of a performance trace including a breakdown of the LCP subpart timings (that we will explain shortly).&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/optimize-lcp/image/lcp-devtools.png&quot; alt=&quot;Local and field LCP in the Chrome DevTools Performance panel&quot; width=&quot;1200&quot; height=&quot;840&quot; srcset=&quot;https://web.dev/static/articles/optimize-lcp/image/lcp-devtools_36.png 36w,https://web.dev/static/articles/optimize-lcp/image/lcp-devtools_48.png 48w,https://web.dev/static/articles/optimize-lcp/image/lcp-devtools_72.png 72w,https://web.dev/static/articles/optimize-lcp/image/lcp-devtools_96.png 96w,https://web.dev/static/articles/optimize-lcp/image/lcp-devtools_480.png 480w,https://web.dev/static/articles/optimize-lcp/image/lcp-devtools_720.png 720w,https://web.dev/static/articles/optimize-lcp/image/lcp-devtools_856.png 856w,https://web.dev/static/articles/optimize-lcp/image/lcp-devtools_960.png 960w,https://web.dev/static/articles/optimize-lcp/image/lcp-devtools_1440.png 1440w,https://web.dev/static/articles/optimize-lcp/image/lcp-devtools_1920.png 1920w,https://web.dev/static/articles/optimize-lcp/image/lcp-devtools_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption class=&quot;wd-caption&quot;&gt;
    Local and field LCP in the Chrome DevTools Performance panel live metrics and trace views.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;By layering field data onto the Performance panel, you can assess whether a page has any real-user LCP issues and adapt your local environment settings to better reproduce and debug those issues.&lt;/p&gt;

&lt;h3 id=&quot;using_pagespeed_insights_crux_lcp_data&quot; data-text=&quot;Using PageSpeed Insights CrUX LCP data&quot; tabindex=&quot;-1&quot;&gt;Using PageSpeed Insights CrUX LCP data&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://pagespeed.web.dev/&quot;&gt;PageSpeed Insights&lt;/a&gt; provides access to CrUX data in the top section labeled &lt;strong&gt;Discover what your real users are experiencing&lt;/strong&gt;. More detailed lab-based data is available in the bottom section labeled &lt;strong&gt;Diagnose performance issues&lt;/strong&gt;. If CrUX data is available for your website, always concentrate on the real user data first.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/optimize-lcp/image/pagespeed-insights-crux-updated.png&quot; alt=&quot;CrUX data shown in PageSpeed Insights&quot; width=&quot;800&quot; height=&quot;475&quot; srcset=&quot;https://web.dev/static/articles/optimize-lcp/image/pagespeed-insights-crux-updated_36.png 36w,https://web.dev/static/articles/optimize-lcp/image/pagespeed-insights-crux-updated_48.png 48w,https://web.dev/static/articles/optimize-lcp/image/pagespeed-insights-crux-updated_72.png 72w,https://web.dev/static/articles/optimize-lcp/image/pagespeed-insights-crux-updated_96.png 96w,https://web.dev/static/articles/optimize-lcp/image/pagespeed-insights-crux-updated_480.png 480w,https://web.dev/static/articles/optimize-lcp/image/pagespeed-insights-crux-updated_720.png 720w,https://web.dev/static/articles/optimize-lcp/image/pagespeed-insights-crux-updated_856.png 856w,https://web.dev/static/articles/optimize-lcp/image/pagespeed-insights-crux-updated_960.png 960w,https://web.dev/static/articles/optimize-lcp/image/pagespeed-insights-crux-updated_1440.png 1440w,https://web.dev/static/articles/optimize-lcp/image/pagespeed-insights-crux-updated_1920.png 1920w,https://web.dev/static/articles/optimize-lcp/image/pagespeed-insights-crux-updated_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption class=&quot;wd-caption&quot;&gt;
    CrUX data shown in PageSpeed Insights.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;aside class=&quot;note&quot;&gt;
  &lt;b&gt;Note:&lt;/b&gt; Where CrUX does not provide data (for example, a page with insufficient traffic to get page-level data), CrUX should be supplemented with RUM data collected using JavaScript APIs running on the web page. This can also provide much more data than CrUX can expose as a public dataset. Later in this guide we will explain how to collect this data using JavaScript.
&lt;/aside&gt;

&lt;p&gt;PageSpeed Insights shows up to four different CrUX data:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Mobile&lt;/strong&gt; data for &lt;strong&gt;This URL&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Desktop&lt;/strong&gt; data for &lt;strong&gt;This URL&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mobile&lt;/strong&gt; data for the whole &lt;strong&gt;Origin&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Desktop&lt;/strong&gt; data for the whole &lt;strong&gt;Origin&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can toggle these in the controls at the top and top right-hand side of this section. If a URL doesn&#39;t have enough data to be shown at the URL level, but does have data for the origin, PageSpeed Insights always shows the origin data.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/optimize-lcp/image/url-level-data-unavailable.png&quot; alt=&quot;PageSpeed Insight falling back to origin-level data where url-level data is not available&quot; width=&quot;800&quot; height=&quot;257&quot; srcset=&quot;https://web.dev/static/articles/optimize-lcp/image/url-level-data-unavailable_36.png 36w,https://web.dev/static/articles/optimize-lcp/image/url-level-data-unavailable_48.png 48w,https://web.dev/static/articles/optimize-lcp/image/url-level-data-unavailable_72.png 72w,https://web.dev/static/articles/optimize-lcp/image/url-level-data-unavailable_96.png 96w,https://web.dev/static/articles/optimize-lcp/image/url-level-data-unavailable_480.png 480w,https://web.dev/static/articles/optimize-lcp/image/url-level-data-unavailable_720.png 720w,https://web.dev/static/articles/optimize-lcp/image/url-level-data-unavailable_856.png 856w,https://web.dev/static/articles/optimize-lcp/image/url-level-data-unavailable_960.png 960w,https://web.dev/static/articles/optimize-lcp/image/url-level-data-unavailable_1440.png 1440w,https://web.dev/static/articles/optimize-lcp/image/url-level-data-unavailable_1920.png 1920w,https://web.dev/static/articles/optimize-lcp/image/url-level-data-unavailable_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption class=&quot;wd-caption&quot;&gt;
    When PageSpeed Insights doesn&#39;t have URL-level data, it shows
    origin-level data.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The LCP for the whole origin may be very different to an individual page&#39;s LCP depending on how the LCP is loaded on that page compared to other pages on that origin. It can also be affected by how visitors navigate to these pages. Home pages tend to be visted by new users and so may often be loaded &quot;cold&quot;, without any cached content and so are often the slowest pages on a website.&lt;/p&gt;

&lt;p&gt;Looking at the four different categories of CrUX data can help you understand whether an LCP issue is specific to this page, or a more general site-wide issue. Similarly, it can show which device types have LCP issues.&lt;/p&gt;

&lt;h3 id=&quot;using_pagespeed_insights_crux_supplementary_metrics&quot; data-text=&quot;Using PageSpeed Insights CrUX supplementary metrics&quot; tabindex=&quot;-1&quot;&gt;Using PageSpeed Insights CrUX supplementary metrics&lt;/h3&gt;

&lt;p&gt;Those looking to optimize LCP should also use the &lt;a href=&quot;https://web.dev/articles/fcp&quot;&gt;First Contentful Paint (FCP)&lt;/a&gt; and &lt;a href=&quot;https://web.dev/articles/ttfb&quot;&gt;Time to First Byte (TTFB)&lt;/a&gt; timings, which are good diagnostic metrics that can provide valuable insights into LCP.&lt;/p&gt;

&lt;p&gt;TTFB is the time when the visitor starting to navigate to a page (for example, clicking on a link), until the first bytes of the HTML document are received. A high TTFB can make achieving a 2.5 second LCP challenging, or even impossible.&lt;/p&gt;

&lt;p&gt;A high TTFB can be due to multiple server redirects, visitors located far away from the nearest site server, visitors on poor network conditions, or an inability to use cached content due to query parameters.&lt;/p&gt;

&lt;p&gt;Once a page starts rendering, there may be an initial paint (for example, the background color), followed by some content appearing (for example, the site header). The appearance of the initial content is measured by FCP. The delta between FCP and other metrics can be very telling.&lt;/p&gt;

&lt;p&gt;A large delta between TTFB and FCP could indicate that the browser needs to download a lot of render-blocking assets. It can also be a sign it must complete a lot of work to render any meaningful content—a classic sign of a site that relies heavily on client-side rendering.&lt;/p&gt;

&lt;p&gt;A large delta between FCP and LCP indicates that the LCP resource is either not immediately available for the browser to prioritize (for example, text or images that are managed by JavaScript rather than being available in the initial HTML), or that the browser is completing other work before it can display the LCP content.&lt;/p&gt;

&lt;h3 id=&quot;using_pagespeed_insights_lighthouse_data&quot; data-text=&quot;Using PageSpeed Insights Lighthouse data&quot; tabindex=&quot;-1&quot;&gt;Using PageSpeed Insights Lighthouse data&lt;/h3&gt;

&lt;p&gt;The Lighthouse section of PageSpeed Insights offers some guidance to improving LCP, but first you should check if the LCP given is broadly in agreement with real user data provided by CrUX. If Lighthouse and CrUX disagree, then CrUX likely provides a more accurate picture of your user experience. Make sure your CrUX data is for your page, not the full origin, before you act on it.&lt;/p&gt;

&lt;p&gt;If both Lighthouse and CrUX show LCP values that need improvement, the Lighthouse section can provide valuable guidance on ways to improve LCP. Use the LCP filter to only show audits relevant to LCP as follows:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/optimize-lcp/image/psi-lighthouse-lcp-opportunities.png&quot; alt=&quot;Lighthouse LCP Opportunities and Diagnostics&quot; width=&quot;800&quot; height=&quot;498&quot; srcset=&quot;https://web.dev/static/articles/optimize-lcp/image/psi-lighthouse-lcp-opportunities_36.png 36w,https://web.dev/static/articles/optimize-lcp/image/psi-lighthouse-lcp-opportunities_48.png 48w,https://web.dev/static/articles/optimize-lcp/image/psi-lighthouse-lcp-opportunities_72.png 72w,https://web.dev/static/articles/optimize-lcp/image/psi-lighthouse-lcp-opportunities_96.png 96w,https://web.dev/static/articles/optimize-lcp/image/psi-lighthouse-lcp-opportunities_480.png 480w,https://web.dev/static/articles/optimize-lcp/image/psi-lighthouse-lcp-opportunities_720.png 720w,https://web.dev/static/articles/optimize-lcp/image/psi-lighthouse-lcp-opportunities_856.png 856w,https://web.dev/static/articles/optimize-lcp/image/psi-lighthouse-lcp-opportunities_960.png 960w,https://web.dev/static/articles/optimize-lcp/image/psi-lighthouse-lcp-opportunities_1440.png 1440w,https://web.dev/static/articles/optimize-lcp/image/psi-lighthouse-lcp-opportunities_1920.png 1920w,https://web.dev/static/articles/optimize-lcp/image/psi-lighthouse-lcp-opportunities_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption class=&quot;wd-caption&quot;&gt;
    Lighthouse diagnostics and suggestions for improving LCP.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;As well as the &lt;strong&gt;Opportunities&lt;/strong&gt; to improve, there is &lt;strong&gt;Diagnostic&lt;/strong&gt; information that may provide more information to help diagnose the issue. The &lt;strong&gt;Largest Contentful Paint element&lt;/strong&gt; diagnostic shows a useful breakdown of the various timings that made up the LCP:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/optimize-lcp/image/lighthouse-lcp-subparts.png&quot; alt=&quot;LCP subparts in Lighthouse&quot; width=&quot;800&quot; height=&quot;474&quot; srcset=&quot;https://web.dev/static/articles/optimize-lcp/image/lighthouse-lcp-subparts_36.png 36w,https://web.dev/static/articles/optimize-lcp/image/lighthouse-lcp-subparts_48.png 48w,https://web.dev/static/articles/optimize-lcp/image/lighthouse-lcp-subparts_72.png 72w,https://web.dev/static/articles/optimize-lcp/image/lighthouse-lcp-subparts_96.png 96w,https://web.dev/static/articles/optimize-lcp/image/lighthouse-lcp-subparts_480.png 480w,https://web.dev/static/articles/optimize-lcp/image/lighthouse-lcp-subparts_720.png 720w,https://web.dev/static/articles/optimize-lcp/image/lighthouse-lcp-subparts_856.png 856w,https://web.dev/static/articles/optimize-lcp/image/lighthouse-lcp-subparts_960.png 960w,https://web.dev/static/articles/optimize-lcp/image/lighthouse-lcp-subparts_1440.png 1440w,https://web.dev/static/articles/optimize-lcp/image/lighthouse-lcp-subparts_1920.png 1920w,https://web.dev/static/articles/optimize-lcp/image/lighthouse-lcp-subparts_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption class=&quot;wd-caption&quot;&gt;
    Lighthouse&#39;s breakdown of LCP elements.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;LCP resource types and subparts are also &lt;a href=&quot;https://developer.chrome.com/blog/crux-2025-02&quot;&gt;available in CrUX&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We will delve into these subparts next.&lt;/p&gt;

&lt;h2 id=&quot;lcp-breakdown&quot; data-text=&quot;LCP breakdown&quot; tabindex=&quot;-1&quot;&gt;LCP breakdown&lt;/h2&gt;

&lt;p&gt;Optimizing for LCP can be a more complex task when PageSpeed Insights does not give you the answer on how to improve this metric. With complex tasks it&#39;s generally better to break them down into smaller, more manageable tasks and address each separately.&lt;/p&gt;

&lt;p&gt;This section presents a methodology for how to break down LCP into its most critical subparts and then present specific recommendations and best practices for how to optimize each part.&lt;/p&gt;

&lt;aside class=&quot;note&quot;&gt;
  &lt;p&gt;
    &lt;b&gt;Note:&lt;/b&gt; For a visual overview of the context presented in this guide, see &lt;a href=&quot;https://youtu.be/fWoI9DXmpdk&quot;&gt;A Deep Dive into Optimizing LCP&lt;/a&gt; from Google I/O &#39;22:
  &lt;/p&gt;
  &lt;devsite-video video-id=&quot;fWoI9DXmpdk&quot;&gt;&lt;/devsite-video&gt;
&lt;/aside&gt;

&lt;p&gt;Most page loads typically include a number of network requests, but for the purposes of identifying opportunities to improve LCP, you should start by looking at just two:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The initial HTML document&lt;/li&gt;
&lt;li&gt;The LCP resource (if applicable)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;While other requests on the page can affect LCP, these two requests—specifically the times when the LCP resource begins and ends—reveal whether or not your page is optimized for LCP.&lt;/p&gt;

&lt;p&gt;To identify the LCP resource, you can use developer tools (such as PageSpeed Insights discussed before, &lt;a href=&quot;https://developer.chrome.com/docs/devtools&quot;&gt;Chrome DevTools&lt;/a&gt;, or &lt;a href=&quot;https://webpagetest.org/&quot;&gt;WebPageTest&lt;/a&gt;) to determine the &lt;a href=&quot;https://web.dev/articles/lcp#what_elements_are_considered&quot;&gt;LCP element&lt;/a&gt;. From there you can match the URL (again, if applicable) loaded by the element on a &lt;a href=&quot;https://developer.chrome.com/docs/devtools/network/reference&quot;&gt;network waterfall&lt;/a&gt; of all resources loaded by the page.&lt;/p&gt;

&lt;p&gt;For example, the following visualization shows these resources highlighted on a network waterfall diagram from a typical page load, where the LCP element requires an image request to render.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-the-919387402b1d3.png&quot; alt=&quot;A network waterfall with the HTML and LCP resources highlighted&quot; width=&quot;800&quot; height=&quot;375&quot; srcset=&quot;https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-the-919387402b1d3_36.png 36w,https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-the-919387402b1d3_48.png 48w,https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-the-919387402b1d3_72.png 72w,https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-the-919387402b1d3_96.png 96w,https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-the-919387402b1d3_480.png 480w,https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-the-919387402b1d3_720.png 720w,https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-the-919387402b1d3_856.png 856w,https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-the-919387402b1d3_960.png 960w,https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-the-919387402b1d3_1440.png 1440w,https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-the-919387402b1d3_1920.png 1920w,https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-the-919387402b1d3_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption class=&quot;wd-caption&quot;&gt;
    A waterfall diagram showing the loading times for
    a webpage&#39;s HTML and the resources the LCP needs.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;For a well-optimized page, you want your LCP resource request to start loading as early as it can, and you want the LCP element to render as quickly as possible after the LCP resource finishes loading. To help visualize whether or not a particular page is following this principle, you can break down the total LCP time into the following subparts:&lt;/p&gt;

&lt;dl&gt;
&lt;dt&gt;&lt;a href=&quot;https://web.dev/articles/ttfb&quot;&gt;Time to First Byte (TTFB)&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;The time from when the user initiates loading the page until the browser
receives the first byte of the HTML document response.&lt;/dd&gt;
&lt;dt&gt;Resource load delay&lt;/dt&gt;
&lt;dd&gt;The time between TTFB and when the browser starts loading the LCP resource. If
the LCP element doesn&#39;t require a resource load to render (for example, if the
element is a text node rendered with a system font), this time is 0.&lt;/dd&gt;
&lt;dt&gt;Resource load duration&lt;/dt&gt;
&lt;dd&gt;The duration of time it takes to load the LCP resource itself. If the LCP
element doesn&#39;t require a resource load to render, this time is 0.&lt;/dd&gt;
&lt;dt&gt;Element render delay&lt;/dt&gt;
&lt;dd&gt;The time between when the LCP resource finishes loading and the LCP element
rendering fully.&lt;/dd&gt;
&lt;/dl&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/optimize-lcp/image/lcp-breakdown-four-subparts.png&quot; alt=&quot;A breakdown of LCP showing the four subparts&quot; width=&quot;800&quot; height=&quot;450&quot; srcset=&quot;https://web.dev/static/articles/optimize-lcp/image/lcp-breakdown-four-subparts_36.png 36w,https://web.dev/static/articles/optimize-lcp/image/lcp-breakdown-four-subparts_48.png 48w,https://web.dev/static/articles/optimize-lcp/image/lcp-breakdown-four-subparts_72.png 72w,https://web.dev/static/articles/optimize-lcp/image/lcp-breakdown-four-subparts_96.png 96w,https://web.dev/static/articles/optimize-lcp/image/lcp-breakdown-four-subparts_480.png 480w,https://web.dev/static/articles/optimize-lcp/image/lcp-breakdown-four-subparts_720.png 720w,https://web.dev/static/articles/optimize-lcp/image/lcp-breakdown-four-subparts_856.png 856w,https://web.dev/static/articles/optimize-lcp/image/lcp-breakdown-four-subparts_960.png 960w,https://web.dev/static/articles/optimize-lcp/image/lcp-breakdown-four-subparts_1440.png 1440w,https://web.dev/static/articles/optimize-lcp/image/lcp-breakdown-four-subparts_1920.png 1920w,https://web.dev/static/articles/optimize-lcp/image/lcp-breakdown-four-subparts_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption&gt;
    The same waterfall diagram, with the four LCP subparts overlaid
    on the timeline.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Every single page can have its LCP value broken down into these four subparts. There is no overlap or gap between them. Collectively, they add up to the full LCP time.&lt;/p&gt;

&lt;p&gt;When optimizing LCP, it&#39;s helpful to try to optimize these subparts individually. But it&#39;s also important to keep in mind that you need to optimize all of them. In some cases, an optimization applied to one part won&#39;t improve LCP, it will just shift the time saved to another part.&lt;/p&gt;

&lt;p&gt;For example, in the earlier network waterfall, if you reduced the file size of our image by compressing it more or switching to a more optimal format (such as AVIF or WebP), that would reduce the &lt;strong&gt;resource load duration&lt;/strong&gt;, but it wouldn&#39;t actually improve LCP because the time would just shift to the &lt;strong&gt;element render delay&lt;/strong&gt; subpart:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/optimize-lcp/image/the-same-lcp-breakdown-render-delay.png&quot; alt=&quot;The same breakdown of LCP shown earlier where the resource load duration subpart is shortened, but the overall LCP time remains the same.&quot; width=&quot;800&quot; height=&quot;450&quot; srcset=&quot;https://web.dev/static/articles/optimize-lcp/image/the-same-lcp-breakdown-render-delay_36.png 36w,https://web.dev/static/articles/optimize-lcp/image/the-same-lcp-breakdown-render-delay_48.png 48w,https://web.dev/static/articles/optimize-lcp/image/the-same-lcp-breakdown-render-delay_72.png 72w,https://web.dev/static/articles/optimize-lcp/image/the-same-lcp-breakdown-render-delay_96.png 96w,https://web.dev/static/articles/optimize-lcp/image/the-same-lcp-breakdown-render-delay_480.png 480w,https://web.dev/static/articles/optimize-lcp/image/the-same-lcp-breakdown-render-delay_720.png 720w,https://web.dev/static/articles/optimize-lcp/image/the-same-lcp-breakdown-render-delay_856.png 856w,https://web.dev/static/articles/optimize-lcp/image/the-same-lcp-breakdown-render-delay_960.png 960w,https://web.dev/static/articles/optimize-lcp/image/the-same-lcp-breakdown-render-delay_1440.png 1440w,https://web.dev/static/articles/optimize-lcp/image/the-same-lcp-breakdown-render-delay_1920.png 1920w,https://web.dev/static/articles/optimize-lcp/image/the-same-lcp-breakdown-render-delay_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption class=&quot;wd-caption&quot;&gt;
    Shortening the resource load duration increases the element render delay without reducing LCP.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The reason this happens is because, on this page, the LCP element is hidden until the JavaScript code finishes loading, and then everything is revealed at once.&lt;/p&gt;

&lt;p&gt;This example helps illustrate the point that you need to optimize all of these subparts in order to achieve the best LCP outcomes.&lt;/p&gt;

&lt;h3 id=&quot;optimal_sub-part_times&quot; data-text=&quot;Optimal subpart times&quot; tabindex=&quot;-1&quot;&gt;Optimal subpart times&lt;/h3&gt;

&lt;p&gt;In order to optimize each subpart of LCP, it&#39;s important to understand what the ideal breakdown of these subparts is on a well-optimized page.&lt;/p&gt;

&lt;p&gt;Of the four subparts, two have the word &quot;delay&quot; in their names. That is a clue that you want to get these times as close to zero as possible. The other two parts involve network requests, which by their very nature take time.&lt;/p&gt;

&lt;div class=&quot;table-wrapper scrollbar&quot;&gt;
  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;LCP subpart&lt;/th&gt;
        &lt;th&gt;% of LCP&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;Time to first byte&lt;/td&gt;
        &lt;td&gt;~40%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;Resource load delay&lt;/td&gt;
        &lt;td&gt;&amp;lt;10%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;Resource load duration&lt;/td&gt;
        &lt;td&gt;~40%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;Element render delay&lt;/td&gt;
        &lt;td&gt;&amp;lt;10%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;strong&gt;TOTAL&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;100%&lt;/strong&gt;&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;
&lt;/div&gt;

&lt;p&gt;Note that these time breakdowns are guidelines, not strict rules. If the LCP times on your pages are consistently within 2.5 seconds, then it doesn&#39;t really matter what the relative proportions are. But if you&#39;re spending a lot of unnecessary time in either of the &quot;delay&quot; portions, then it will be very difficult to constantly hit the &lt;a href=&quot;https://web.dev/articles/lcp#what_is_a_good_lcp_score&quot;&gt;2.5 second target&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A good way to think about the breakdown of LCP time is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;vast majority&lt;/strong&gt; of the LCP time should be spent loading the HTML document and LCP source.&lt;/li&gt;
&lt;li&gt;Any time before LCP where one of these two resources is &lt;em&gt;not&lt;/em&gt; loading is &lt;strong&gt;an opportunity to improve&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;aside class=&quot;warning&quot;&gt;
  &lt;b&gt;Warning: &lt;/b&gt; Given the &lt;a href=&quot;https://web.dev/articles/lcp#what_is_a_good_lcp_score&quot;&gt;2.5 second target&lt;/a&gt; for LCP, it may be tempting to try to convert these percentages into absolute numbers, but that is not recommended. These subparts are only meaningful relative to each other, so it&#39;s best to always measure them that way.
&lt;/aside&gt;

&lt;h2 id=&quot;how-to-optimize-each-part&quot; data-text=&quot;How to optimize each part&quot; tabindex=&quot;-1&quot;&gt;How to optimize each part&lt;/h2&gt;

&lt;p&gt;Now that you understand how each of the LCP subpart times should break down on a well-optimized page, you can start optimizing your own pages.&lt;/p&gt;

&lt;p&gt;The next four sections will present recommendations and best practices for how to optimize each part. They&#39;re presented in order, starting with the optimizations that are likely to have the biggest impact.&lt;/p&gt;

&lt;h3 id=&quot;1_eliminate_resource_load_delay&quot; data-text=&quot;1. Eliminate resource load delay&quot; tabindex=&quot;-1&quot;&gt;1. Eliminate &lt;em&gt;resource load delay&lt;/em&gt;&lt;/h3&gt;

&lt;p&gt;The goal in this step is to ensure the LCP resource starts loading as early as possible. While in theory the earliest a resource &lt;em&gt;could&lt;/em&gt; start loading is immediately after TTFB, in practice there is always some delay before browsers actually start loading resources.&lt;/p&gt;

&lt;p&gt;A good rule of thumb is that your LCP resource should start loading at the same time as the first resource loaded by that page. Or, to put that another way, if the LCP resource starts loading later than the first resource, then there&#39;s opportunity for improvement.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-diagr-1ee19fc20ee1f.png&quot; alt=&quot;A network waterfall diagram showing the LCP resource starting after the first resource, showing the opportunity for improvement&quot; width=&quot;800&quot; height=&quot;375&quot; srcset=&quot;https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-diagr-1ee19fc20ee1f_36.png 36w,https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-diagr-1ee19fc20ee1f_48.png 48w,https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-diagr-1ee19fc20ee1f_72.png 72w,https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-diagr-1ee19fc20ee1f_96.png 96w,https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-diagr-1ee19fc20ee1f_480.png 480w,https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-diagr-1ee19fc20ee1f_720.png 720w,https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-diagr-1ee19fc20ee1f_856.png 856w,https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-diagr-1ee19fc20ee1f_960.png 960w,https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-diagr-1ee19fc20ee1f_1440.png 1440w,https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-diagr-1ee19fc20ee1f_1920.png 1920w,https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-diagr-1ee19fc20ee1f_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption class=&quot;wd-caption&quot;&gt;
    On this page, the LCP resource starts loading well after the style
    sheet that loads first. There&#39;s room for improvement here.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Generally speaking, there are two factors that affect how quickly an LCP resource can be loading:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When the resource is discovered.&lt;/li&gt;
&lt;li&gt;What priority the resource is given.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;optimize-when-the-resource-is-discovered&quot; data-text=&quot;Optimize when the resource is discovered&quot; tabindex=&quot;-1&quot;&gt;Optimize when the resource is discovered&lt;/h4&gt;

&lt;p&gt;To ensure your LCP resource starts loading as early as possible, it&#39;s critical that the resource is discoverable in the initial HTML document response by the browser&#39;s &lt;a href=&quot;https://web.dev/articles/preload-scanner&quot;&gt;preload scanner&lt;/a&gt;. For example, in the following cases, the browser can discover the LCP resource by scanning the HTML document response:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The LCP element is an &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;img&amp;gt;&lt;/code&gt; element, and its &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;src&lt;/code&gt; or &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;srcset&lt;/code&gt; attributes are present in the initial HTML markup.&lt;/li&gt;
&lt;li&gt;The LCP element requires a &lt;a href=&quot;https://web.dev/articles/optimize-css-background-images-with-media-queries&quot;&gt;CSS background image&lt;/a&gt;, but that image is preloaded using &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;link rel=&quot;preload&quot;&amp;gt;&lt;/code&gt; in the HTML markup (or using a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;Link&lt;/code&gt; header).&lt;/li&gt;
&lt;li&gt;The LCP element is a text node that requires a web font to render, and the font is loaded using &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;link rel=&quot;preload&quot;&amp;gt;&lt;/code&gt; in the HTML markup (or using a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;Link&lt;/code&gt; header).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here are some examples where the LCP resource cannot be discovered from scanning the HTML document response:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The LCP element is an &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;img&amp;gt;&lt;/code&gt; that is dynamically added to the page using JavaScript.&lt;/li&gt;
&lt;li&gt;The LCP element is lazily loaded with a JavaScript library that hides its &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;src&lt;/code&gt; or &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;srcset&lt;/code&gt; attributes (often as &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;data-src&lt;/code&gt; or &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;data-srcset&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;The LCP element requires a CSS background image.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In each of these cases, the browser needs to run the script or apply the stylesheet—which usually involves waiting for network requests to finish—before it can discover the LCP resource and could start loading it. This is never optimal.&lt;/p&gt;

&lt;p&gt;To eliminate unnecessary resource load delay, your LCP resource should be discoverable from the HTML source. In cases where the resource is only referenced from an external CSS or JavaScript file, the LCP resource should be preloaded with a high &lt;a href=&quot;https://web.dev/articles/optimize-lcp?hl=en#optimize_the_priority_the_resource_is_given&quot;&gt;fetch priority&lt;/a&gt;, for example:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;HTML&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;!-- Load the stylesheet that will reference the LCP image. --&amp;gt;
&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;/path/to/styles.css&quot;&amp;gt;

&amp;lt;!-- Preload the LCP image with a high fetchpriority so it starts loading with the stylesheet. --&amp;gt;
&amp;lt;link rel=&quot;preload&quot; fetchpriority=&quot;high&quot; as=&quot;image&quot; href=&quot;/path/to/hero-image.webp&quot; type=&quot;image/webp&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;aside class=&quot;warning&quot;&gt;
  &lt;b&gt;Warning:&lt;/b&gt; On most pages, ensuring that the LCP resource starts loading at the same time as the first resource is good enough, but be aware that it is possible to construct a page where none of the resources are discovered early and all of them start loading significantly later than TTFB. So while comparing with the first resource is a good way to identify opportunities to improve, it may not be sufficient in some cases, so it&#39;s still important to measure this time relative to TTFB and ensure it remains small.
&lt;/aside&gt;

&lt;h4 id=&quot;optimize_the_priority_the_resource_is_given&quot; data-text=&quot;Optimize the priority the resource is given&quot; tabindex=&quot;-1&quot;&gt;Optimize the priority the resource is given&lt;/h4&gt;

&lt;p&gt;Even if the LCP resource is discoverable from the HTML markup, it &lt;em&gt;still&lt;/em&gt; may not start loading as early as the first resource. This can happen if the browser preload scanner&#39;s priority heuristics don&#39;t recognize that the resource is important, or if it determines that other resources are more important.&lt;/p&gt;

&lt;p&gt;For example, you can delay your LCP image using HTML if you set &lt;a href=&quot;https://web.dev/articles/browser-level-image-lazy-loading&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;loading=&quot;lazy&quot;&lt;/code&gt;&lt;/a&gt; on your &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;img&amp;gt;&lt;/code&gt; element. Using lazy loading means that the resource won&#39;t be loaded until after layout confirms the image is in the viewport and so may begin loading later than it otherwise would.&lt;/p&gt;

&lt;aside class=&quot;warning&quot;&gt;
  &lt;b&gt;Warning:&lt;/b&gt; Never lazy-load your LCP image, as that will always lead to unnecessary resource load delay, and will have a negative impact on LCP.
&lt;/aside&gt;

&lt;p&gt;Even without lazy loading, images are not initially loaded with the highest priority by browsers as they are not render-blocking resources. You can hint to the browser as to which resources are most important using the &lt;a href=&quot;https://web.dev/articles/fetch-priority&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;fetchpriority&lt;/code&gt;&lt;/a&gt; attribute for resources that could benefit from a higher priority:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;HTML&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;img fetchpriority=&quot;high&quot; src=&quot;/path/to/hero-image.webp&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;It&#39;s a good idea to set &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;fetchpriority=&quot;high&quot;&lt;/code&gt; on an &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;img&amp;gt;&lt;/code&gt; element if you think it&#39;s likely to be your page&#39;s LCP element. However, setting a high priority on more than one or two images makes priority setting unhelpful in reducing LCP.&lt;/p&gt;

&lt;p&gt;You can also lower the priority of images that might be early in the document response but aren&#39;t visible due to styling, such as images in carousel slides that aren&#39;t visible at startup:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;HTML&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;img fetchpriority=&quot;low&quot; src=&quot;/path/to/carousel-slide-3.webp&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;Deprioritizing certain resources can afford more bandwidth to resources that need it more—but be careful. Always check resource priority in DevTools and test changes with lab and field tools.&lt;/p&gt;

&lt;p&gt;After you have optimized your LCP resource priority and discovery time, your network waterfall should look like this (with the LCP resource starting at the same time as the first resource):&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-diagr-b6906b9fce22.png&quot; alt=&quot;A network waterfall diagram showing the LCP resource now starting at the same time as the first resource&quot; width=&quot;800&quot; height=&quot;375&quot; srcset=&quot;https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-diagr-b6906b9fce22_36.png 36w,https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-diagr-b6906b9fce22_48.png 48w,https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-diagr-b6906b9fce22_72.png 72w,https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-diagr-b6906b9fce22_96.png 96w,https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-diagr-b6906b9fce22_480.png 480w,https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-diagr-b6906b9fce22_720.png 720w,https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-diagr-b6906b9fce22_856.png 856w,https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-diagr-b6906b9fce22_960.png 960w,https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-diagr-b6906b9fce22_1440.png 1440w,https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-diagr-b6906b9fce22_1920.png 1920w,https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-diagr-b6906b9fce22_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption class=&quot;wd-caption&quot;&gt;
    The LCP resource now starts loading at the same time as the style sheet.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;aside class=&quot;key-point&quot; id=&quot;different-origin&quot;&gt;
  &lt;b&gt;Key point:&lt;/b&gt; Another reason your LCP resource may not start loading as early as possible—even when it&#39;s discoverable from the HTML source—is if it&#39;s hosted on a different origin, as these requests require the browser to connect to that origin before the resource can start loading. When possible, it&#39;s a good idea to host critical resources on the same origin as your HTML document resource because then those resources can save time by reusing the existing connection (more on this point later).
&lt;/aside&gt;

&lt;h3 id=&quot;2_eliminate_element_render_delay&quot; data-text=&quot;2. Eliminate element render delay&quot; tabindex=&quot;-1&quot;&gt;2. Eliminate &lt;em&gt;element render delay&lt;/em&gt;&lt;/h3&gt;

&lt;p&gt;The goal in this step is to ensure the LCP element can render &lt;em&gt;immediately&lt;/em&gt; after its resource has finished loading, no matter when that happens.&lt;/p&gt;

&lt;p&gt;The primary reason the LCP element &lt;em&gt;wouldn&#39;t&lt;/em&gt; be able to render immediately after its resource finishes loading is if rendering is &lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/render-blocking-resources&quot;&gt;blocked&lt;/a&gt; for some other reason:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rendering of the entire page is blocked due to stylesheets or synchronous scripts in the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;head&amp;gt;&lt;/code&gt; that are still loading.&lt;/li&gt;
&lt;li&gt;The LCP resource has finished loading, but the LCP element has not yet been added to the DOM (it&#39;s waiting for some JavaScript code to load).&lt;/li&gt;
&lt;li&gt;The element is being hidden by some other code, such as an A/B testing library that&#39;s still determining what experiment the user should be in.&lt;/li&gt;
&lt;li&gt;The main thread is blocked due to &lt;a href=&quot;https://web.dev/articles/long-tasks-devtools#what_are_long_tasks&quot;&gt;long tasks&lt;/a&gt;, and rendering work needs to wait until those long tasks complete.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The following sections explain how to address the most common causes of unnecessary element render delay.&lt;/p&gt;

&lt;h4 id=&quot;reduce_or_inline_render-blocking_stylesheets&quot; data-text=&quot;Reduce or inline render-blocking stylesheets&quot; tabindex=&quot;-1&quot;&gt;Reduce or inline render-blocking stylesheets&lt;/h4&gt;

&lt;p&gt;Style sheets loaded from the HTML markup will block rendering of all content that follows them, which is good, since you generally don&#39;t want to render unstyled HTML. However, if the style sheet is so large that it takes significantly longer to load than the LCP resource, then it will prevent the LCP element from rendering—even after its resource has finished loading, as shown in this example:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-diagr-42b740846d30d.png&quot; alt=&quot;A network waterfall diagram showing a large CSS file blocking rendering of the LCP element because it takes longer to load than the LCP resource&quot; width=&quot;800&quot; height=&quot;450&quot; srcset=&quot;https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-diagr-42b740846d30d_36.png 36w,https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-diagr-42b740846d30d_48.png 48w,https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-diagr-42b740846d30d_72.png 72w,https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-diagr-42b740846d30d_96.png 96w,https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-diagr-42b740846d30d_480.png 480w,https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-diagr-42b740846d30d_720.png 720w,https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-diagr-42b740846d30d_856.png 856w,https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-diagr-42b740846d30d_960.png 960w,https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-diagr-42b740846d30d_1440.png 1440w,https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-diagr-42b740846d30d_1920.png 1920w,https://web.dev/static/articles/optimize-lcp/image/a-network-waterfall-diagr-42b740846d30d_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption class=&quot;wd-caption&quot;&gt;
    The image and the style sheet start loading at the same time, but the image can&#39;t render until the style sheet is ready.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;To fix this, your options are to either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;inline the style sheet into the HTML to avoid the additional network request; or,&lt;/li&gt;
&lt;li&gt;reduce the size of the style sheet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In general, inlining your style sheet is only recommended if your style sheet is small since inlined content in the HTML cannot benefit from caching in subsequent page loads. If a style sheet is so large that it takes longer to load than the LCP resource, then it&#39;s unlikely to be a good candidate for inlining.&lt;/p&gt;

&lt;p&gt;In most cases, the best way to ensure the style sheet does not block rendering of the LCP element is to reduce its size so that it&#39;s smaller than the LCP resource. This should ensure it&#39;s not a bottleneck for most visits.&lt;/p&gt;

&lt;p&gt;Some recommendations to reduce the size of the style sheet are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/unused-css-rules&quot;&gt;Remove unused CSS&lt;/a&gt;: use Chrome DevTools to find CSS rules that aren&#39;t being used and can potentially be removed (or deferred).&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/articles/defer-non-critical-css&quot;&gt;Defer non-critical CSS&lt;/a&gt;: split your style sheet out into styles that are required for initial page load and then styles that can be loaded lazily.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/articles/reduce-network-payloads-using-text-compression&quot;&gt;Minify and compress CSS&lt;/a&gt;: for styles that are critical, make sure you&#39;re reducing their &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/transferSize&quot;&gt;transfer size&lt;/a&gt; as much as possible.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;defer_or_inline_render-blocking_javascript&quot; data-text=&quot;Defer or inline render-blocking JavaScript&quot; tabindex=&quot;-1&quot;&gt;Defer or inline render-blocking JavaScript&lt;/h4&gt;

&lt;p&gt;It is almost never necessary to add synchronous scripts (scripts without the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;async&lt;/code&gt; or &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;defer&lt;/code&gt; attributes) to the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;head&amp;gt;&lt;/code&gt; of your pages, and doing so will almost always have a negative impact on performance.&lt;/p&gt;

&lt;p&gt;In cases where JavaScript code needs to run as early as possible in the page load, it&#39;s best to inline it so rendering isn&#39;t delayed waiting on another network request. As with stylesheets, though, you should only inline scripts if they&#39;re very small.&lt;/p&gt;

&lt;div class=&quot;wd-compare&quot;&gt;
  &lt;div class=&quot;compare-worse&quot;&gt;Don&#39;t&lt;/div&gt;
  &lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;HTML&quot;&gt;&amp;lt;head&amp;gt;
  &amp;lt;script src=&quot;/path/to/main.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/head&amp;gt;&lt;/pre&gt;&lt;/devsite-code&gt;

&lt;/div&gt;

&lt;div class=&quot;wd-compare&quot;&gt;
  &lt;div class=&quot;compare-better&quot;&gt;Do&lt;/div&gt;
  &lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;HTML&quot;&gt;&amp;lt;head&amp;gt;
  &amp;lt;script&amp;gt;
    // Inline script contents directly in the HTML.
    // IMPORTANT: only do this for very small scripts.
  &amp;lt;/script&amp;gt;
&amp;lt;/head&amp;gt;&lt;/pre&gt;&lt;/devsite-code&gt;

&lt;/div&gt;

&lt;h4 id=&quot;use_server-side_rendering&quot; data-text=&quot;Use server-side rendering&quot; tabindex=&quot;-1&quot;&gt;Use server-side rendering&lt;/h4&gt;

&lt;p&gt;&lt;a href=&quot;https://web.dev/articles/rendering-on-the-web#server-side_rendering&quot;&gt;Server-side rendering&lt;/a&gt; (SSR) is the process of running your client-side application logic on the server and responding to HTML document requests with the full HTML markup.&lt;/p&gt;

&lt;p&gt;From the perspective of optimizing LCP, there are two primary advantage of SSR:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your image resources will be discoverable from the HTML source (as discussed in &lt;a href=&quot;https://web.dev/articles/optimize-lcp?hl=en#1_eliminate_resource_load_delay&quot;&gt;step 1&lt;/a&gt; earlier).&lt;/li&gt;
&lt;li&gt;Your page content won&#39;t require additional JavaScript requests to finish before it can render.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The main downside of SSR is it requires additional server processing time, which can slow down your TTFB. This trade-off is usually worth it though because server processing times are within your control, whereas the network and device capabilities of your users are not.&lt;/p&gt;

&lt;p&gt;A similar option to SSR is called static site generation (SSG) or &lt;a href=&quot;https://web.dev/articles/rendering-on-the-web#terminology&quot;&gt;prerendering&lt;/a&gt;. This is the process of generating your HTML pages in a build step rather than on-demand. If prerendering is possible with your architecture, it&#39;s generally a better choice for performance.&lt;/p&gt;

&lt;h4 id=&quot;break_up_long_tasks&quot; data-text=&quot;Break up long tasks&quot; tabindex=&quot;-1&quot;&gt;Break up long tasks&lt;/h4&gt;

&lt;p&gt;Even if you&#39;ve followed the advice from earlier, and your JavaScript code is not render-blocking nor is it responsible for rendering your elements, it can still delay LCP.&lt;/p&gt;

&lt;p&gt;The most common reason this happens is when pages load large JavaScript files, which need to be parsed and executed on the browser&#39;s main thread. This means that, even if your image resource is fully downloaded, it may still have to wait until an unrelated script finishes executing before it can render.&lt;/p&gt;

&lt;p&gt;All browsers today render images on the main thread, which means anything that blocks the main thread can also lead to unnecessary &lt;em&gt;element render delay&lt;/em&gt;.&lt;/p&gt;

&lt;h3 id=&quot;reduce-resource-load-duration&quot; data-text=&quot;3. Reduce resource load duration&quot; tabindex=&quot;-1&quot;&gt;3. Reduce resource load duration&lt;/h3&gt;

&lt;p&gt;The goal of this step is to reduce the time spent transferring the bytes of the resource over the network to the user&#39;s device. In general, there are four ways to do that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduce the size of the resource.&lt;/li&gt;
&lt;li&gt;Reduce the distance the resource has to travel.&lt;/li&gt;
&lt;li&gt;Reduce contention for network bandwidth.&lt;/li&gt;
&lt;li&gt;Eliminate the network time entirely.&lt;/li&gt;
&lt;/ul&gt;

&lt;aside class=&quot;warning&quot;&gt;
  &lt;b&gt;Warning:&lt;/b&gt; &lt;a href=&quot;https://web.dev/blog/common-misconceptions-lcp&quot;&gt;Our research&lt;/a&gt; shows that the load duration portion of LCP tends not to be a significant bottleneck for most sites. Whenever possible, consult your site&#39;s real-user data for help deciding which LCP subpart to focus your efforts on.
&lt;/aside&gt;

&lt;h4 id=&quot;reduce-size&quot; data-text=&quot;Reduce the size of the resource&quot; tabindex=&quot;-1&quot;&gt;Reduce the size of the resource&lt;/h4&gt;

&lt;p&gt;The LCP resource of a page (if it has one) will either be an image or a web font. The following guides go into great detail about how to reduce the size of both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/uses-responsive-images&quot;&gt;Serve the optimal image size&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/uses-webp-images&quot;&gt;Use modern image formats&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/uses-optimized-images&quot;&gt;Compress images&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/articles/reduce-webfont-size&quot;&gt;Reduce web font size&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;reduce_the_distance_the_resource_has_to_travel&quot; data-text=&quot;Reduce the distance the resource has to travel&quot; tabindex=&quot;-1&quot;&gt;Reduce the distance the resource has to travel&lt;/h4&gt;

&lt;p&gt;In addition to reducing the size of a resource, you can also reduce the load times by getting your servers as geographically close to your users as possible. And the best way to do that is to use a &lt;a href=&quot;https://web.dev/articles/content-delivery-networks&quot;&gt;content delivery network&lt;/a&gt; (CDN).&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://web.dev/articles/image-cdns&quot;&gt;Image CDNs&lt;/a&gt; in particular are especially helpful because they not only reduce the distance the resource has to travel, but they also generally reduce the size of the resource—automatically implementing all of the size-reduction recommendations from earlier for you.&lt;/p&gt;

&lt;aside class=&quot;key-point&quot;&gt;
  &lt;b&gt;Key point:&lt;/b&gt; While image CDNs are a great way to reduce resource load durations, using a third-party domain to host your images comes with an additional connection cost. While preconnecting to the origin can mitigate some of this cost, the best option is to serve images from the same origin as your HTML document. Many CDNs allow you to proxy requests from your origin to theirs, which is a great option to look into if available.
&lt;/aside&gt;

&lt;h4 id=&quot;reduce_contention_for_network_bandwidth&quot; data-text=&quot;Reduce contention for network bandwidth&quot; tabindex=&quot;-1&quot;&gt;Reduce contention for network bandwidth&lt;/h4&gt;

&lt;p&gt;Even if you&#39;ve reduced the size of your resource and the distance it has to travel, a resource can still take a long time to load if you&#39;re loading many other resources at the same time. This problem is known as &lt;em&gt;network contention&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;If you&#39;ve given your LCP resource a &lt;a href=&quot;https://web.dev/articles/fetch-priority&quot;&gt;high &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;fetchpriority&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://web.dev/articles/optimize-lcp?hl=en#1_eliminate_resource_load_delay&quot;&gt;started loading it as soon as possible&lt;/a&gt; then the browser will do its best to prevent lower-priority resources from competing with it. However, if you&#39;re loading many resources with high &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;fetchpriority&lt;/code&gt;, or if you&#39;re just loading a lot of resources in general, then it could affect how quickly the LCP resource loads.&lt;/p&gt;

&lt;h4 id=&quot;eliminate_the_network_time_entirely&quot; data-text=&quot;Eliminate the network time entirely&quot; tabindex=&quot;-1&quot;&gt;Eliminate the network time entirely&lt;/h4&gt;

&lt;p&gt;The best way to reduce resource load duration is to eliminate the network entirely from the process. If you serve your resources with an &lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/uses-long-cache-ttl&quot;&gt;efficient cache-control policy&lt;/a&gt;, then visitors who request those resources a second time will have them served from the cache—bringing the &lt;em&gt;resource load duration&lt;/em&gt; to essentially zero!&lt;/p&gt;

&lt;p&gt;If your LCP resource is a web font, in addition to &lt;a href=&quot;https://web.dev/articles/reduce-webfont-size&quot;&gt;reducing web font size&lt;/a&gt;, you should also consider whether you need to block rendering on the web font resource load. If you set a &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/@font-face/font-display&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;font-display&lt;/code&gt;&lt;/a&gt; value of anything other than &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;auto&lt;/code&gt; or &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;block&lt;/code&gt;, then text will &lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/font-display&quot;&gt;always be visible during load&lt;/a&gt;, and LCP won&#39;t be blocked on an additional network request.&lt;/p&gt;

&lt;p&gt;Finally, if your LCP resource is small, it may make sense to inline the resources as a &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Basics_of_HTTP/Data_URIs&quot;&gt;data URL&lt;/a&gt;, which will also eliminate the additional network request. However, using data URLs &lt;a href=&quot;https://calendar.perfplanet.com/2018/performance-anti-patterns-base64-encoding/&quot;&gt;comes with caveats&lt;/a&gt; because then the resources cannot be cached and in some cases can lead to longer render delays because of the additional &lt;a href=&quot;https://www.catchpoint.com/blog/data-uri&quot;&gt;decode cost&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;reduce-ttfb&quot; data-text=&quot;4. Reduce time to first byte&quot; tabindex=&quot;-1&quot;&gt;4. Reduce &lt;em&gt;time to first byte&lt;/em&gt;&lt;/h3&gt;

&lt;p&gt;The goal of this step is to deliver the initial HTML as quickly as possible. This step is listed last because it&#39;s often the one developers have the least control over. However, it&#39;s also one of the most important steps because it directly affects every step that comes after it. Nothing can happen on the frontend until the backend delivers that first byte of content, so anything you can do to speed up your TTFB will improve every other load metric as well.&lt;/p&gt;

&lt;p&gt;A common cause of a slow TTFB for an otherwise fast site is visitors arriving through multiple redirects, such as from advertisements or &lt;a href=&quot;https://en.wikipedia.org/wiki/URL_shortening&quot;&gt;shortened links&lt;/a&gt;. Always minimize the number of redirects a visitor must wait through.&lt;/p&gt;

&lt;p&gt;Another common cause is when cached content cannot be used from a CDN edge server, and all requests must be directed all the way back to the origin server. This can happen if unique URL parameters are used by visitors for analytics—even if they don&#39;t result in different pages.&lt;/p&gt;

&lt;p&gt;For specific guidance on optimizing TTFB, consult the &lt;a href=&quot;https://web.dev/articles/optimize-ttfb&quot;&gt;optimize TTFB guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;monitor_lcp_breakdown_in_javascript&quot; data-text=&quot;Monitor LCP breakdown in JavaScript&quot; tabindex=&quot;-1&quot;&gt;Monitor LCP breakdown in JavaScript&lt;/h2&gt;

&lt;p&gt;The timing information for all of the LCP subparts discussed earlier is available to you in JavaScript through a combination of the following performance APIs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://w3c.github.io/largest-contentful-paint/&quot;&gt;Largest Contentful Paint API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3.org/TR/navigation-timing-2/&quot;&gt;Navigation Timing API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3.org/TR/resource-timing-2/&quot;&gt;Resource Timing API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many RUM products already calculate the subparts using these APIs. The &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals&quot;&gt;web-vitals library&lt;/a&gt; also includes these LCP subpart timings in the attribution build and its code can be referenced for how to calculate these in JavaScript.&lt;/p&gt;

&lt;p&gt;Chrome DevTools, and Lighthouse also measures these subparts as shown in the previous screenshots, saving you the need to calculate them manually in JavaScript when using those tools.&lt;/p&gt;

&lt;h2 id=&quot;summary&quot; data-text=&quot;Summary&quot; tabindex=&quot;-1&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;LCP is complex, and its timing can be affected by a number of factors. But if you consider that optimizing LCP is primarily about optimizing the load of the LCP resource, it can significantly simplify things.&lt;/p&gt;

&lt;p&gt;At a high level, optimizing LCP can be summarized in four steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Ensure the LCP resource starts loading as early as possible.&lt;/li&gt;
&lt;li&gt;Ensure the LCP element can render as soon as its resource finishes loading.&lt;/li&gt;
&lt;li&gt;Reduce the load time of the LCP resource as much as you can without sacrificing quality.&lt;/li&gt;
&lt;li&gt;Deliver the initial HTML document as fast as possible.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you&#39;re able to follow these steps on your pages, then you should feel confident that you&#39;re delivering an optimal loading experience to your users, and you should see that reflected in your real-world LCP scores.&lt;/p&gt;

  

  
</description><link>https://web.dev/articles/optimize-lcp?hl=en</link><guid isPermaLink="false">https://web.dev/articles/optimize-lcp?hl=en</guid><pubDate>Mon, 31 Mar 2025 07:00:00 GMT</pubDate></item><item><title>Permission UX</title><description>&lt;p&gt;&lt;style&gt;
  .wd-author__links {
    display: flex;
  }
  &lt;/style&gt;

&lt;/p&gt;&lt;p&gt;&lt;/p&gt;

&lt;p&gt;The natural step after getting a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;PushSubscription&lt;/code&gt; and saving it our server is
to trigger a push message, but there is one thing I flagrantly glossed over. The
user experience when asking for permission from the user to send them push
messages.&lt;/p&gt;

&lt;p&gt;Sadly, very few sites give much consideration to how they ask their user for
permission, so let&#39;s take a brief aside to look at both good and bad UX.&lt;/p&gt;

&lt;h2 id=&quot;common_patterns&quot; data-text=&quot;Common patterns&quot; tabindex=&quot;-1&quot;&gt;Common patterns&lt;/h2&gt;

&lt;p&gt;There have been a few common patterns emerging that should guide and help you when
deciding what is best for your users and use case.&lt;/p&gt;

&lt;h3 id=&quot;value_proposition&quot; data-text=&quot;Value proposition&quot; tabindex=&quot;-1&quot;&gt;Value proposition&lt;/h3&gt;

&lt;p&gt;Ask users to subscribe to push at a time when the benefit is obvious.&lt;/p&gt;

&lt;p&gt;For example, a user has just bought an item on an online store and finished the
checkout flow. The site can then offer updates on the delivery status.&lt;/p&gt;

&lt;p&gt;There are a range of situations where this approach works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A particular item is out of stock, would you like to be notified when it&#39;s next available?&lt;/li&gt;
&lt;li&gt;This breaking news story will be regularly updated, would you like to be notified as the
story develops?&lt;/li&gt;
&lt;li&gt;You&#39;re the highest bidder, would you like to be notified if you are outbid?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are all points where the user has invested in your service and there
is a clear value proposition for them to enable push notifications.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://web.dev/static/articles/push-notifications-permissions-ux/image/owen-campbell-moores-exa-7047e2dfb6c6.png&quot; alt=&quot;Owen Campbell-Moore&#39;s example of good UX for push.&quot; width=&quot;800&quot; height=&quot;1352&quot; srcset=&quot;https://web.dev/static/articles/push-notifications-permissions-ux/image/owen-campbell-moores-exa-7047e2dfb6c6_36.png 36w,https://web.dev/static/articles/push-notifications-permissions-ux/image/owen-campbell-moores-exa-7047e2dfb6c6_48.png 48w,https://web.dev/static/articles/push-notifications-permissions-ux/image/owen-campbell-moores-exa-7047e2dfb6c6_72.png 72w,https://web.dev/static/articles/push-notifications-permissions-ux/image/owen-campbell-moores-exa-7047e2dfb6c6_96.png 96w,https://web.dev/static/articles/push-notifications-permissions-ux/image/owen-campbell-moores-exa-7047e2dfb6c6_480.png 480w,https://web.dev/static/articles/push-notifications-permissions-ux/image/owen-campbell-moores-exa-7047e2dfb6c6_720.png 720w,https://web.dev/static/articles/push-notifications-permissions-ux/image/owen-campbell-moores-exa-7047e2dfb6c6_856.png 856w,https://web.dev/static/articles/push-notifications-permissions-ux/image/owen-campbell-moores-exa-7047e2dfb6c6_960.png 960w,https://web.dev/static/articles/push-notifications-permissions-ux/image/owen-campbell-moores-exa-7047e2dfb6c6_1440.png 1440w,https://web.dev/static/articles/push-notifications-permissions-ux/image/owen-campbell-moores-exa-7047e2dfb6c6_1920.png 1920w,https://web.dev/static/articles/push-notifications-permissions-ux/image/owen-campbell-moores-exa-7047e2dfb6c6_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;

&lt;p&gt;Owen created a mock of a hypothetical airline
website to demonstrate this approach.&lt;/p&gt;

&lt;p&gt;After the user has booked a flight it asks if the user would like notifications of flight
delays.&lt;/p&gt;

&lt;p&gt;Note that this is a custom UI from the website.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://web.dev/static/articles/push-notifications-permissions-ux/image/owen-campbell-moores-exa-61fe811c7c45b.png&quot; alt=&quot;Owen Campbell-Moore&#39;s example of good UX for the permission prompt.&quot; width=&quot;800&quot; height=&quot;1352&quot; srcset=&quot;https://web.dev/static/articles/push-notifications-permissions-ux/image/owen-campbell-moores-exa-61fe811c7c45b_36.png 36w,https://web.dev/static/articles/push-notifications-permissions-ux/image/owen-campbell-moores-exa-61fe811c7c45b_48.png 48w,https://web.dev/static/articles/push-notifications-permissions-ux/image/owen-campbell-moores-exa-61fe811c7c45b_72.png 72w,https://web.dev/static/articles/push-notifications-permissions-ux/image/owen-campbell-moores-exa-61fe811c7c45b_96.png 96w,https://web.dev/static/articles/push-notifications-permissions-ux/image/owen-campbell-moores-exa-61fe811c7c45b_480.png 480w,https://web.dev/static/articles/push-notifications-permissions-ux/image/owen-campbell-moores-exa-61fe811c7c45b_720.png 720w,https://web.dev/static/articles/push-notifications-permissions-ux/image/owen-campbell-moores-exa-61fe811c7c45b_856.png 856w,https://web.dev/static/articles/push-notifications-permissions-ux/image/owen-campbell-moores-exa-61fe811c7c45b_960.png 960w,https://web.dev/static/articles/push-notifications-permissions-ux/image/owen-campbell-moores-exa-61fe811c7c45b_1440.png 1440w,https://web.dev/static/articles/push-notifications-permissions-ux/image/owen-campbell-moores-exa-61fe811c7c45b_1920.png 1920w,https://web.dev/static/articles/push-notifications-permissions-ux/image/owen-campbell-moores-exa-61fe811c7c45b_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;

&lt;p&gt;Another nice touch to Owen&#39;s demo is that if the user clicks to enable
notifications, the site adds a semi-transparent overlay on the entire page when
it shows the permission prompt. This draws the users attention to the
permission prompt.&lt;/p&gt;

&lt;p&gt;The alternative to this example, the &lt;strong&gt;bad UX&lt;/strong&gt; for asking permission, is to request
permission as soon as a user lands on the airline&#39;s site.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://web.dev/static/articles/push-notifications-permissions-ux/image/owen-campbell-moores-exa-cf237664538e7.png&quot; alt=&quot;Owen Campbell-Moore&#39;s example of bad UX for push.&quot; width=&quot;800&quot; height=&quot;1371&quot; srcset=&quot;https://web.dev/static/articles/push-notifications-permissions-ux/image/owen-campbell-moores-exa-cf237664538e7_36.png 36w,https://web.dev/static/articles/push-notifications-permissions-ux/image/owen-campbell-moores-exa-cf237664538e7_48.png 48w,https://web.dev/static/articles/push-notifications-permissions-ux/image/owen-campbell-moores-exa-cf237664538e7_72.png 72w,https://web.dev/static/articles/push-notifications-permissions-ux/image/owen-campbell-moores-exa-cf237664538e7_96.png 96w,https://web.dev/static/articles/push-notifications-permissions-ux/image/owen-campbell-moores-exa-cf237664538e7_480.png 480w,https://web.dev/static/articles/push-notifications-permissions-ux/image/owen-campbell-moores-exa-cf237664538e7_720.png 720w,https://web.dev/static/articles/push-notifications-permissions-ux/image/owen-campbell-moores-exa-cf237664538e7_856.png 856w,https://web.dev/static/articles/push-notifications-permissions-ux/image/owen-campbell-moores-exa-cf237664538e7_960.png 960w,https://web.dev/static/articles/push-notifications-permissions-ux/image/owen-campbell-moores-exa-cf237664538e7_1440.png 1440w,https://web.dev/static/articles/push-notifications-permissions-ux/image/owen-campbell-moores-exa-cf237664538e7_1920.png 1920w,https://web.dev/static/articles/push-notifications-permissions-ux/image/owen-campbell-moores-exa-cf237664538e7_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;

&lt;p&gt;This approach provides no context as to why notifications are needed or
useful to the user. The user is also blocked from achieving their original
task (i.e. book a flight) by this permission prompt.&lt;/p&gt;

&lt;h3 id=&quot;double_permission&quot; data-text=&quot;Double Permission&quot; tabindex=&quot;-1&quot;&gt;Double Permission&lt;/h3&gt;

&lt;p&gt;You may feel that your site has a clear use case for push messaging and as
a result want to ask the user for permission as soon as possible.&lt;/p&gt;

&lt;p&gt;For example instant messaging and email clients. Showing a message for a
new message or email is an established user experience across a range of
platforms.&lt;/p&gt;

&lt;p&gt;For these category of apps, it&#39;s worth considering the double permission
pattern.&lt;/p&gt;

&lt;p&gt;First show a dialog that your website controls, explaining the value for your site&#39;s use case.
The dialog can then offer buttons to trigger or forego the necessary permission request.
If the user provides a positive signal, request permission, triggering the real browser permission prompt.&lt;/p&gt;

&lt;p&gt;With this approach you display a custom prompt in your web app which first provides
context. By doing this, the user can chose to enable or disable without your website
running the risk of being permanently blocked because the user was annoyed by an
unexpected permission prompt. If the user selects enable on the custom UI,
display the actual permission prompt, otherwise hide your custom dialog and
respect the user&#39;s choice.&lt;/p&gt;

&lt;p&gt;You can read more about &lt;a href=&quot;https://web.dev/articles/permissions-best-practices&quot;&gt;permissions best practices&lt;/a&gt; and
&lt;a href=&quot;https://web.dev/case-studies/google-meet-permissions-best-practices&quot;&gt;how Google Meet improved their permission flow&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;settings_panel&quot; data-text=&quot;Settings Panel&quot; tabindex=&quot;-1&quot;&gt;Settings Panel&lt;/h3&gt;

&lt;p&gt;You can move notifications into a settings panel, giving users an easy way
to enable and disable push messaging, without the need of cluttering your
web app&#39;s UI.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://web.dev/static/articles/push-notifications-permissions-ux/image/when-first-load-page-c6daa695515cb.png&quot; alt=&quot;When you first load the page, there is no prompt.&quot; width=&quot;800&quot; height=&quot;1388&quot; srcset=&quot;https://web.dev/static/articles/push-notifications-permissions-ux/image/when-first-load-page-c6daa695515cb_36.png 36w,https://web.dev/static/articles/push-notifications-permissions-ux/image/when-first-load-page-c6daa695515cb_48.png 48w,https://web.dev/static/articles/push-notifications-permissions-ux/image/when-first-load-page-c6daa695515cb_72.png 72w,https://web.dev/static/articles/push-notifications-permissions-ux/image/when-first-load-page-c6daa695515cb_96.png 96w,https://web.dev/static/articles/push-notifications-permissions-ux/image/when-first-load-page-c6daa695515cb_480.png 480w,https://web.dev/static/articles/push-notifications-permissions-ux/image/when-first-load-page-c6daa695515cb_720.png 720w,https://web.dev/static/articles/push-notifications-permissions-ux/image/when-first-load-page-c6daa695515cb_856.png 856w,https://web.dev/static/articles/push-notifications-permissions-ux/image/when-first-load-page-c6daa695515cb_960.png 960w,https://web.dev/static/articles/push-notifications-permissions-ux/image/when-first-load-page-c6daa695515cb_1440.png 1440w,https://web.dev/static/articles/push-notifications-permissions-ux/image/when-first-load-page-c6daa695515cb_1920.png 1920w,https://web.dev/static/articles/push-notifications-permissions-ux/image/when-first-load-page-c6daa695515cb_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;

&lt;p&gt;A good example of this is the Google I/O site.
When you first load up the Google I/O site, you aren&#39;t asked to do anything,
the user is left to explore the site.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://web.dev/static/articles/push-notifications-permissions-ux/image/the-settings-panel-googl-bf491e62b232f.png&quot; alt=&quot;The settings panel on Google IO&#39;s web app for push messaging.&quot; width=&quot;800&quot; height=&quot;1388&quot; srcset=&quot;https://web.dev/static/articles/push-notifications-permissions-ux/image/the-settings-panel-googl-bf491e62b232f_36.png 36w,https://web.dev/static/articles/push-notifications-permissions-ux/image/the-settings-panel-googl-bf491e62b232f_48.png 48w,https://web.dev/static/articles/push-notifications-permissions-ux/image/the-settings-panel-googl-bf491e62b232f_72.png 72w,https://web.dev/static/articles/push-notifications-permissions-ux/image/the-settings-panel-googl-bf491e62b232f_96.png 96w,https://web.dev/static/articles/push-notifications-permissions-ux/image/the-settings-panel-googl-bf491e62b232f_480.png 480w,https://web.dev/static/articles/push-notifications-permissions-ux/image/the-settings-panel-googl-bf491e62b232f_720.png 720w,https://web.dev/static/articles/push-notifications-permissions-ux/image/the-settings-panel-googl-bf491e62b232f_856.png 856w,https://web.dev/static/articles/push-notifications-permissions-ux/image/the-settings-panel-googl-bf491e62b232f_960.png 960w,https://web.dev/static/articles/push-notifications-permissions-ux/image/the-settings-panel-googl-bf491e62b232f_1440.png 1440w,https://web.dev/static/articles/push-notifications-permissions-ux/image/the-settings-panel-googl-bf491e62b232f_1920.png 1920w,https://web.dev/static/articles/push-notifications-permissions-ux/image/the-settings-panel-googl-bf491e62b232f_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;

&lt;p&gt;After a few visits, clicking the menu item on the right reveals a settings
panel allowing the user to set up and manage notifications.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://web.dev/static/articles/push-notifications-permissions-ux/image/google-ios-web-app-displ-38b26943545ba.png&quot; alt=&quot;Google IO&#39;s web app displaying the permission prompt.&quot; width=&quot;800&quot; height=&quot;1388&quot; srcset=&quot;https://web.dev/static/articles/push-notifications-permissions-ux/image/google-ios-web-app-displ-38b26943545ba_36.png 36w,https://web.dev/static/articles/push-notifications-permissions-ux/image/google-ios-web-app-displ-38b26943545ba_48.png 48w,https://web.dev/static/articles/push-notifications-permissions-ux/image/google-ios-web-app-displ-38b26943545ba_72.png 72w,https://web.dev/static/articles/push-notifications-permissions-ux/image/google-ios-web-app-displ-38b26943545ba_96.png 96w,https://web.dev/static/articles/push-notifications-permissions-ux/image/google-ios-web-app-displ-38b26943545ba_480.png 480w,https://web.dev/static/articles/push-notifications-permissions-ux/image/google-ios-web-app-displ-38b26943545ba_720.png 720w,https://web.dev/static/articles/push-notifications-permissions-ux/image/google-ios-web-app-displ-38b26943545ba_856.png 856w,https://web.dev/static/articles/push-notifications-permissions-ux/image/google-ios-web-app-displ-38b26943545ba_960.png 960w,https://web.dev/static/articles/push-notifications-permissions-ux/image/google-ios-web-app-displ-38b26943545ba_1440.png 1440w,https://web.dev/static/articles/push-notifications-permissions-ux/image/google-ios-web-app-displ-38b26943545ba_1920.png 1920w,https://web.dev/static/articles/push-notifications-permissions-ux/image/google-ios-web-app-displ-38b26943545ba_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;

&lt;p&gt;Clicking on the checkbox displays the permission prompt. No hidden surprises.&lt;/p&gt;

&lt;p&gt;After the permission has been granted, the checkbox is checked and the user
is good to go. The great thing about this UI is that users can enable and
disable notifications from one location on the website.&lt;/p&gt;

&lt;h3 id=&quot;passive_approach&quot; data-text=&quot;Passive approach&quot; tabindex=&quot;-1&quot;&gt;Passive approach&lt;/h3&gt;

&lt;p&gt;One of the easiest ways to offer push to a user is to have a button
or toggle switch that enables / disables push messages in a location
on the page that is consistent throughout a site.&lt;/p&gt;

&lt;p&gt;This doesn&#39;t drive users to enable push notifications, but offers a
reliable and easy way for users to opt in and out of engaging with your
website. For sites like blogs that might have some regular viewers as well
as high bounce rates, this is a solid option as it targets regular viewers
without annoying drive-by visitors.&lt;/p&gt;

&lt;p&gt;On my personal site, I have a toggle switch for push messaging in the footer.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://web.dev/static/articles/push-notifications-permissions-ux/image/example-gauntfacecom-pu-f48a35a3b4437.png&quot; alt=&quot;Example of Gauntface.com push notification toggle in
footer&quot; width=&quot;800&quot; height=&quot;483&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;

&lt;p&gt;It&#39;s fairly out of the way, but for regular visitors it should get enough
attention from readers wanting to get updates. One-time visitors are
completely unaffected.&lt;/p&gt;

&lt;p&gt;If the user subscribes to push messaging, the state of the toggle switch
changes and maintains state throughout the site.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://web.dev/static/articles/push-notifications-permissions-ux/image/example-gauntfacecom-n-c8e492a171de4.png&quot; alt=&quot;Example of Gauntface.com with notifications
enabled&quot; width=&quot;800&quot; height=&quot;483&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;

&lt;h3 id=&quot;the_bad_ux&quot; data-text=&quot;The bad UX&quot; tabindex=&quot;-1&quot;&gt;The bad UX&lt;/h3&gt;

&lt;p&gt;Those are some of the common practices I&#39;ve noticed on the web. Sadly, there is one very common
bad practice.&lt;/p&gt;

&lt;p&gt;The worst thing you can do is to show the permission dialog to users as soon as they
land on your site.&lt;/p&gt;

&lt;p&gt;They have zero context on why they are being asked for a permission, they may
not even know what your website is for, what it does or what it offers. Blocking
permissions at this point out of frustration is not uncommon, this pop-up is
getting in the way of what they are trying to do.&lt;/p&gt;

&lt;p&gt;Remember, if the user &lt;em&gt;blocks&lt;/em&gt; the permission request, your web app can&#39;t ask for permission
again. To get permission after being blocked, the user has to change the permission in the
browser&#39;s UI and doing so is not easy, obvious or fun for the user.&lt;/p&gt;

&lt;p&gt;No matter what, don&#39;t ask for permission as soon as the user opens your site, consider some
other UI or approach that has an incentive for the user to grant permission.&lt;/p&gt;

&lt;h3 id=&quot;offer_a_way_out&quot; data-text=&quot;Offer a way out&quot; tabindex=&quot;-1&quot;&gt;Offer a way out&lt;/h3&gt;

&lt;p&gt;In addition to considering the UX to subscribe a user to push, &lt;strong&gt;please&lt;/strong&gt; consider how a user
should unsubscribe or opt out of push messaging.&lt;/p&gt;

&lt;p&gt;The number of sites that ask for permission as soon as the page loads and then
offer no UI for disabling push notifications is astounding.&lt;/p&gt;

&lt;p&gt;Your site should explain to your users how they can disable push. If you don&#39;t, users are
likely to take the nuclear option and block permission permanently.&lt;/p&gt;

&lt;h2 id=&quot;where_to_go_next&quot; data-text=&quot;Where to go next&quot; tabindex=&quot;-1&quot;&gt;Where to go next&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/articles/permissions-best-practices&quot;&gt;Web Permissions Best Practices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/case-studies/google-meet-permissions-best-practices&quot;&gt;Less friction, more control: How Google Meet improved audio and video permissions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/articles/push-notifications-overview&quot;&gt;Web Push Notification Overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/articles/push-notifications-how-push-works&quot;&gt;How Push Works&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/articles/push-notifications-subscribing-a-user&quot;&gt;Subscribing a User&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/articles/sending-messages-with-web-push-libraries&quot;&gt;Sending Messages with Web Push Libraries&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/articles/push-notifications-web-push-protocol&quot;&gt;Web Push Protocol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/articles/push-notifications-handling-messages&quot;&gt;Handling Push Events&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/articles/push-notifications-display-a-notification&quot;&gt;Displaying a Notification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/articles/push-notifications-notification-behaviour&quot;&gt;Notification Behavior&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/articles/push-notifications-common-notification-patterns&quot;&gt;Common Notification Patterns&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/articles/push-notifications-faq&quot;&gt;Push Notifications FAQ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/articles/push-notifications-common-issues-and-reporting-bugs&quot;&gt;Common Issues and Reporting Bugs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;code_labs&quot; data-text=&quot;Code labs&quot; tabindex=&quot;-1&quot;&gt;Code labs&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/articles/push-notifications-client-codelab&quot;&gt;Build a push notification client&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/articles/push-notifications-server-codelab&quot;&gt;Build a push notification server&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

  

  
</description><link>https://web.dev/articles/push-notifications-permissions-ux?hl=en</link><guid isPermaLink="false">https://web.dev/articles/push-notifications-permissions-ux?hl=en</guid><pubDate>Wed, 26 Mar 2025 07:00:00 GMT</pubDate></item><item><title>How to think about Baseline and polyfills</title><description>&lt;p&gt;&lt;style&gt;
  .wd-author__links {
    display: flex;
  }
  &lt;/style&gt;

&lt;/p&gt;&lt;p&gt;&lt;/p&gt;

&lt;p&gt;
&lt;/p&gt;&lt;p class=&quot;wd-pubdates&quot; style=&quot;font-size: 90%; border-bottom: 1px solid var(--devsite-ref-palette--grey200);color: var(--devsite-ref-palette--blue-grey700);&quot;&gt;
  Published: March 18, 2025
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Polyfills have long been a part of the web developer experience, as they attempt to provide support for web features that aren&#39;t supported in all browsers. It would seem that polyfills are an indispensable tool in the web developer&#39;s toolkit, but it&#39;s nearly impossible to distill such a complex area of concern into a single, definitive statement.&lt;/p&gt;

&lt;p&gt;Knowing when to use a polyfill for a feature depends on its availability across browsers, and &lt;a href=&quot;https://web.dev/baseline&quot;&gt;Baseline&lt;/a&gt; can be helpful in making that determination. While Baseline doesn&#39;t tell you whether or not you should use polyfills, the clarity it brings to the availability of web platform features gives you an opportunity to be more selective, as excessive polyfills in an application can have significant drawbacks.&lt;/p&gt;

&lt;h2 id=&quot;what_are_polyfills&quot; data-text=&quot;What are polyfills?&quot; tabindex=&quot;-1&quot;&gt;What are polyfills?&lt;/h2&gt;

&lt;p&gt;A polyfill is code that evaluates support for a feature in a browser, and—if the feature is unsupported—it will use JavaScript to attempt to provide that missing functionality.&lt;/p&gt;

&lt;p&gt;An early example of a polyfill is &lt;a href=&quot;https://github.com/paulirish/matchMedia.js/&quot;&gt;Matchmedia.js&lt;/a&gt;, which used JavaScript to detect and provide support for the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Window/matchMedia&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;matchMedia&lt;/code&gt; method&lt;/a&gt;. &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;matchMedia&lt;/code&gt; lets developers evaluate whether a media query matches the state of the current viewport in JavaScript. By loading a polyfill to provide the missing feature, it became possible to use the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;matchMedia&lt;/code&gt; method in any browser—but this polyfill is now no longer necessary, as &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;matchMedia&lt;/code&gt; is &lt;a href=&quot;https://caniuse.com/matchmedia&quot;&gt;available in nearly every browser in use&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;While adding support for unsupported features is advantageous in some ways, the disadvantages of polyfills often go overlooked. As the number of polyfills increase in a web application, they can begin to have negative impacts.&lt;/p&gt;

&lt;h2 id=&quot;what_are_the_disadvantages_of_polyfills&quot; data-text=&quot;What are the disadvantages of polyfills?&quot; tabindex=&quot;-1&quot;&gt;What are the disadvantages of polyfills?&lt;/h2&gt;

&lt;p&gt;A feature that&#39;s polyfilled always has a cost compared to the same feature that&#39;s supported in a browser, and these costs add up as you add more polyfills:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Polyfills are written in JavaScript, and any additional JavaScript in a web application can affect performance. Negative impacts can include render blocking, and blocking the main thread due to the &lt;a href=&quot;https://web.dev/articles/script-evaluation-and-long-tasks&quot;&gt;additional long tasks that JavaScript can introduce&lt;/a&gt; through additional parsing and compiling work.&lt;/li&gt;
&lt;li&gt;Polyfills can also impact accessibility if they don&#39;t adhere to the specification of features they attempt to emulate. Some features modify the accessibility tree in ways that polyfills don&#39;t or can&#39;t.&lt;/li&gt;
&lt;li&gt;Compared to features implemented directly in browsers, polyfills may not have the ability to faithfully reproduce all aspects of the features they try to provide. This can lead to situations where the user experience is impacted in unexpected ways, or restricts how much of the feature is usable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A good example of a polyfill that comes with some of these disadvantages is the &lt;a href=&quot;https://github.com/GoogleChromeLabs/container-query-polyfill&quot;&gt;container queries polyfill&lt;/a&gt;. &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/CSS_containment/Container_queries&quot;&gt;Container queries&lt;/a&gt; let you apply CSS rules based on the state of an HTML element itself, as opposed to media queries, which let you apply CSS to elements based on the state of the viewport. This polyfill uses the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;ResizeObserver&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;MutationObserver&lt;/code&gt; JavaScript APIs to mimic the capabilities of container queries. In particular, &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;ResizeObserver&lt;/code&gt; callbacks occur just before the browser draws a new frame, which increases &lt;a href=&quot;https://web.dev/articles/optimize-inp#minimize_presentation_delay&quot;&gt;presentation delay&lt;/a&gt;, an important consideration when optimizing a page&#39;s &lt;a href=&quot;https://web.dev/inp&quot;&gt;Interaction to Next Paint (INP)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Beyond performance, there are some aspects of container queries that a polyfill can&#39;t provide. While it&#39;s true that not every polyfill results in user experience problems or incomplete implementations of web features, the more of them you use, the more likely your application will be impacted in a negative way.&lt;/p&gt;

&lt;h2 id=&quot;where_does_baseline_come_in&quot; data-text=&quot;Where does Baseline come in?&quot; tabindex=&quot;-1&quot;&gt;Where does Baseline come in?&lt;/h2&gt;

&lt;p&gt;It used to be difficult to evaluate whether a particular web feature was safe to use, even though data sources such as &lt;a href=&quot;https://github.com/mdn/browser-compat-data&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;browser-compat-data&lt;/code&gt;&lt;/a&gt; have been used by various tools to help developers make these difficult decisions. &lt;a href=&quot;https://caniuse.com/&quot;&gt;Can I Use&lt;/a&gt; is a popular web feature support matrix that uses this data.&lt;/p&gt;

&lt;p&gt;Baseline goes a step further by providing clear and consistent messaging about which web features are supported across all browsers through three status indicators:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Limited availability&lt;/strong&gt;: The feature is not supported across all major browsers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Newly available&lt;/strong&gt;: The feature has emerged in all major browsers within the last 30 months.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Widely available&lt;/strong&gt;: The feature has been supported in all major browsers for more than the last 30 months.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Baseline does &lt;em&gt;not&lt;/em&gt; consider polyfills as a part of its messaging.&lt;/strong&gt; For example, if a feature has Limited availability, but is polyfillable for all browsers, that feature is never assumed to be either Newly or Widely available. The determining factor for whether a feature is Newly or Widely available is if a given feature is fully implemented in all major browsers. This communicates to developers which features are safe to use—and that informs developers on whether they should use a polyfill.&lt;/p&gt;

&lt;h2 id=&quot;determine_your_baseline_threshold&quot; data-text=&quot;Determine your Baseline threshold&quot; tabindex=&quot;-1&quot;&gt;Determine your Baseline threshold&lt;/h2&gt;

&lt;p&gt;The main advantage of Baseline is that it reduces the number of things you need to worry about in your developer workflow. If you decide to adopt anything that has passed the Widely available line without worrying about polyfills, that means the only features you need to consider the status of are those outside of that line. Baseline can then help you decide what to do with Newly available features.&lt;/p&gt;

&lt;p&gt;A feature becomes Newly available when supported by all target browsers, but that doesn&#39;t automatically mean that you can use the feature on day one and expect that every browser used by every user supports it. Most browsers now update themselves, but it takes time for all users to receive those updates. One factor to consider is when a device&#39;s browser version is tied to its operating system version. In these cases, there&#39;s a point at which a given user can no longer receive operating system updates until they replace their device.&lt;/p&gt;

&lt;p&gt;Baseline features are grouped in yearly sets—for example, &lt;a href=&quot;https://web.dev/baseline/2025&quot;&gt;Baseline 2025&lt;/a&gt;, &lt;a href=&quot;https://web.dev/baseline/2024&quot;&gt;Baseline 2024&lt;/a&gt;, and so on—until enough time passes in which those features become part of the Widely available set. By coupling this information with information about your users, you can now make informed decisions about whether to use a Newly available feature, or use it with a polyfill.&lt;/p&gt;

&lt;p&gt;The best data source for determining a Baseline threshold is the one for your website. Tools in this space are evolving to include data for Baseline thresholds. &lt;a href=&quot;https://www.rumvision.com/&quot;&gt;RUMvision&lt;/a&gt; is a product that provides this information.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/baseline-and-polyfills/image/fig-1.png&quot; alt=&quot;RUMvision&#39;s Baseline implementation can display support for web features in their product dashboard, which lets users search for real-world support data for discrete web features, or by Baseline feature sets.&quot; width=&quot;800&quot; height=&quot;360&quot; srcset=&quot;https://web.dev/static/articles/baseline-and-polyfills/image/fig-1_36.png 36w,https://web.dev/static/articles/baseline-and-polyfills/image/fig-1_48.png 48w,https://web.dev/static/articles/baseline-and-polyfills/image/fig-1_72.png 72w,https://web.dev/static/articles/baseline-and-polyfills/image/fig-1_96.png 96w,https://web.dev/static/articles/baseline-and-polyfills/image/fig-1_480.png 480w,https://web.dev/static/articles/baseline-and-polyfills/image/fig-1_720.png 720w,https://web.dev/static/articles/baseline-and-polyfills/image/fig-1_856.png 856w,https://web.dev/static/articles/baseline-and-polyfills/image/fig-1_960.png 960w,https://web.dev/static/articles/baseline-and-polyfills/image/fig-1_1440.png 1440w,https://web.dev/static/articles/baseline-and-polyfills/image/fig-1_1920.png 1920w,https://web.dev/static/articles/baseline-and-polyfills/image/fig-1_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption&gt;
    RUMvision&#39;s solution can show you how many users support web features. You can use it to search for distinct features to give you more data in your decisions on which ones to use.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;In RUMvision&#39;s case, you can search not just for support of discrete features, but you can also see which percentage of users support a given Baseline feature set by year.&lt;/p&gt;

&lt;p&gt;If you don&#39;t have RUM data for your site yet, &lt;a href=&quot;https://rumarchive.com/insights/#baseline&quot;&gt;RUM Insights&lt;/a&gt; gives you a broad view of the support of each Baseline feature set. One pattern you&#39;ll notice is that Baseline thresholds within the Widely available window are supported by 98% or more of all users, with the earliest thresholds achieving near 100% support by users.&lt;/p&gt;

&lt;p&gt;How you determine feature support is your call, but generally speaking, the closer you get to 100% of users, the more confidence you can have that you can use Baseline web features without the need for a polyfill. As a general rule if 98% or 99% of users support a feature in their browser of choice, you can likely use the feature safely—but you still need to think about what that means for a minority of users.&lt;/p&gt;

&lt;h2 id=&quot;when_and_when_not_polyfill_a_missing_feature&quot; data-text=&quot;When and when not polyfill a missing feature&quot; tabindex=&quot;-1&quot;&gt;When and when not polyfill a missing feature&lt;/h2&gt;

&lt;p&gt;The decision to use a polyfill is subjective, and it depends completely on your web application&#39;s requirements. However, knowing that there are potential user experience disadvantages to using them, you should strive to use as few of them as possible. Knowing whether a feature has reached Baseline Newly or Widely available should inform this decision.&lt;/p&gt;

&lt;p&gt;Say you&#39;re looking to use a Baseline Newly available, but you have data that tells you that 95% of your users currently support the feature. This means that 5%—or one in every twenty users—will be using a browser that doesn&#39;t support the feature. This doesn&#39;t mean you should automatically reach for a polyfill, &lt;strong&gt;because whether you need a polyfill depends on the feature&lt;/strong&gt;, and what the impact is if it&#39;s missing for a user.&lt;/p&gt;

&lt;p&gt;While this can be a difficult call to make, it might help you to think about the potential negative user experience impacts that may result for users in browsers that don&#39;t support the feature you&#39;re looking to use, and whether those impacts warrant the use of a polyfill.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://web.dev/static/articles/baseline-and-polyfills/image/fig-2.png&quot; alt=&quot;A framework for how to think about polyfilling a feature, with the impact of a missing feature qualified as an enhancement, an additive feature, or critical feature. Each of these categories comes with increasingly noticeable and potentially negative consequences for using Limited availability features on the web, and how that might inform your decision to use a polyfill.&quot; width=&quot;800&quot; height=&quot;194&quot; srcset=&quot;https://web.dev/static/articles/baseline-and-polyfills/image/fig-2_36.png 36w,https://web.dev/static/articles/baseline-and-polyfills/image/fig-2_48.png 48w,https://web.dev/static/articles/baseline-and-polyfills/image/fig-2_72.png 72w,https://web.dev/static/articles/baseline-and-polyfills/image/fig-2_96.png 96w,https://web.dev/static/articles/baseline-and-polyfills/image/fig-2_480.png 480w,https://web.dev/static/articles/baseline-and-polyfills/image/fig-2_720.png 720w,https://web.dev/static/articles/baseline-and-polyfills/image/fig-2_856.png 856w,https://web.dev/static/articles/baseline-and-polyfills/image/fig-2_960.png 960w,https://web.dev/static/articles/baseline-and-polyfills/image/fig-2_1440.png 1440w,https://web.dev/static/articles/baseline-and-polyfills/image/fig-2_1920.png 1920w,https://web.dev/static/articles/baseline-and-polyfills/image/fig-2_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;

&lt;aside class=&quot;note&quot;&gt;
  &lt;b&gt;Note:&lt;/b&gt; This criteria assumes the feature you might want to polyfill, can be. Not all web features are possible to polyfill!
&lt;/aside&gt;

&lt;p&gt;This evaluation criteria classifies features into three categories:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Enhancement:&lt;/strong&gt; The feature enhances the user experience in a way that doesn&#39;t result in visual changes or reduced functionality if it&#39;s unsupported. If the feature is missing for a user, they&#39;ll likely not notice. You very likely don&#39;t need to polyfill these features if they&#39;re missing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Additive:&lt;/strong&gt; The feature provides an additive benefit that &lt;em&gt;may&lt;/em&gt; impact how a web page looks or functions, but not in a way that surfaces serious problems. In fact, the user may not even notice unless they compare the same experience in a different browser that supports the feature. If a polyfill is available, lean towards not using it, especially if you&#39;re already polyfilling many other features, as adding additional polyfills will only worsen performance.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Critical:&lt;/strong&gt; The feature provides necessary functionality that, if missing in the user&#39;s browser, will result in a broken experience due to JavaScript evaluation and runtime errors, broken layouts, and other unacceptable outcomes. In this case, you decide to polyfill the feature, or avoid using it and figure out a different workaround.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It can be tricky to figure out which features fall into which category, but here are some practical examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Good examples of &lt;strong&gt;enhancement features&lt;/strong&gt; are those related to performance. Features such as &lt;a href=&quot;https://web.dev/articles/fetch-priority&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;fetchpriority&lt;/code&gt;&lt;/a&gt;, &lt;a href=&quot;https://en.wikipedia.org/wiki/HTTP/3&quot;&gt;HTTP/3&lt;/a&gt; and others can improve the performance of a page. This is good for improving the user experience, but if the user uses a browser that doesn&#39;t support them, they won&#39;t experience adverse or even noticeable effects in many cases. Product stakeholders may be okay with the overwhelming majority of users gaining a benefit, so long as the vast minority are not left out in the cold.&lt;/li&gt;
&lt;li&gt;Examples of &lt;strong&gt;additive features&lt;/strong&gt; are those that enhance the visual appeal of a page. &lt;a href=&quot;https://web.dev/blog/color-spaces-and-functions&quot;&gt;Color spaces and functions&lt;/a&gt; and &lt;a href=&quot;https://web.dev/articles/css-subgrid&quot;&gt;subgrid&lt;/a&gt; are good examples of additive features that users may not notice if they&#39;re missing. In this case, some project stakeholders may have concerns about brand integrity if such functionality is missing for some users, but could be convinced that adoption of such features is okay if the experience is not utterly broken.&lt;/li&gt;
&lt;li&gt;Adoption of &lt;strong&gt;critical features&lt;/strong&gt; are non-negotiable. One example of such a feature might be the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTML/Element/datalist&quot;&gt;HTML &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;datalist&amp;gt;&lt;/code&gt; element&lt;/a&gt;, which if absent, could create a suboptimal user experience. Expect that project stakeholders won&#39;t accept adoption of features that will break the user experience in some browsers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Generally speaking, here&#39;s how you can evaluate which feature falls into which category by its Baseline status:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If a feature is &lt;strong&gt;Widely available&lt;/strong&gt; you shouldn&#39;t reach for a polyfill—unless you have data about your users that explicitly tell you otherwise.&lt;/li&gt;
&lt;li&gt;If a feature is &lt;strong&gt;Newly available&lt;/strong&gt; it has a very good likelihood of being supported broadly, but user support data should be used to determine which Baseline threshold is best to adhere to to minimize nuisances or unacceptable outcomes, with the understanding that support will improve to nearly 100% over time.&lt;/li&gt;
&lt;li&gt;If a feature is &lt;strong&gt;Limited availability&lt;/strong&gt; it has the highest likelihood of creating a broken experience for some users, and therefore should be approached with caution, &lt;em&gt;even if a polyfill is available&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;should_i_ever_use_limited_availability_features&quot; data-text=&quot;Should I ever use Limited availability features?&quot; tabindex=&quot;-1&quot;&gt;Should I ever use Limited availability features?&lt;/h2&gt;

&lt;p&gt;The answer to this question is yes, but with some important caveats:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Baseline features that have Limited availability are never guaranteed to become Newly available and beyond. You must very carefully consider what this means for the user experience.&lt;/li&gt;
&lt;li&gt;If you decide to adopt Limited availability features alongside polyfills, understand that you are passing on additional bytes to users unless you conditionally load those polyfills so that only those users who need them, get them. Even then, users who need them are still incurring a performance cost.&lt;/li&gt;
&lt;li&gt;Beyond the performance cost, users who require polyfills for Limited availability features may experience issues if a polyfill for a feature can&#39;t faithfully replicate the functionality of its natively implemented equivalent. This can impact functionality, and in some cases, even accessibility.&lt;/li&gt;
&lt;li&gt;Limited availability features are also subject to changes in their specified behavior. In particular, any feature only implemented in one browser may have to change when other browsers start to implement support. This means that your code, and any polyfill can fall out of sync with such changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unless your users are locked to a single browser engine, using Baseline Newly and especially Widely available features is the recommended approach. Doing so means you will generally spend less time thinking about feature support, and more time using them, and spending the time you save on solving other problems.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot; data-text=&quot;Conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;The rules for whether or not you should use polyfills as you adopt web platform features aren&#39;t clear-cut. Like anything you do on the web, it requires careful thought while weighing benefits and risks. To review, a rough set of rules to approach the problem are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Understand that while polyfills are advantageous in some ways, they represent potential performance and accessibility costs, and may be unable to faithfully replicate unimplemented web features.&lt;/li&gt;
&lt;li&gt;Determine your Baseline threshold with data from your users, if possible. If that&#39;s not possible, the Baseline Widely available set of features is a good starting point, and consider using &lt;a href=&quot;https://rumarchive.com/insights/#baseline&quot;&gt;RUM insights data&lt;/a&gt; to make informed decisions.&lt;/li&gt;
&lt;li&gt;With that data, assess how many users could be impacted if their browser doesn&#39;t support features you&#39;re looking to use, and assess the severity of that impact.&lt;/li&gt;
&lt;li&gt;Communicate with project stakeholders to determine what features are acceptable and appropriate to use for your project&#39;s goals and business needs.&lt;/li&gt;
&lt;li&gt;If you must use Limited availability features, assess your audience and the risk for using them. Unless your users are locked to using a single browser engine, you can&#39;t guarantee compatibility, even with a polyfill.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These reasons are why polyfills are not included as a part of Baseline. The role of Baseline is to inform you of which features are supported in all major browser engines. You&#39;ll still need to know how many of your users can use Baseline features, and make decisions based on your users and the needs of your project. Baseline Widely available is a good default, and will often have the broadest web feature support for users in many cases.&lt;/p&gt;

&lt;p&gt;The web platform is maturing in its capabilities faster than ever before, and there is a growing opportunity to use more of it as more features become interoperable and more broadly supported as time passes. This will provide you with more opportunities to use more web platform features with less polyfills.&lt;/p&gt;

  

  
</description><link>https://web.dev/articles/baseline-and-polyfills?hl=en</link><guid isPermaLink="false">https://web.dev/articles/baseline-and-polyfills?hl=en</guid><pubDate>Tue, 18 Mar 2025 07:00:00 GMT</pubDate></item><item><title>How to query the Web Platform Dashboard for Baseline tooling</title><description>&lt;p&gt;&lt;style&gt;
  .wd-author__links {
    display: flex;
  }
  &lt;/style&gt;

&lt;/p&gt;

&lt;p class=&quot;wd-pubdates&quot; style=&quot;font-size: 90%; border-bottom: 1px solid var(--devsite-ref-palette--grey200);color: var(--devsite-ref-palette--blue-grey700);&quot;&gt;
  Published: March 4, 2025
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://web.dev/baseline&quot;&gt;Baseline&lt;/a&gt; was originated by the Chrome team and is now defined by the &lt;a href=&quot;https://web-platform-dx.github.io/web-features/webdx-cg/&quot;&gt;WebDX Community Group&lt;/a&gt;. Baseline brings clarity to features that are interoperable between browsers. It&#39;s designed to help you identify what features you can use—or can&#39;t use—across all major browser engines. However, you need a way to identify what those features are.&lt;/p&gt;

&lt;p&gt;Thankfully, you have the ability to query which features are Baseline Newly or Widely available using the &lt;a href=&quot;https://webstatus.dev/&quot;&gt;Web Platform Dashboard&lt;/a&gt;—which is powered by the &lt;a href=&quot;https://www.npmjs.com/package/web-features&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;web-features&lt;/code&gt; npm package&lt;/a&gt;. You can also access this information using its HTTP API to integrate Baseline data into your tooling workflow, and this guide explains how.&lt;/p&gt;

&lt;h2 id=&quot;the_web_platform_dashboards_relevant_baseline_query_grammar&quot; data-text=&quot;The Web Platform Dashboard&#39;s relevant Baseline query grammar&quot; tabindex=&quot;-1&quot;&gt;The Web Platform Dashboard&#39;s relevant Baseline query grammar&lt;/h2&gt;

&lt;p&gt;The Web Platform dashboard uses a specific &lt;a href=&quot;https://github.com/GoogleChrome/webstatus.dev/blob/main/antlr/FeatureSearch.md&quot;&gt;query grammar&lt;/a&gt; to help you search web feature support. You can use this query grammar directly on the dashboard itself.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/web-platform-dashboard-baseline/image/fig-1.png&quot; alt=&quot;A list of features on the Web Platform Status dashboard, showing the baseline_status query parameter in action to display Baseline Newly available features.&quot; width=&quot;800&quot; height=&quot;386&quot; srcset=&quot;https://web.dev/static/articles/web-platform-dashboard-baseline/image/fig-1_36.png 36w,https://web.dev/static/articles/web-platform-dashboard-baseline/image/fig-1_48.png 48w,https://web.dev/static/articles/web-platform-dashboard-baseline/image/fig-1_72.png 72w,https://web.dev/static/articles/web-platform-dashboard-baseline/image/fig-1_96.png 96w,https://web.dev/static/articles/web-platform-dashboard-baseline/image/fig-1_480.png 480w,https://web.dev/static/articles/web-platform-dashboard-baseline/image/fig-1_720.png 720w,https://web.dev/static/articles/web-platform-dashboard-baseline/image/fig-1_856.png 856w,https://web.dev/static/articles/web-platform-dashboard-baseline/image/fig-1_960.png 960w,https://web.dev/static/articles/web-platform-dashboard-baseline/image/fig-1_1440.png 1440w,https://web.dev/static/articles/web-platform-dashboard-baseline/image/fig-1_1920.png 1920w,https://web.dev/static/articles/web-platform-dashboard-baseline/image/fig-1_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption&gt;
    A listing of features on the Web Platform Dashboard. The list is based on a search that shows only Baseline Newly available features using the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;baseline_status:newly&lt;/code&gt; search query.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;As you type in the search box at the top of the page, you&#39;ll be presented with a number of query parameters you can use to filter web features.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/web-platform-dashboard-baseline/image/fig-2.png&quot; alt=&quot;The autocomplete feature of the Web Platform Status Dashboard, showing an initial value of &#39;baseline&#39;, revealing all of the available query parameters beginning with that initial value.&quot; width=&quot;800&quot; height=&quot;257&quot; srcset=&quot;https://web.dev/static/articles/web-platform-dashboard-baseline/image/fig-2_36.png 36w,https://web.dev/static/articles/web-platform-dashboard-baseline/image/fig-2_48.png 48w,https://web.dev/static/articles/web-platform-dashboard-baseline/image/fig-2_72.png 72w,https://web.dev/static/articles/web-platform-dashboard-baseline/image/fig-2_96.png 96w,https://web.dev/static/articles/web-platform-dashboard-baseline/image/fig-2_480.png 480w,https://web.dev/static/articles/web-platform-dashboard-baseline/image/fig-2_720.png 720w,https://web.dev/static/articles/web-platform-dashboard-baseline/image/fig-2_856.png 856w,https://web.dev/static/articles/web-platform-dashboard-baseline/image/fig-2_960.png 960w,https://web.dev/static/articles/web-platform-dashboard-baseline/image/fig-2_1440.png 1440w,https://web.dev/static/articles/web-platform-dashboard-baseline/image/fig-2_1920.png 1920w,https://web.dev/static/articles/web-platform-dashboard-baseline/image/fig-2_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption&gt;
    The search box on the Web Platform Status Dashboard will reveal available query parameters as you type. In this case, the autocomplete suggests Baseline-related parameters to filter by Baseline status, or even a date range.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The query grammar is expressive, giving you a lot of flexibility in how to filter the web features that are displayed on the dashboard. The following screenshot shows how the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;id&lt;/code&gt; query parameter can be used to narrow down to a specific feature:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/web-platform-dashboard-baseline/image/fig-3.png&quot; alt=&quot;The Web Platform status dashboard showing how to query support by feature in the search box by using the id parameter.&quot; width=&quot;800&quot; height=&quot;186&quot; srcset=&quot;https://web.dev/static/articles/web-platform-dashboard-baseline/image/fig-3_36.png 36w,https://web.dev/static/articles/web-platform-dashboard-baseline/image/fig-3_48.png 48w,https://web.dev/static/articles/web-platform-dashboard-baseline/image/fig-3_72.png 72w,https://web.dev/static/articles/web-platform-dashboard-baseline/image/fig-3_96.png 96w,https://web.dev/static/articles/web-platform-dashboard-baseline/image/fig-3_480.png 480w,https://web.dev/static/articles/web-platform-dashboard-baseline/image/fig-3_720.png 720w,https://web.dev/static/articles/web-platform-dashboard-baseline/image/fig-3_856.png 856w,https://web.dev/static/articles/web-platform-dashboard-baseline/image/fig-3_960.png 960w,https://web.dev/static/articles/web-platform-dashboard-baseline/image/fig-3_1440.png 1440w,https://web.dev/static/articles/web-platform-dashboard-baseline/image/fig-3_1920.png 1920w,https://web.dev/static/articles/web-platform-dashboard-baseline/image/fig-3_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption&gt;
    The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;id&lt;/code&gt; parameter in this example has a value of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;html&lt;/code&gt;, which indicates support for the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;html&amp;gt;&lt;/code&gt; element, which is (unsurprisingly) Baseline Widely available.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The &lt;a href=&quot;https://github.com/GoogleChrome/webstatus.dev/blob/main/antlr/FeatureSearch.md&quot;&gt;query grammar&lt;/a&gt; is documented—but knowing all of it is &lt;em&gt;not&lt;/em&gt; required to find Baseline data for features. You can use any of these options on the dashboard, but here are some that are especially useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;baseline_status&lt;/code&gt;: Use this parameter to filter features by three enumerated values:
&lt;ul&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;limited&lt;/code&gt;: Returns features that have limited browser support. If you filter by this value, you&#39;ll only receive features that have not reached any Baseline status.&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;newly&lt;/code&gt;: Returns features that are Baseline Newly available.&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;widely&lt;/code&gt;: Returns features that are Baseline Widely available, which are features that have been Baseline for at least 30 months. These are features that you can expect to use without worrying about browser support or polyfills.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;baseline_date&lt;/code&gt;: Uses a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;YYYY-MM-DD..YYYY-MM-DD&lt;/code&gt; format to specify an upper and lower bound for when features reached Baseline. For example, to find all features in the span of a year, use a value like &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;2024-01-01..2025-01-01&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;id&lt;/code&gt;: The identifier for a given feature. These identifiers are defined in the &lt;a href=&quot;https://github.com/web-platform-dx/web-features/tree/main/features&quot;&gt;web-features&lt;/a&gt; package. For example, the &lt;a href=&quot;https://github.com/web-platform-dx/web-features/blob/main/features/promise-try.yml&quot;&gt;feature entry for &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;Promise.try()&lt;/code&gt;&lt;/a&gt; maps to an ID of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;promise-try&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;group&lt;/code&gt;: One of many &lt;a href=&quot;https://github.com/web-platform-dx/web-features/tree/main/groups&quot;&gt;enumerated group names&lt;/a&gt; for features. This is useful criteria if you only want to query for a certain subset of web platform features. For example, you could filter to a list of CSS features with a value of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;css&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While the dashboard frontend is certainly useful, it rests on top of an HTTP API that you can query directly. For example, here&#39;s &lt;a href=&quot;https://api.webstatus.dev/v1/features?q=baseline_status:newly&quot;&gt;an endpoint&lt;/a&gt; that gets all Baseline features that are Newly available:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;Text only&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;https://api.webstatus.dev/v1/features?q=baseline_status:newly
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;aside class=&quot;note&quot;&gt;
  &lt;b&gt;Note:&lt;/b&gt; There are also logical query modifiers, such as &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;AND&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;OR&lt;/code&gt; keywords, or negation using the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;-&lt;/code&gt; operator. Consult the &lt;a href=&quot;https://github.com/GoogleChrome/webstatus.dev/blob/main/antlr/FeatureSearch.md&quot;&gt;full querying documentation&lt;/a&gt; for more information.
&lt;/aside&gt;

&lt;h2 id=&quot;the_json_response_structure&quot; data-text=&quot;The JSON response structure&quot; tabindex=&quot;-1&quot;&gt;The JSON response structure&lt;/h2&gt;

&lt;p&gt;The JSON response you receive from the HTTP API has a consistent shape for every feature. The response returned contains a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;data&lt;/code&gt; property at the top level. This property contains an array of all matching features. While this isn&#39;t exhaustive list of &lt;em&gt;all&lt;/em&gt; the fields available in the JSON response, here are some useful ones when it comes to Baseline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;baseline&lt;/code&gt;: Contains general information about the Baseline status of a given feature, with the following subfields:
&lt;ul&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;status&lt;/code&gt;: The Baseline status for a feature. Values can be either &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;limited&lt;/code&gt;, &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;newly&lt;/code&gt;, or &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;widely&lt;/code&gt;. &lt;strong&gt;Note:&lt;/strong&gt; this will be the only subfield if &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;status&lt;/code&gt; has a value of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;limited&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;low_date&lt;/code&gt;: This indicates the date on which the given feature became Baseline Newly available. This field only appears if &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;status&lt;/code&gt; is either &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;newly&lt;/code&gt; or &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;widely&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;high_date&lt;/code&gt;: This indicates the date on which the given feature became Baseline Widely available. This field is only available if &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;status&lt;/code&gt; is &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;widely&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;feature_id&lt;/code&gt;: The ID for the feature. For example, for CSS grid, this would be a value of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&quot;grid&quot;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;name&lt;/code&gt;: The formatted name of the feature. This can be similar to &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;feature_id&lt;/code&gt; in some cases, but is usually different. For example, the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;feature_id&lt;/code&gt; value for &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;Promise.try()&lt;/code&gt; is &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&quot;promise-try&quot;&lt;/code&gt;, whereas the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;name&lt;/code&gt; field for the same feature is &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&quot;Promise.try()&quot;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;spec&lt;/code&gt;: This field contains a subfield named &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;links&lt;/code&gt;, which is an array of links to specifications and other resources.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are other fields, often containing information about when specific supporting browsers implemented the feature at which version, data about &lt;a href=&quot;https://web-platform-tests.org/&quot;&gt;Web Platform Tests&lt;/a&gt;, and other things you may or may not care about.&lt;/p&gt;

&lt;h2 id=&quot;example_queries&quot; data-text=&quot;Example queries&quot; tabindex=&quot;-1&quot;&gt;Example queries&lt;/h2&gt;

&lt;p&gt;Now that you have a quick explanation of some of the available query parameters, take a look at some example queries that you can use in your tools and scripts to select web status features that could be useful for your workflow.&lt;/p&gt;

&lt;h3 id=&quot;get_data_for_a_single_feature&quot; data-text=&quot;Get data for a single feature&quot; tabindex=&quot;-1&quot;&gt;Get data for a single feature&lt;/h3&gt;

&lt;p&gt;A good way to get familiar with the API is to start with a basic example that gets data for a single web feature.&lt;/p&gt;

&lt;aside class=&quot;beta&quot;&gt;
  &lt;b&gt;Try it:&lt;/b&gt; You can see how querying works by pasting the code snippets in this guide in the console of your browser&#39;s developer tools.
&lt;/aside&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JavaScript&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;// Specify and encode the query for a query string:&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;encodeURIComponent&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;id:grid&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-c1&quot;&gt;// Construct the URL:&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-kd&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-sb&quot;&gt;`https://api.webstatus.dev/v1/features?q=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-si&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;devsite-syntax-si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;devsite-syntax-sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-c1&quot;&gt;// Fetch the resource:&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;// Convert the response to JSON:&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;// Log data for each feature to the:&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;In this case, we specify an &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;id&lt;/code&gt; parameter with a value of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;grid&lt;/code&gt; to get feature support information for CSS grid, which is Baseline Widely available. You could use this information, for example, to detect usage of the feature and let users know that they can use it in all modern browser engines without worrying about support.&lt;/p&gt;

&lt;p&gt;This is just a start, though, and the HTTP API can do much more for you than get data for a single feature.&lt;/p&gt;

&lt;h3 id=&quot;get_all_baseline_newly_and_widely_available_features&quot; data-text=&quot;Get all Baseline Newly and Widely available features&quot; tabindex=&quot;-1&quot;&gt;Get all Baseline Newly and Widely available features&lt;/h3&gt;

&lt;p&gt;Say you&#39;d like to have a script that pulls in all features that are either Baseline Newly or Widely available. This could be useful, for example, if you have a script that runs on some interval and you&#39;d like to get an updated list that changes as features emerge from limited availability to either Baseline status:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JavaScript&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;encodeURIComponent&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;-baseline_status:limited&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-kd&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-sb&quot;&gt;`https://api.webstatus.dev/v1/features?q=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-si&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;devsite-syntax-si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;devsite-syntax-sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;aside class=&quot;note&quot;&gt;
  &lt;b&gt;Note:&lt;/b&gt; Take notice of the use of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;-baseline_status:limited&lt;/code&gt; for the query. This excludes any feature with a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;status&lt;/code&gt; of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;limited&lt;/code&gt;. An equivalent query would be &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;baseline_status:newly AND baseline_status:widely&lt;/code&gt;.
&lt;/aside&gt;

&lt;p&gt;This query doesn&#39;t get &lt;em&gt;all&lt;/em&gt; Baseline Newly and Widely available features, but only the first 100. If the number of features retrieved exceeds this, there is a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;metadata&lt;/code&gt; field at the top level of the JSON response that can contain up to two subfields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;next_page_token&lt;/code&gt;: A string containing a token you can add to the query string of the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;GET&lt;/code&gt; request to the backend. When you use it and re-fetch data from the webstatus.dev backend, it will return the next batch of matching features. &lt;strong&gt;Note:&lt;/strong&gt; This field will be unavailable if the current query returns less than 100 results, or if the current query is at the end of the result set.&lt;/li&gt;
&lt;li&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;total&lt;/code&gt;: An integer indicating the total number of features available for the current query.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These fields are useful for pagination. With them, we can try the following code to get all of the results for a query that returns a large amount of data:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JavaScript&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;async&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;queryWebStatusDashboard&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;urlBase&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;https://api.webstatus.dev/v1/features?q=&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kd&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;queryUrl&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;devsite-syntax-si&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;urlBase&lt;/span&gt;&lt;span class=&quot;devsite-syntax-si&quot;&gt;}${&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;encodeURIComponent&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;devsite-syntax-sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;queryUrl&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;+=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-sb&quot;&gt;`&amp;amp;page_token=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-si&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;encodeURIComponent&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;devsite-syntax-sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;queryUrl&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;// See if there&#39;s a page token in this query:&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;next_page_token&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-ow&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;next_page_token&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;queryWebStatusDashboard&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;next_page_token&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;All results collected&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-c1&quot;&gt;// Make the first query, and if there are more&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-c1&quot;&gt;// than 100 entries, the function will run&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-c1&quot;&gt;// recursively until all features are fetched&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-nx&quot;&gt;queryWebStatusDashboard&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;-baseline_status:limited&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;aside class=&quot;caution&quot;&gt;
  &lt;b&gt;Caution:&lt;/b&gt; The prior code example is meant to be illustrative of how you can access paginated results. It&#39;s not necessarily something you should use in a production environment.
&lt;/aside&gt;

&lt;h3 id=&quot;get_all_baseline_newly_available_css_features&quot; data-text=&quot;Get all Baseline Newly available CSS features&quot; tabindex=&quot;-1&quot;&gt;Get all Baseline Newly available CSS features&lt;/h3&gt;

&lt;p&gt;Say you&#39;re an engineer with a special focus on CSS, and you&#39;re interested to know which CSS features become Baseline Newly available features. This is a perfect use case for the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;group&lt;/code&gt; query value, as well as how to use the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;AND&lt;/code&gt; operator when querying webstatus.dev:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JavaScript&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;encodeURIComponent&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;baseline_status:newly AND group:css&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-kd&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-sb&quot;&gt;`https://api.webstatus.dev/v1/features?q=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-si&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;devsite-syntax-si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;devsite-syntax-sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;When specifying a value of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;css&lt;/code&gt; for &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;group&lt;/code&gt;, you can query for all Baseline Newly available features. If you wanted to expand the scope to include Baseline Widely available CSS features as well, you could use the approach from the last code sample and use the negation operator with a query such as &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;-baseline_status:limited AND group:css&#39;&lt;/code&gt;.&lt;/p&gt;

&lt;aside class=&quot;note&quot;&gt;
  &lt;b&gt;Note:&lt;/b&gt; For an enumerated list of all possible &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;group&lt;/code&gt; values, consult the &lt;a href=&quot;https://github.com/web-platform-dx/web-features/tree/main/groups&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;web-features&lt;/code&gt; GitHub repository&lt;/a&gt;.
&lt;/aside&gt;

&lt;p&gt;Another field you can query is the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;snapshot&lt;/code&gt; field, which is useful for finding JavaScript features that are part of a specific set of ECMAScript features. The following code checks for all Baseline Newly available features that are part of the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;ecmascript-2023&lt;/code&gt; snapshot:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JavaScript&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;encodeURIComponent&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;baseline_status:newly AND snapshot:ecmascript-2023&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-kd&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-sb&quot;&gt;`https://api.webstatus.dev/v1/features?q=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-si&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;devsite-syntax-si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;devsite-syntax-sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;aside class=&quot;note&quot;&gt;
  &lt;b&gt;Note:&lt;/b&gt; For a full list of available snapshots you can query against, &lt;a href=&quot;https://github.com/web-platform-dx/web-features/tree/main/snapshots&quot;&gt;consult the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;web-features&lt;/code&gt; GitHub repository&lt;/a&gt;.
&lt;/aside&gt;

&lt;h3 id=&quot;get_all_baseline_features_within_a_date_range&quot; data-text=&quot;Get all Baseline features within a date range&quot; tabindex=&quot;-1&quot;&gt;Get all Baseline features within a date range&lt;/h3&gt;

&lt;p&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;baseline_date&lt;/code&gt; field can be queried to find all features that became Baseline within a specific date range:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JavaScript&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nb&quot;&gt;encodeURIComponent&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;baseline_status:widely AND baseline_date:2022-01-01..2022-12-31 AND group:css&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-kd&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-sb&quot;&gt;`https://api.webstatus.dev/v1/features?q=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-si&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;devsite-syntax-si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;devsite-syntax-sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;The preceding code sample will query for CSS features that became Baseline Widely available at any point in 2022. The query syntax for &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;baseline_date&lt;/code&gt; lets you specify a beginning and ending date, separated by &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;..&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot; data-text=&quot;Conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;The queries in this guide are meant to be a starting point for you to begin experimenting with how to query the webstatus.dev backend. With any number of available query parameters, you should be able to get results that are specific to your application.&lt;/p&gt;

&lt;p&gt;Knowing how to query the Web Platform Dashboard&#39;s HTTP API gives you the ability to build tools that might be useful for your work, and keep you informed as to whether the features you want to use for your project have broad enough browser support. This means you can build web applications using modern browser features that you can safely use, providing a much more enjoyable developer experience.&lt;/p&gt;

  

  
</description><link>https://web.dev/articles/web-platform-dashboard-baseline?hl=en</link><guid isPermaLink="false">https://web.dev/articles/web-platform-dashboard-baseline?hl=en</guid><pubDate>Tue, 04 Mar 2025 08:00:00 GMT</pubDate></item><item><title>Core Web Vitals workflows with Google tools</title><description>&lt;p&gt;Combine Google tools to audit, improve and monitor your website effectively.&lt;/p&gt;

&lt;p&gt;&lt;style&gt;
  .wd-author__links {
    display: flex;
  }
  &lt;/style&gt;

&lt;/p&gt;&lt;p&gt;&lt;/p&gt;

&lt;p&gt;
&lt;/p&gt;&lt;p class=&quot;wd-pubdates&quot; style=&quot;font-size: 90%; border-bottom: 1px solid var(--devsite-ref-palette--grey200);color: var(--devsite-ref-palette--blue-grey700);&quot;&gt;
  Published: May 28, 2020
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://web.dev/articles/vitals&quot;&gt;Core Web Vitals&lt;/a&gt; are a set of metrics that assess the user experience on criteria such as load performance, responsiveness to user input, and layout stability.&lt;/p&gt;

&lt;p&gt;A workflow for improving Core Web Vitals for your website will be explored in this guide, but where that workflow begins depends on whether you&#39;re collecting your own field data. Where it ends may depend on which of Google&#39;s tools you&#39;ll find useful in diagnosing and fixing user experience problems.&lt;/p&gt;

&lt;h2 id=&quot;best-measured-in-the-field&quot; data-text=&quot;Core Web Vitals are best measured in the field&quot; tabindex=&quot;-1&quot;&gt;Core Web Vitals are best measured in the field&lt;/h2&gt;

&lt;p&gt;Core Web Vitals are specifically designed to measure how users experience your website—they are &lt;a href=&quot;https://web.dev/articles/user-centric-performance-metrics&quot;&gt;user centric metrics&lt;/a&gt;. Lab-based tools such as Lighthouse are diagnostic tools to highlight potential performance problems and best practices. Lab-based tools are run under certain, predefined conditions and may not reflect the real-life Core Web Vitals measurements your users experience.&lt;/p&gt;

&lt;aside class=&quot;key-term&quot;&gt;
&lt;a href=&quot;https://web.dev/articles/lab-and-field-data-differences#lab_data&quot;&gt;&lt;strong&gt;Lab data&lt;/strong&gt;&lt;/a&gt; describes how &lt;em&gt;hypothetical&lt;/em&gt; users &lt;em&gt;may&lt;/em&gt; experience your website. &lt;a href=&quot;https://web.dev/articles/lab-and-field-data-differences#field_data&quot;&gt;&lt;strong&gt;Field data&lt;/strong&gt;&lt;/a&gt; describes how &lt;em&gt;real&lt;/em&gt; users &lt;em&gt;actually&lt;/em&gt; experienced your website. Field data is also known as Real User Monitoring (RUM), and is typically collected by monitoring real user experiences using JavaScript on the pages they load, and reporting various metrics to an analytics solution.
&lt;/aside&gt;

&lt;p&gt;For example, Lighthouse is a lab-based tool that runs tests with simulated throttling in a simulated desktop or mobile environment. While such simulations of slower network and device conditions are helpful when trying to diagnose performance problems, &lt;a href=&quot;https://web.dev/articles/lab-and-field-data-differences&quot;&gt;they&#39;re just a single slice&lt;/a&gt; of the large variety in network conditions and device capabilities, and so may not reflect what users on your sites are experiening.&lt;/p&gt;

&lt;p&gt;Lab-based tools like Lighthouse also typically do a &quot;cold load&quot; of a web page as a totally new visitor. This is often the slowest load, but in real life, visitors may have some assets cached if they have visited before, or when they are browsing around the site. New visitors, and tools, also may experience the site differently with cookie banners or other content present to them.&lt;/p&gt;

&lt;p&gt;In short, while lab-based tools can give an indication of potential performance problems and help you debug and iterate, they may not represent how many of your visitors actually experience your website. Use field data for measuring real-world performance, and lab-based tools like Lighthouse for diagnostics of how to improve it. See also the &lt;a href=&quot;https://web.dev/articles/vitals-tools?hl=en#when_to_use_lighthouse&quot;&gt;When to use Lighthouse&lt;/a&gt; section.&lt;/p&gt;

&lt;p&gt;Google measures Core Web Vitals through the &lt;a href=&quot;https://developer.chrome.com/docs/crux&quot;&gt;Chrome User Experience Report (CrUX)&lt;/a&gt;. This is a public dataset collected from real Chrome users. It is the backbone of many Google and third-party tools which report a site&#39;s Core Web Vitals.&lt;/p&gt;

&lt;p&gt;CrUX has its limitations, though. It can often tell you &lt;em&gt;when&lt;/em&gt; there is a problem, but often has insufficient data to tell you &lt;em&gt;why&lt;/em&gt;.&lt;/p&gt;

&lt;h3 id=&quot;collect-own-field-data&quot; data-text=&quot;Collect your own field data if possible&quot; tabindex=&quot;-1&quot;&gt;Collect your own field data if possible&lt;/h3&gt;

&lt;p&gt;The best dataset for improving website performance in the field is the one &lt;em&gt;you&lt;/em&gt; build. That starts with collecting field data from your website&#39;s visitors. How you do this depends on the size of your organization, and whether you want to pay for &lt;a href=&quot;https://en.wikipedia.org/wiki/Real_user_monitoring#RUM_software&quot;&gt;a third-party solution&lt;/a&gt; or create your own.&lt;/p&gt;

&lt;p&gt;Paid solutions will almost certainly measure Core Web Vitals (and other performance metrics) and usually provide a variety of tools to dig into the resulting data. In large organizations with significant resources, this may be the preferred method.&lt;/p&gt;

&lt;p&gt;However, you may not be part of a large organization—or even one with the means to afford a third-party solution. In these cases, Google&#39;s &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;web-vitals&lt;/code&gt; library&lt;/a&gt; will help you gather &lt;em&gt;all&lt;/em&gt; Web Vitals. However, you&#39;ll be responsible for how that data is reported, stored, and analyzed.&lt;/p&gt;

&lt;aside class=&quot;note&quot;&gt;
If you&#39;re building your own metrics collection and reporting system, this &lt;a href=&quot;https://web.dev/articles/vitals-field-measurement-best-practices&quot;&gt;guide of best practices&lt;/a&gt; is worth a read to avoid common pitfalls.
&lt;/aside&gt;

&lt;p&gt;If you&#39;re already using Google Analytics, but you haven&#39;t started collecting your own field data, there may be an opportunity for you to use the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;web-vitals&lt;/code&gt; library to &lt;a href=&quot;https://web.dev/articles/debug-performance-in-the-field#usage_with_the_web_vitals_javascript_library&quot;&gt;send Web Vitals collected in the field to Google Analytics&lt;/a&gt; and use the &lt;a href=&quot;https://web.dev/articles/vitals-ga4&quot;&gt;BigQuery exports of GA4&lt;/a&gt; to report on the data.&lt;/p&gt;

&lt;h2 id=&quot;googles-tools&quot; data-text=&quot;Understand Google&#39;s tools&quot; tabindex=&quot;-1&quot;&gt;Understand Google&#39;s tools&lt;/h2&gt;

&lt;p&gt;Regardless of whether you&#39;re collecting your own field data, there are several Google tools that may be useful in analyzing Core Web Vitals. Before establishing a workflow, a high-level overview of each tool can help you to understand which tools may—or may not—be best for you.&lt;/p&gt;

&lt;aside class=&quot;note&quot;&gt;
As you continue, understand that it&#39;s not necessary for you to use &lt;em&gt;all&lt;/em&gt; of the tools in this guide—only those that you believe will help you in improving your website&#39;s Core Web Vitals.
&lt;/aside&gt;

&lt;h3 id=&quot;crux&quot; data-text=&quot;Chrome User Experience Report (CrUX)&quot; tabindex=&quot;-1&quot;&gt;Chrome User Experience Report (CrUX)&lt;/h3&gt;

&lt;p&gt;As mentioned previously, CrUX is a public dataset of field data gathered from &lt;a href=&quot;https://developer.chrome.com/docs/crux/methodology/#user_eligibility&quot;&gt;a segment of real Google Chrome users&lt;/a&gt; from millions of websites. It includes Core Web Vital metrics and other metrics for websites with sufficient traffic.&lt;/p&gt;

&lt;p&gt;CrUX is available as a monthly &lt;a href=&quot;https://developer.chrome.com/docs/crux/bigquery&quot;&gt;BigQuery dataset&lt;/a&gt; at the origin level, or as a &lt;a href=&quot;https://developer.chrome.com/docs/crux/api&quot;&gt;daily API&lt;/a&gt; at the URL or origin-level, provided a URL or origin has enough samples in the CrUX dataset. The CrUX data is available through &lt;a href=&quot;https://developer.chrome.com/docs/crux/methodology/tools&quot;&gt;various CrUX tools&lt;/a&gt; for both programmatic access and visual tools for use by uses.&lt;/p&gt;

&lt;h4 id=&quot;when_to_use_crux&quot; data-text=&quot;When to use CrUX&quot; tabindex=&quot;-1&quot;&gt;When to use CrUX&lt;/h4&gt;

&lt;p&gt;Even if you gather your own field data, CrUX is still useful. Though CrUX represents a subset of Chrome users, it&#39;s helpful to compare your website&#39;s field data to see how it aligns with its CrUX data. There are advantages and disadvantages to each, which &lt;a href=&quot;https://web.dev/articles/crux-and-rum-differences&quot;&gt;can result in differences&lt;/a&gt;. If you don&#39;t gather &lt;em&gt;any&lt;/em&gt; field data for your website, CrUX is especially valuable to provide a high-level overview—provided your website is represented in its dataset.&lt;/p&gt;

&lt;p&gt;You can use CrUX directly, or using another tool (including those mentioned below). Using the CrUX dataset directly, either using BigQuery or the API, is useful to expose data that is not shown in other tools—for example country-level data is often not available on other tools, or to view the &lt;a href=&quot;https://developer.chrome.com/docs/crux/methodology/metrics&quot;&gt;additional metrics in CrUX&lt;/a&gt; which again are often not surfaced in other tooling.&lt;/p&gt;

&lt;h4 id=&quot;when_not_to_use_crux&quot; data-text=&quot;When not to use CrUX&quot; tabindex=&quot;-1&quot;&gt;When &lt;em&gt;not&lt;/em&gt; to use CrUX&lt;/h4&gt;

&lt;p&gt;CrUX only represents Chrome users, and even then, &lt;a href=&quot;https://developer.chrome.com/docs/crux/methodology/#user_eligibility&quot;&gt;only a subset of Chrome users&lt;/a&gt;. A full RUM solution can include more experiences across Chrome and other browsers where they support the Web Vital metrics.&lt;/p&gt;

&lt;p&gt;Websites that don&#39;t receive enough traffic are not represented in the CrUX dataset. If this is the case for you, you&#39;ll need to gather your own field data to understand how your website performs in the field, as CrUX won&#39;t be an option. Alternatively, you will need to depend on lab data, but with the limitations that it may not be representative as described previously.&lt;/p&gt;

&lt;p&gt;Since the data CrUX provides is a rolling average over the previous 28 days, it&#39;s not an ideal tool during development, as it will take a fair amount of time for improvements to be reflected in the CrUX dataset.&lt;/p&gt;

&lt;p&gt;Finally, as a public dataset, CrUX is limited to how much information it can make available, and how this data can be queried. Capturing your own RUM data lets you gather more details (for example, &lt;a href=&quot;https://web.dev/articles/debug-performance-in-the-field#lcp&quot;&gt;the LCP element&lt;/a&gt;), and segment the data more to identify problems. Do logged in users experience better or worse Core Web Vitals than logged out users? Do users with a slow LCP have a particular LCP element? Which interactions are causing high FID and INP values?&lt;/p&gt;

&lt;h3 id=&quot;pagespeed-insights&quot; data-text=&quot;PageSpeed Insights (PSI)&quot; tabindex=&quot;-1&quot;&gt;PageSpeed Insights (PSI)&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://pagespeed.web.dev/&quot;&gt;PSI&lt;/a&gt; is a tool that reports field data from CrUX &lt;em&gt;and&lt;/em&gt; lab from Lighthouse for a given page. See those individual sections for more details.&lt;/p&gt;

&lt;h4 id=&quot;when_to_use_psi&quot; data-text=&quot;When to use PSI&quot; tabindex=&quot;-1&quot;&gt;When to use PSI&lt;/h4&gt;

&lt;p&gt;PSI is great for assessing CrUX performance at the page level or origin-level, for both mobile and desktop users. It&#39;s a good choice for an initial overview of Core Web Vitals for a page or site. It also lets you view Core Web Vitals data for other sites like competitors.&lt;/p&gt;

&lt;p&gt;PSI also provides Lighthouse data, which gives useful recommendations to improve your Core Web Vitals—if the metrics align. Where these don&#39;t align, Lighthouse recommendations may be less relevant.&lt;/p&gt;

&lt;p&gt;Since Lighthouse is run from the server, it can form a more consistent baseline than running Lighthouse from DevTools.&lt;/p&gt;

&lt;h4 id=&quot;when_not_to_use_psi&quot; data-text=&quot;When not to use PSI&quot; tabindex=&quot;-1&quot;&gt;When &lt;em&gt;not&lt;/em&gt; to use PSI&lt;/h4&gt;

&lt;p&gt;PSI is only available for public URLs. It cannot be used on development sites that are not publicly accessible.&lt;/p&gt;

&lt;p&gt;CrUX data is only available when sites meet certain &lt;a href=&quot;https://developer.chrome.com/docs/crux/methodology/#eligibility&quot;&gt;eligibility criteria&lt;/a&gt;, including site popularity thresholds. PSI is less useful when CrUX data is not available for a page or origin as it can only show the Lighthouse lab data in these cases.&lt;/p&gt;

&lt;p&gt;Similarly, if you only have origin-level CrUX data rather than the specific URL being tested, then this also limits its usefulness of correlating the origin-level field data to the page-level lab diagnostics. Having the origin-level field data is still very useful information to have as a summary of the site&#39;s performance and the Lighthouse audits may help, but extra caution should be used in this case.&lt;/p&gt;

&lt;p&gt;Finally, where page-level data is available in CrUX, but differs from the Lighthouse lab data, recommendations from Lighthouse may be of limited value. This can happen particularly for &lt;a href=&quot;https://web.dev/articles/optimize-cls#identifying_post_load_cls_issues&quot;&gt;post-load CLS issues&lt;/a&gt;, and for interactivity Core Web Vitals (FID and INP) where lab-based audits are less useful.&lt;/p&gt;

&lt;h3 id=&quot;search-console&quot; data-text=&quot;Search Console&quot; tabindex=&quot;-1&quot;&gt;Search Console&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://search.google.com/search-console/about&quot;&gt;Search Console&lt;/a&gt; measures your site&#39;s search traffic and performance, &lt;a href=&quot;https://support.google.com/webmasters/answer/9205520&quot;&gt;including Core Web Vitals&lt;/a&gt;. It is only available to site owners who have &lt;a href=&quot;https://support.google.com/webmasters/answer/34592&quot;&gt;confirmed their ownership of the site&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A valuable feature of Search Console is that it groups similar pages (for example, pages that use the same template) into a single group assessment. Search Console also includes a Core Web Vitals report based on field data from CrUX.&lt;/p&gt;

&lt;h4 id=&quot;when_to_use_search_console&quot; data-text=&quot;When to use Search Console&quot; tabindex=&quot;-1&quot;&gt;When to use Search Console&lt;/h4&gt;

&lt;p&gt;Search Console is well-suited for both developers and those in non-developer roles to assess both search and page performance in ways other Google tools don&#39;t. Its presentation of CrUX data and grouping of pages by similarity offers novel insight into how performance improvements impact entire categories of pages.&lt;/p&gt;

&lt;h4 id=&quot;when_not_to_use_search_console&quot; data-text=&quot;When not to use Search Console&quot; tabindex=&quot;-1&quot;&gt;When &lt;em&gt;not&lt;/em&gt; to use Search Console&lt;/h4&gt;

&lt;p&gt;Search Console may not be a fit for projects that use different third-party tools which group pages by similarity, or if a website isn&#39;t represented in the CrUX dataset.&lt;/p&gt;

&lt;p&gt;Page grouping can also be somewhat confusing when the example pages in a group have different characteristics than the rest of the group—for example, if the group fails particular Core Web Vitals overall, but the example pages all seem to pass the same Core Web Vitals. This can happen when a group contains a long tail or seldomly visited pages that may be slower to load, as they&#39;re less likely to be cached. When there are sufficient volumes of these pages in the long tail, they can influence the group&#39;s overall pass rate.&lt;/p&gt;

&lt;h3 id=&quot;lighthouse&quot; data-text=&quot;Lighthouse&quot; tabindex=&quot;-1&quot;&gt;Lighthouse&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/overview&quot;&gt;Lighthouse&lt;/a&gt; is a lab tool that provides specific opportunities for improving page performance. &lt;a href=&quot;https://web.dev/articles/lighthouse-user-flows&quot;&gt;Lighthouse user flows&lt;/a&gt; also allow developers to script interaction flows for performance testing beyond page load.&lt;/p&gt;

&lt;aside class=&quot;note&quot;&gt;
Because Lighthouse is a lab tool, and &lt;a href=&quot;https://web.dev/articles/inp&quot;&gt;Interaction to Next Paint (INP)&lt;/a&gt; is a field metric, it reports &lt;a href=&quot;https://web.dev/articles/tbt&quot;&gt;Total Blocking Time&lt;/a&gt; instead.
&lt;/aside&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/GoogleChrome/lighthouse-ci&quot;&gt;Lighthouse-CI&lt;/a&gt; is a related tool that runs Lighthouse during project builds and deploys to assist with performance regression testing. It presents a Lighthouse report along with pull requests, and tracks performance metrics over time.&lt;/p&gt;

&lt;h4 id=&quot;when_to_use_lighthouse&quot; data-text=&quot;When to use Lighthouse&quot; tabindex=&quot;-1&quot;&gt;When to use Lighthouse&lt;/h4&gt;

&lt;p&gt;Lighthouse is excellent for finding performance improvement opportunities during development in both local and staging environments. Lighthouse CI is similarly useful in the build and deploy phases to staging and production environments, where performance regression testing is needed to preserve good user experiences.&lt;/p&gt;

&lt;aside class=&quot;caution&quot;&gt;
Don&#39;t rely solely on Lighthouse CI during production deployments, as you may miss performance regressions that you&#39;d otherwise catch in local and staging environments during development with Lighthouse.
&lt;/aside&gt;

&lt;h4 id=&quot;when_not_to_use_lighthouse&quot; data-text=&quot;When not to use Lighthouse&quot; tabindex=&quot;-1&quot;&gt;When &lt;em&gt;not&lt;/em&gt; to use Lighthouse&lt;/h4&gt;

&lt;p&gt;Lighthouse (or Lighthouse CI) &lt;strong&gt;is &lt;em&gt;not&lt;/em&gt; a substitute for field data&lt;/strong&gt;. Lighthouse is primarily a diagnostic tool listing potential issues and best practices from a predefined page load. The recommendations it surfaces may not always match the performance experienced by your users.&lt;/p&gt;

&lt;aside class=&quot;key-point&quot;&gt;
&lt;strong&gt;Always concentrate on field Core Web Vitals over Lighthouse metrics and scores&lt;/strong&gt;. In particular, the Performance Score of Lighthouse is a broad measure of that lab test and &lt;a href=&quot;https://philipwalton.com/articles/my-challenge-to-the-web-performance-community/&quot;&gt;often does not correlate&lt;/a&gt; with field Core Web Vitals.
&lt;/aside&gt;

&lt;p&gt;While Lighthouse can be used to diagnose production sites through tools like PageSpeed Insights, Lighthouse would ideally be used in development and continuous integration environments to address performance issues before they reach production.&lt;/p&gt;

&lt;p&gt;The audits that Lighthouse provides are also available through &quot;insights&quot; in the Performance panel in Chrome DevTools, which provides a more in-depth analysis of a page&#39;s performance.&lt;/p&gt;

&lt;h3 id=&quot;perf-panel&quot; data-text=&quot;The Performance panel in Chrome DevTools&quot; tabindex=&quot;-1&quot;&gt;The Performance panel in Chrome DevTools&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://developer.chrome.com/docs/devtools&quot;&gt;Chrome DevTools&lt;/a&gt; is a collection of in-browser development tools, including the &lt;a href=&quot;https://developer.chrome.com/docs/devtools/performance/overview&quot;&gt;Performance panel&lt;/a&gt;. The Performance panel is a lab tool that consists of two &quot;modes&quot;:&lt;/p&gt;

&lt;p&gt;When the Performance panel is first opened a Live Metrics screen provides current Core Web Vitals metric, with the ability to import field data from CrUX. It is useful as a &quot;live&quot; view of performance as you interact with the page to attempt to uncover performance issues—particularly for post-load issues you might see with the CLS and INP metrics.&lt;/p&gt;

&lt;p&gt;Secondly, the Performance panel allows developers to capture a profile (or trace) of all page activity during page load or a recorded time period. This view offers deep insight into everything it observes across dimensions such as network, rendering, painting, and scripting activity, as well as a page&#39;s Core Web Vitals. It also includes insights, similar to those provided by Lighthouse.&lt;/p&gt;

&lt;h4 id=&quot;when_to_use_the_performance_panel&quot; data-text=&quot;When to use the Performance panel&quot; tabindex=&quot;-1&quot;&gt;When to use the Performance panel&lt;/h4&gt;

&lt;p&gt;The Performance panel should be used by developers to gain deep insight into a particular page performance.&lt;/p&gt;

&lt;p&gt;The live metrics view can be used to quickly understand the current performance characteristics of the page, and also spot potential problems as the page is interacted with.&lt;/p&gt;

&lt;p&gt;The trace view is particularly useful to debug responsiveness issues affecting INP. Once a poorly responding interaction is identified and repeatable, the Performance panel can provide a wealth of data as to what is going on in the browser to help understand the issue, from main thread blocking, to JavaScript call stacks, to rendering work.&lt;/p&gt;

&lt;h4 id=&quot;when_not_to_use_the_performance_panel&quot; data-text=&quot;When not to use the Performance panel&quot; tabindex=&quot;-1&quot;&gt;When &lt;em&gt;not&lt;/em&gt; to use the Performance panel&lt;/h4&gt;

&lt;p&gt;The Performance panel is a developer tool that primarily provides lab data—though with some field context from CrUX. It&#39;s not a substitute for field data.&lt;/p&gt;

&lt;p&gt;The trace view contains a lot of debugging information, but because of that, it can be difficult to understand for novice developers or those in non-developer roles. However the live metrics view that the panel opens with, addresses this by providing an easier to use interface for those not requiring the full detail.&lt;/p&gt;

&lt;h2 id=&quot;three-step-workflow&quot; data-text=&quot;A three step workflow for ensuring your website&#39;s Core Web Vitals stay healthy&quot; tabindex=&quot;-1&quot;&gt;A three step workflow for ensuring your website&#39;s Core Web Vitals stay healthy&lt;/h2&gt;

&lt;p&gt;When working to improve the user experience, it&#39;s best to think of the process as a continuous cycle. For improving Core Web Vitals and other performance metrics, one approach could be:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Evaluate website health and identify pain points.&lt;/li&gt;
&lt;li&gt;Debug and optimize.&lt;/li&gt;
&lt;li&gt;Monitor with continuous integration tools to catch and prevent regressions.&lt;/li&gt;
&lt;/ol&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/vitals-tools/image/web-vitals-loop.png&quot; alt=&quot;The three step process, rendered as a continuous cycle. The first step reads &#39;Evaluate website health and identify paint points&#39;, the second &#39;Debug and optimize&#39;, and the third &#39;Monitor and continuous development&#39;.&quot; width=&quot;800&quot; height=&quot;450&quot; srcset=&quot;https://web.dev/static/articles/vitals-tools/image/web-vitals-loop_36.png 36w,https://web.dev/static/articles/vitals-tools/image/web-vitals-loop_48.png 48w,https://web.dev/static/articles/vitals-tools/image/web-vitals-loop_72.png 72w,https://web.dev/static/articles/vitals-tools/image/web-vitals-loop_96.png 96w,https://web.dev/static/articles/vitals-tools/image/web-vitals-loop_480.png 480w,https://web.dev/static/articles/vitals-tools/image/web-vitals-loop_720.png 720w,https://web.dev/static/articles/vitals-tools/image/web-vitals-loop_856.png 856w,https://web.dev/static/articles/vitals-tools/image/web-vitals-loop_960.png 960w,https://web.dev/static/articles/vitals-tools/image/web-vitals-loop_1440.png 1440w,https://web.dev/static/articles/vitals-tools/image/web-vitals-loop_1920.png 1920w,https://web.dev/static/articles/vitals-tools/image/web-vitals-loop_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption&gt;Three step workflow&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&quot;evaluate&quot; data-text=&quot;Step 1: Evaluate website health and identify opportunities for improvement&quot; tabindex=&quot;-1&quot;&gt;Step 1: Evaluate website health and identify opportunities for improvement&lt;/h3&gt;

&lt;p&gt;It is best to start with field data to evaluate website health.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use &lt;a href=&quot;https://pagespeed.web.dev/&quot;&gt;PageSpeed Insights&lt;/a&gt; to view overall Core Web Vitals experience metrics on the origin, and specific information on an individual URL.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://search.google.com/search-console/about&quot;&gt;Search Console&lt;/a&gt; can be useful to identify pages which need improvement where its page grouping feature works well for your site.&lt;/li&gt;
&lt;li&gt;If you have RUM data, then that is often the best option to be able to identify particular pages or traffic segments with issues.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Whether you analyze field data you collect yourself or CrUX data, this first step is vital. If you&#39;re not gathering field data, CrUX data may be enough to guide you—again, provided your website is represented in the dataset.&lt;/p&gt;

&lt;h4 id=&quot;analyze-with-psi&quot; data-text=&quot;Analyze site performance with PageSpeed Insights&quot; tabindex=&quot;-1&quot;&gt;Analyze site performance with PageSpeed Insights&lt;/h4&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/vitals-tools/image/psi-crux.png&quot; alt=&quot;How PageSpeed Insights portrays CrUX data for a URL&#39;s Core Web Vitals. Each of the Core Web Vitals is displayed separately, while grouping each Core Web Vital in the &#39;Good&#39;, &#39;Needs Improvement&#39;, and &#39;Poor&#39; thresholds for the last 28 days.&quot; width=&quot;800&quot; height=&quot;485&quot; srcset=&quot;https://web.dev/static/articles/vitals-tools/image/psi-crux_36.png 36w,https://web.dev/static/articles/vitals-tools/image/psi-crux_48.png 48w,https://web.dev/static/articles/vitals-tools/image/psi-crux_72.png 72w,https://web.dev/static/articles/vitals-tools/image/psi-crux_96.png 96w,https://web.dev/static/articles/vitals-tools/image/psi-crux_480.png 480w,https://web.dev/static/articles/vitals-tools/image/psi-crux_720.png 720w,https://web.dev/static/articles/vitals-tools/image/psi-crux_856.png 856w,https://web.dev/static/articles/vitals-tools/image/psi-crux_960.png 960w,https://web.dev/static/articles/vitals-tools/image/psi-crux_1440.png 1440w,https://web.dev/static/articles/vitals-tools/image/psi-crux_1920.png 1920w,https://web.dev/static/articles/vitals-tools/image/psi-crux_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption&gt;Analyze site performance with PageSpeed Insights&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;PageSpeed Insights displays the CrUX data covering the last 28 days of user experience data at the 75th percentile. This means that if 75% of user experiences meet the &lt;a href=&quot;https://web.dev/articles/defining-core-web-vitals-thresholds&quot;&gt;threshold set for a given metric&lt;/a&gt;, then the experience is considered &quot;good&quot;.&lt;/p&gt;

&lt;p&gt;If you have a specific page in mind to look at the performance of, then use that. For an overall view of a site when you first start optimizing, you may want to start with the home page, as it is typically one of the most popular pages on many sites.&lt;/p&gt;

&lt;p&gt;Concentrate on the &lt;strong&gt;what your real users are experiencing&lt;/strong&gt; section of PSI initially. You will see up to four views of the data: mobile and desktop for the URL entered and the whole origin. Compare these and see how they differ. Mobile is typically less performant than desktop since it is a more resource-constrained device operating under potentially less stable network conditions. If the URL and origin data are significantly different, then try to understand why: home pages are often the first pages visited (that is, a landing page) so can be slower than the origin users take the full brunt of an unprimed browser cache. Subsequent pages will likely load faster, as any shared assets will be cached, bringing down the aggregate origin-level data.&lt;/p&gt;

&lt;p&gt;PSI also shows all three Core Web Vitals (LCP, CLS, and INP) plus the diagnostic TTFB and FCP metrics. Are any of the Core Web Vitals failing, and by how much? This will indicate where to concentrate your efforts.&lt;/p&gt;

&lt;p&gt;Understand the relationships between these numbers—particularly for LCP. If LCP is slow, as it is in this example, then look at TTFB and FCP which are both milestones to that metric. In this example we have a 1.8 second TTFB, which is going to make it very tough to meet the 2.5 second recommended threshold for good LCP. This suggests either a slow backend (server issues or a lack of CDN), slower networks, or redirects delaying the first HTML bytes. Look at the &lt;a href=&quot;https://web.dev/articles/optimize-ttfb&quot;&gt;Optimize TTFB guide&lt;/a&gt; for more information. FCP takes another second on top of that, which again may be indicative of slower networks. LCP is not long after FCP in this example suggesting the LCP resource is well optimized once the page itself loads. CrUX also now shows &lt;a href=&quot;https://web.dev/blog/crux-2025-02&quot;&gt;more diagnostic information in resource types and subparts&lt;/a&gt;, which also helps you diagnose LCP issues.&lt;/p&gt;

&lt;p&gt;For CLS, look at the CrUX CLS and the Lighthouse CLS scores to see if this is a load CLS issue (which Lighthouse will catch and advise on), or a post-load CLS issue that Lighthouse won&#39;t catch. More for information &lt;a href=&quot;https://web.dev/articles/optimize-cls#understanding_where_your_shifts_are_coming_from&quot;&gt;see the Optimize CLS guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For responsiveness, look at the INP scores. Look at the TBT audits in Lighthouse to see if a lot of JavaScript processing is happening during the initial page load, which is likely to impact INP. INP can be a tricky metric to improve, so consult the &lt;a href=&quot;https://web.dev/articles/optimize-inp&quot;&gt;Optimize INP guide&lt;/a&gt; for more information.&lt;/p&gt;

&lt;h4 id=&quot;identify-search-console&quot; data-text=&quot;Identify poorly performing pages in Search Console&quot; tabindex=&quot;-1&quot;&gt;Identify poorly performing pages in Search Console&lt;/h4&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/vitals-tools/image/a-screenshot-a-core-web-7b003f1fa24f.png&quot; alt=&quot;Core Web Vitals report in Search Console. The report is broken down into Desktop and Mobile categories, with line graphs detailing the distribution of pages with Core Web Vitals in the &#39;Good&#39;, &#39;Needs Improvement&#39;, and &#39;Poor&#39; categories over time.&quot; width=&quot;800&quot; height=&quot;639&quot; srcset=&quot;https://web.dev/static/articles/vitals-tools/image/a-screenshot-a-core-web-7b003f1fa24f_36.png 36w,https://web.dev/static/articles/vitals-tools/image/a-screenshot-a-core-web-7b003f1fa24f_48.png 48w,https://web.dev/static/articles/vitals-tools/image/a-screenshot-a-core-web-7b003f1fa24f_72.png 72w,https://web.dev/static/articles/vitals-tools/image/a-screenshot-a-core-web-7b003f1fa24f_96.png 96w,https://web.dev/static/articles/vitals-tools/image/a-screenshot-a-core-web-7b003f1fa24f_480.png 480w,https://web.dev/static/articles/vitals-tools/image/a-screenshot-a-core-web-7b003f1fa24f_720.png 720w,https://web.dev/static/articles/vitals-tools/image/a-screenshot-a-core-web-7b003f1fa24f_856.png 856w,https://web.dev/static/articles/vitals-tools/image/a-screenshot-a-core-web-7b003f1fa24f_960.png 960w,https://web.dev/static/articles/vitals-tools/image/a-screenshot-a-core-web-7b003f1fa24f_1440.png 1440w,https://web.dev/static/articles/vitals-tools/image/a-screenshot-a-core-web-7b003f1fa24f_1920.png 1920w,https://web.dev/static/articles/vitals-tools/image/a-screenshot-a-core-web-7b003f1fa24f_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption&gt;Identify poorly performing pages in Search Console&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;While PSI is useful when you have a specific URL you want to test or the site as a whole, Search Console can help target your efforts to particular types of pages. This is particularly useful if many pages share common themes or technologies and Search Console can successfully identify these.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://support.google.com/webmasters/answer/9205520&quot;&gt;Core Web Vitals report in Search Console&lt;/a&gt; shows the big picture of your website&#39;s performance, but you can still drill down into specific pages that need attention. With Search Console, you can also:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Identify individual page groups that need improvement, and those that provide a good user experience.&lt;/li&gt;
&lt;li&gt;Get granular data on performance by URL grouped by status, metric, and groups of similar web pages (such as product detail pages on an ecommerce website).&lt;/li&gt;
&lt;li&gt;Get detailed reports that bucket URLs in each user experience quality category for both mobile and desktop.&lt;/li&gt;
&lt;/ul&gt;

&lt;aside class=&quot;caution&quot;&gt;
The data in Search Console is different from what&#39;s shown in the PageSpeed Insights origin view or other CrUX tools. This is because Search Console organizes information by URL groupings, whereas PageSpeed Insights view and other CrUX tools organizes data by URL or by origin. If you have few poorly performing URLs, but those URLs receive a large portion of overall traffic, Search Console may only show a few poorly performing URLs while other CrUX tooling may show a high percentage of poor user experiences at the origin level.
&lt;/aside&gt;

&lt;p&gt;Once you have some specific pages to look at, you can use PSI as explained previously to gather further understanding of the issues for those pages.&lt;/p&gt;

&lt;h3 id=&quot;debug&quot; data-text=&quot;Step 2: Debug and optimize&quot; tabindex=&quot;-1&quot;&gt;Step 2: Debug and optimize&lt;/h3&gt;

&lt;p&gt;In step 1, you should have identified pages which require performance improvements, and also which of the Core Web Vitals metrics you want to improve. You can use the Google tooling to get further information to understand the root cause to identify the issue.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Look at &lt;a href=&quot;https://web.dev/articles/vitals-tools?hl=en#lighthouse&quot;&gt;Lighthouse&lt;/a&gt; audits get high-level guidance for the page&lt;/li&gt;
&lt;li&gt;Use the &lt;a href=&quot;https://web.dev/articles/vitals-tools?hl=en#perf-panel&quot;&gt;Performance panel&lt;/a&gt; live metrics view to analyze Core Web Vitals in real time.&lt;/li&gt;
&lt;li&gt;Use the Performance panel tracing to debug performance issues and test code changes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For more detailed guidance, see these guides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/articles/optimize-lcp&quot;&gt;Optimizing LCP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/articles/optimize-inp&quot;&gt;Optimizing INP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/articles/optimize-cls&quot;&gt;Optimizing CLS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;opportunities-lighthouse&quot; data-text=&quot;Uncover opportunities with Lighthouse&quot; tabindex=&quot;-1&quot;&gt;Uncover opportunities with Lighthouse&lt;/h4&gt;

&lt;p&gt;PageSpeed Insights runs Lighthouse for you. It is also possible to run Lighthouse from Chrome DevTools, which is useful to validate fixes locally, though the Peformance Panel (covered next) is a more comprehensive tools for identifying and fixing performance issues locally.&lt;/p&gt;

&lt;p&gt;A key point is to validate that the Lighthouse audit replicates the issues you are trying to solve (for example, slow LCP, or CLS issues). Out of the box, Lighthouse only assesses the user experience during page load. Since it&#39;s a lab tool, it also excludes INP in favor of TBT.&lt;/p&gt;

&lt;aside class=&quot;caution&quot;&gt;
By default, Lighthouse simulates a mid-tier mobile device on a throttled slow 4G connection. This may find issues that wouldn&#39;t ordinarily appear on high-speed devices and fast internet connections. This simulated throttling may not be representative of the variety of user experiences among your website&#39;s user base at the 75th percentile. However, these metrics are an indicator of where performance problems exist, and may translate into better performance overall in the field if the problems Lighthouse finds are addressed.
&lt;/aside&gt;

&lt;p&gt;When the Lighthouse metrics suggest a similar problem as the one you are trying to solve, the wealth of information in its audits can help identify issues and suggest solutions.&lt;/p&gt;

&lt;p&gt;You can filter the audits to just the Core Web Vitals you are interested in to focus on fixes for issues related to a specific metric:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/vitals-tools/image/lighthouse-filter.png&quot; alt=&quot;Lighthouse filter options for key metrics&quot; width=&quot;700&quot; height=&quot;100&quot; srcset=&quot;https://web.dev/static/articles/vitals-tools/image/lighthouse-filter_36.png 36w,https://web.dev/static/articles/vitals-tools/image/lighthouse-filter_48.png 48w,https://web.dev/static/articles/vitals-tools/image/lighthouse-filter_72.png 72w,https://web.dev/static/articles/vitals-tools/image/lighthouse-filter_96.png 96w,https://web.dev/static/articles/vitals-tools/image/lighthouse-filter_480.png 480w,https://web.dev/static/articles/vitals-tools/image/lighthouse-filter_720.png 720w,https://web.dev/static/articles/vitals-tools/image/lighthouse-filter_856.png 856w,https://web.dev/static/articles/vitals-tools/image/lighthouse-filter_960.png 960w,https://web.dev/static/articles/vitals-tools/image/lighthouse-filter_1440.png 1440w,https://web.dev/static/articles/vitals-tools/image/lighthouse-filter_1920.png 1920w,https://web.dev/static/articles/vitals-tools/image/lighthouse-filter_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption&gt;Lighthouse filter options&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;For INP, use the TBT audits to identify issues that can potentially affect those metrics, but be aware that without interactions, Lighthouse is limited in how much it can diagnose.&lt;/p&gt;

&lt;h4 id=&quot;analyze-real-time&quot; data-text=&quot;Analyze in real time with the Chrome DevTools live metrics screen&quot; tabindex=&quot;-1&quot;&gt;Analyze in real time with the Chrome DevTools live metrics screen&lt;/h4&gt;

&lt;p&gt;The Chrome DevTools live metrics screen in the Performance panel shows Core Web Vitals in real time during page load &lt;em&gt;and&lt;/em&gt; while browsing a page. Because of this, it can capture INP as well as layout shifts that occur after load. It is also possible to see more detailed information on each metric:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/vitals-tools/image/performance-panel-live-metrics.png&quot; alt=&quot;Live metrics view in Chrome DevTools Performance panel&quot; width=&quot;800&quot; height=&quot;528&quot; srcset=&quot;https://web.dev/static/articles/vitals-tools/image/performance-panel-live-metrics_36.png 36w,https://web.dev/static/articles/vitals-tools/image/performance-panel-live-metrics_48.png 48w,https://web.dev/static/articles/vitals-tools/image/performance-panel-live-metrics_72.png 72w,https://web.dev/static/articles/vitals-tools/image/performance-panel-live-metrics_96.png 96w,https://web.dev/static/articles/vitals-tools/image/performance-panel-live-metrics_480.png 480w,https://web.dev/static/articles/vitals-tools/image/performance-panel-live-metrics_720.png 720w,https://web.dev/static/articles/vitals-tools/image/performance-panel-live-metrics_856.png 856w,https://web.dev/static/articles/vitals-tools/image/performance-panel-live-metrics_960.png 960w,https://web.dev/static/articles/vitals-tools/image/performance-panel-live-metrics_1440.png 1440w,https://web.dev/static/articles/vitals-tools/image/performance-panel-live-metrics_1920.png 1920w,https://web.dev/static/articles/vitals-tools/image/performance-panel-live-metrics_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption&gt;Live metrics view in Chrome DevTools Performance panel&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;This view gives a lot of helpful information to help identify performance issues, and can even pull in field information from CrUX. For even more information you can drill down with a trace.&lt;/p&gt;

&lt;h4 id=&quot;drilldown-with-perf-panel&quot; data-text=&quot;Drill down with the Performance panel&quot; tabindex=&quot;-1&quot;&gt;Drill down with the Performance panel&lt;/h4&gt;

&lt;p&gt;The Performance panel in Chrome DevTools lets you record a profile (or trace) of all page behavior during a recorded period of time.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/vitals-tools/image/devtools-perf-trace.png&quot; alt=&quot;Chrome DevTools Performance Panel trace showing flame chart with a long task highlighted&quot; width=&quot;800&quot; height=&quot;450&quot; srcset=&quot;https://web.dev/static/articles/vitals-tools/image/devtools-perf-trace_36.png 36w,https://web.dev/static/articles/vitals-tools/image/devtools-perf-trace_48.png 48w,https://web.dev/static/articles/vitals-tools/image/devtools-perf-trace_72.png 72w,https://web.dev/static/articles/vitals-tools/image/devtools-perf-trace_96.png 96w,https://web.dev/static/articles/vitals-tools/image/devtools-perf-trace_480.png 480w,https://web.dev/static/articles/vitals-tools/image/devtools-perf-trace_720.png 720w,https://web.dev/static/articles/vitals-tools/image/devtools-perf-trace_856.png 856w,https://web.dev/static/articles/vitals-tools/image/devtools-perf-trace_960.png 960w,https://web.dev/static/articles/vitals-tools/image/devtools-perf-trace_1440.png 1440w,https://web.dev/static/articles/vitals-tools/image/devtools-perf-trace_1920.png 1920w,https://web.dev/static/articles/vitals-tools/image/devtools-perf-trace_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption&gt;Chrome DevTools Performance Panel trace&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Performance insights are available in the &lt;strong&gt;Insights&lt;/strong&gt; side panel. This also shows the Core Web Vital metrics along with the field values for those when available.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Layout Shifts&lt;/strong&gt; track highlights layout shifts and clicking on these provides more details about the elements that shifted for debugging CLS.&lt;/p&gt;

&lt;p&gt;Key timings—such as LCP—are shown in the &lt;strong&gt;Timings&lt;/strong&gt; available at the bottom of the trace. Click these for more details.&lt;/p&gt;

&lt;p&gt;Long Tasks (that can lead to INP issues) are also highlighted with red triangles in the flame chart.&lt;/p&gt;

&lt;p&gt;These features—as well as information in other parts of the &lt;a href=&quot;https://developer.chrome.com/docs/devtools/performance/overview&quot;&gt;Performance panel&lt;/a&gt;—can help you determine whether fixes are having any effect on a page&#39;s Core Web Vitals.&lt;/p&gt;

&lt;h4 id=&quot;debug-in-field&quot; data-text=&quot;Debug Core Web Vitals in the field&quot; tabindex=&quot;-1&quot;&gt;Debug Core Web Vitals in the field&lt;/h4&gt;

&lt;p&gt;Lab tools can&#39;t always identify the cause of all Core Web Vitals issues affecting your users. This is one reason why it&#39;s so important to collect your own field data, as it takes factors into account that lab data cannot.&lt;/p&gt;

&lt;p&gt;See &lt;a href=&quot;https://web.dev/articles/debug-performance-in-the-field&quot;&gt;debug performance in the field&lt;/a&gt; for more information.&lt;/p&gt;

&lt;h3 id=&quot;monitor-changes&quot; data-text=&quot;Step 3: Monitor for changes&quot; tabindex=&quot;-1&quot;&gt;Step 3: Monitor for changes&lt;/h3&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/vitals-tools/image/a-collection-icons-goog-f791c36c6f471.png&quot; alt=&quot;A collection of icons for Google tools. From left to right, the icons represent &#39;CrUX on BigQuery&#39;, &#39;CrUX API&#39;, &#39;PSI API&#39;, &#39;web-vitals.js&#39;, with &#39;Lighthouse CI&#39; at the far right.&quot; width=&quot;800&quot; height=&quot;185&quot; srcset=&quot;https://web.dev/static/articles/vitals-tools/image/a-collection-icons-goog-f791c36c6f471_36.png 36w,https://web.dev/static/articles/vitals-tools/image/a-collection-icons-goog-f791c36c6f471_48.png 48w,https://web.dev/static/articles/vitals-tools/image/a-collection-icons-goog-f791c36c6f471_72.png 72w,https://web.dev/static/articles/vitals-tools/image/a-collection-icons-goog-f791c36c6f471_96.png 96w,https://web.dev/static/articles/vitals-tools/image/a-collection-icons-goog-f791c36c6f471_480.png 480w,https://web.dev/static/articles/vitals-tools/image/a-collection-icons-goog-f791c36c6f471_720.png 720w,https://web.dev/static/articles/vitals-tools/image/a-collection-icons-goog-f791c36c6f471_856.png 856w,https://web.dev/static/articles/vitals-tools/image/a-collection-icons-goog-f791c36c6f471_960.png 960w,https://web.dev/static/articles/vitals-tools/image/a-collection-icons-goog-f791c36c6f471_1440.png 1440w,https://web.dev/static/articles/vitals-tools/image/a-collection-icons-goog-f791c36c6f471_1920.png 1920w,https://web.dev/static/articles/vitals-tools/image/a-collection-icons-goog-f791c36c6f471_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption&gt;Google&#39;s tools for monitoring changes&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Once you have fixed any issues, you want to ensure they have the required effect and that new issues don&#39;t disrupt your Core Web Vitals. This requires monitoring for performance issues as part of developer workflow to prevent performance issues being released to production, and monitoring the field data regularly to ensure this is the case.&lt;/p&gt;

&lt;aside class=&quot;note&quot;&gt;
&lt;a href=&quot;https://youtu.be/YJGCZCaIZkQ?t=112&quot;&gt;Research by Google&lt;/a&gt; has shown that most performance improvements tend to regress within six months. A website needs continuous monitoring in both the lab &lt;em&gt;and&lt;/em&gt; the field to identify worsening trends in Core Web Vitals and other performance metrics to avoid regressions.
&lt;/aside&gt;

&lt;h4 id=&quot;monitor-regressions&quot; data-text=&quot;Monitor for performance requisitions in Continuous Integration (CI) environments&quot; tabindex=&quot;-1&quot;&gt;Monitor for performance requisitions in Continuous Integration (CI) environments&lt;/h4&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/GoogleChrome/lighthouse-ci&quot;&gt;Lighthouse-CI&lt;/a&gt; lets you automatically run Lighthouse audits on code commits to prevent performance regressions entering code. This can check performance timings (which &lt;a href=&quot;https://developers.google.com/web/tools/lighthouse/variability&quot;&gt;are subject to variability&lt;/a&gt;), or for just &lt;a href=&quot;https://web.dev/lighthouse/performance&quot;&gt;the performance audits&lt;/a&gt;, as a linting tool to prevent bad practices in the code.&lt;/p&gt;

&lt;h4 id=&quot;trends&quot; data-text=&quot;View website health trends with field data&quot; tabindex=&quot;-1&quot;&gt;View website health trends with field data&lt;/h4&gt;

&lt;p&gt;While you should aim to catch and fix all performance issues before they make it to production, monitoring your field data using RUM is essential to find any that slip through. There are many commercial RUM products available that can help with this. The &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;web-vitals&lt;/code&gt;&lt;/a&gt; JavaScript library can automate a website&#39;s field data collection, and optionally use this data to power custom dashboards and alerting systems.&lt;/p&gt;

&lt;p&gt;For sites without a RUM solution, you can use the &lt;a href=&quot;https://developer.chrome.com/docs/crux/methodology/tools&quot;&gt;various CrUX tools&lt;/a&gt; as a basic trend analysis of field data.&lt;/p&gt;

&lt;aside class=&quot;note&quot;&gt;
Even if your website is represented in CrUX, you should still collect your own field data, since CrUX doesn&#39;t include &lt;em&gt;all&lt;/em&gt; users of Chrome or even other browsers—but it&#39;s certainly a good place to start in the absence of any field data.
&lt;/aside&gt;

&lt;h2 id=&quot;conclusion&quot; data-text=&quot;Conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Ensuring fast and delightful user experiences requires a performance-first mindset and adoption of a workflow to ensure progress. With the right tools and processes to audit, debug, and monitor, building great user experiences and staying within the thresholds defined for good Core Web Vitals is within your reach.&lt;/p&gt;

  

  
</description><link>https://web.dev/articles/vitals-tools?hl=en</link><guid isPermaLink="false">https://web.dev/articles/vitals-tools?hl=en</guid><pubDate>Fri, 28 Feb 2025 08:00:00 GMT</pubDate></item><item><title>ruby-align is Baseline Newly available</title><description>&lt;p&gt;&lt;style&gt;
  .wd-author__links {
    display: flex;
  }
  &lt;/style&gt;

&lt;/p&gt;

&lt;p class=&quot;wd-pubdates&quot; style=&quot;font-size: 90%; border-bottom: 1px solid var(--devsite-ref-palette--grey200);color: var(--devsite-ref-palette--blue-grey700);&quot;&gt;
  Published: Feb 21, 2025
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;

&lt;aside class=&quot;success&quot;&gt;
 &lt;b&gt;Celebration:&lt;/b&gt; This web feature is now available in all three major browser engines, and becomes &lt;strong&gt;Baseline Newly available&lt;/strong&gt; as of December 11, 2024.
&lt;/aside&gt;

&lt;p&gt;The HTML &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTML/Element/ruby&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;ruby&amp;gt;&lt;/code&gt;&lt;/a&gt; element lets you display phonetic annotations or other supplemental information above or beside base text. It is commonly used in East Asian languages.&lt;/p&gt;

&lt;p&gt;A &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;ruby&amp;gt;&lt;/code&gt; element consists of two main parts, ruby base which is the main text and ruby text which is the annotation text, marked up with the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;rt&amp;gt;&lt;/code&gt; element.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;HTML&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;
&amp;lt;ruby&amp;gt;
  絵文字
  &amp;lt;rt&amp;gt;emoji&amp;lt;/rt&amp;gt;
&amp;lt;/ruby&amp;gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/blog/baseline-ruby-align/image/ruby.png&quot; alt=&quot;English ruby annotation &#39;Emoji&#39; over a Japanese text.&quot; srcset=&quot;https://web.dev/static/blog/baseline-ruby-align/image/ruby_36.png 36w,https://web.dev/static/blog/baseline-ruby-align/image/ruby_48.png 48w,https://web.dev/static/blog/baseline-ruby-align/image/ruby_72.png 72w,https://web.dev/static/blog/baseline-ruby-align/image/ruby_96.png 96w,https://web.dev/static/blog/baseline-ruby-align/image/ruby_480.png 480w,https://web.dev/static/blog/baseline-ruby-align/image/ruby_720.png 720w,https://web.dev/static/blog/baseline-ruby-align/image/ruby_856.png 856w,https://web.dev/static/blog/baseline-ruby-align/image/ruby_960.png 960w,https://web.dev/static/blog/baseline-ruby-align/image/ruby_1440.png 1440w,https://web.dev/static/blog/baseline-ruby-align/image/ruby_1920.png 1920w,https://web.dev/static/blog/baseline-ruby-align/image/ruby_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption&gt;English pronunciation as an annotation over Japanese base text.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;ruby-align&quot; data-text=&quot;ruby-align&quot; tabindex=&quot;-1&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;ruby-align&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;&lt;devsite-iframe&gt;&lt;iframe src=&quot;https://web.dev/frame/blog/baseline-ruby-align/index_3b0233129cdb4e021ce1d9aa7e2377ffde82e6ca1955f2e0059e5646418d7af6.frame&quot; class=&quot;framebox inherit-locale &quot; allow=&quot;clipboard-write https://web-dot-devsite-v2-prod-3p.appspot.com&quot; allowfullscreen=&quot;&quot; is-upgraded=&quot;&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/iframe&gt;&lt;/devsite-iframe&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/ruby-align&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;ruby-align&lt;/code&gt;&lt;/a&gt; CSS property controls the alignment of ruby base text and ruby annotation text. The property accepts one of the keyword values, &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;space-around&lt;/code&gt;, &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;space-between&lt;/code&gt;, &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;start&lt;/code&gt;, and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;center&lt;/code&gt;.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/blog/baseline-ruby-align/image/ruby-align.png&quot; alt=&quot;Pīnyīn ruby annotation over a Chinese text.&quot; srcset=&quot;https://web.dev/static/blog/baseline-ruby-align/image/ruby-align_36.png 36w,https://web.dev/static/blog/baseline-ruby-align/image/ruby-align_48.png 48w,https://web.dev/static/blog/baseline-ruby-align/image/ruby-align_72.png 72w,https://web.dev/static/blog/baseline-ruby-align/image/ruby-align_96.png 96w,https://web.dev/static/blog/baseline-ruby-align/image/ruby-align_480.png 480w,https://web.dev/static/blog/baseline-ruby-align/image/ruby-align_720.png 720w,https://web.dev/static/blog/baseline-ruby-align/image/ruby-align_856.png 856w,https://web.dev/static/blog/baseline-ruby-align/image/ruby-align_960.png 960w,https://web.dev/static/blog/baseline-ruby-align/image/ruby-align_1440.png 1440w,https://web.dev/static/blog/baseline-ruby-align/image/ruby-align_1920.png 1920w,https://web.dev/static/blog/baseline-ruby-align/image/ruby-align_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption&gt;Image showing use-case for ruby-align property.&lt;/figcaption&gt;
&lt;/figure&gt;
  

  
</description><link>https://web.dev/blog/baseline-ruby-align?hl=en</link><guid isPermaLink="false">https://web.dev/blog/baseline-ruby-align?hl=en</guid><pubDate>Fri, 21 Feb 2025 08:00:00 GMT</pubDate></item><item><title>Prefetch resources to speed up future navigations</title><description>&lt;p&gt;Learn about rel=prefetch resource hint and how to use it.&lt;/p&gt;

&lt;p&gt;&lt;style&gt;
  .wd-author__links {
    display: flex;
  }
  &lt;/style&gt;

&lt;/p&gt;&lt;p&gt;&lt;/p&gt;

&lt;p&gt;
&lt;/p&gt;&lt;p class=&quot;wd-pubdates&quot; style=&quot;font-size: 90%; border-bottom: 1px solid var(--devsite-ref-palette--grey200);color: var(--devsite-ref-palette--blue-grey700);&quot;&gt;
  Published: Sep 12, 2019, Last updated: Feb 8, 2025
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Research shows that &lt;a href=&quot;https://wpostats.com/&quot;&gt;faster load times result in higher conversion rates&lt;/a&gt; and better user experiences. If you have insight into how users move through your website and which pages they will likely visit next, you can improve load times of future navigations by downloading the resources for those pages ahead of time.&lt;/p&gt;

&lt;p&gt;This guide explains how to achieve that with &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;link rel=prefetch&amp;gt;&lt;/code&gt; that lets you implement prefetching in an efficient way.&lt;/p&gt;

&lt;h2 id=&quot;improve_site_performance_with_relprefetch&quot; data-text=&quot;Improve site performance with rel=prefetch&quot; tabindex=&quot;-1&quot;&gt;Improve site performance with &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;rel=prefetch&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;Adding &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;link rel=prefetch&amp;gt;&lt;/code&gt; to a web page tells the browser to download some of the resources (like scripts or CSS files), that the user might need in the future:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;HTML&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;link rel=&quot;prefetch&quot; href=&quot;/css/styles.css&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;prefetch&lt;/code&gt; hint consumes extra bytes for resources that are not immediately needed, so this technique needs to be applied thoughtfully; only prefetch resources when you are confident that users will need them. Consider not prefetching when users are on slow connections. You can detect that with the &lt;a href=&quot;https://web.dev/articles/adaptive-serving-based-on-network-quality&quot;&gt;Network Information API&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;use_cases&quot; data-text=&quot;Use cases&quot; tabindex=&quot;-1&quot;&gt;Use cases&lt;/h2&gt;

&lt;p&gt;Prefetch has many use cases to speed up web pages by downloading resources in advance.&lt;/p&gt;

&lt;h3 id=&quot;prefetch_subsequent_pages&quot; data-text=&quot;Prefetch subsequent pages&quot; tabindex=&quot;-1&quot;&gt;Prefetch subsequent pages&lt;/h3&gt;

&lt;p&gt;Prefetch HTML documents when subsequent pages are predictable, so that when a link is clicked, the page is loaded instantly.&lt;/p&gt;

&lt;aside class=&quot;caution&quot;&gt;&lt;b&gt;Caution:&lt;/b&gt; The &lt;a href=&quot;https://developer.chrome.com/docs/web-platform/prerender-pages&quot;&gt;Speculation Rules API&lt;/a&gt; provides several advantages over prefetch for prefetching navigations for browsers that support this API including &lt;a href=&quot;https://developer.chrome.com/docs/web-platform/prerender-pages#no-cache&quot;&gt;handling non-cacheable navigations&lt;/a&gt; and consistent cross-origin prefetch handling.&lt;/aside&gt;

&lt;p&gt;For example, in a product listing page, you can prefetch the page for the most popular product in the list. In some cases, the next navigation is even easier to anticipate—on a shopping cart page, the likelihood of a user visiting the checkout page is usually high which makes it a good candidate for prefetching.&lt;/p&gt;

&lt;aside class=&quot;note&quot;&gt;&lt;b&gt;Note:&lt;/b&gt; eBay implemented prefetching for the first five results on a search page to speed up future pages loads and saw a positive impact on conversion rates. &lt;/aside&gt;

&lt;p&gt;While prefetching resources does use additional bandwidth, it can improve most performance metrics. &lt;a href=&quot;https://web.dev/articles/ttfb&quot;&gt;Time to First Byte (TTFB)&lt;/a&gt; will often be much lower, as the document request results in a cache hit. Because TTFB will be lower, subsequent time-based metrics will often be lower as well, including &lt;a href=&quot;https://web.dev/articles/lcp&quot;&gt;Largest Contentful Paint (LCP)&lt;/a&gt; and &lt;a href=&quot;https://web.dev/articles/fcp&quot;&gt;First Contentful Paint (FCP)&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;prefetch_static_assets&quot; data-text=&quot;Prefetch static assets&quot; tabindex=&quot;-1&quot;&gt;Prefetch static assets&lt;/h3&gt;

&lt;p&gt;Prefetch static assets, like scripts or stylesheets, when subsequent sections the user might visit can be predicted. This is especially useful when those assets are shared across many pages.&lt;/p&gt;

&lt;p&gt;For example, Netflix takes advantage of the time users spend on logged-out pages, to prefetch React, which will be used once users sign in. Thanks to this, they &lt;a href=&quot;https://medium.com/dev-channel/a-netflix-web-performance-case-study-c0bcde26a9d9&quot;&gt;reduced Time to Interactive by 30% for future navigations&lt;/a&gt;.&lt;/p&gt;

&lt;aside class=&quot;tip&quot;&gt; At the time of this writing, it is possible to share prefetched resources among pages served from different origins. When &lt;a href=&quot;https://groups.google.com/a/chromium.org/forum/#!msg/blink-dev/6KKXv1PqPZ0/oguPntMGDgAJ&quot;&gt;Double-keyed HTTP cache&lt;/a&gt; ships, this will only work for top-level navigations and same-origin subresources, but it won&#39;t be possible to reuse prefetched subresources among different origins. This means that, if &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;a.com&lt;/code&gt; prefetches the resource &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;b.com/library.js&lt;/code&gt;, it won&#39;t be available in &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;c.com&lt;/code&gt; cache. Some browsers, such as WebKit-based ones, already &lt;a href=&quot;https://webkit.org/blog/7675/intelligent-tracking-prevention/&quot;&gt;partition caches and HTML5 storage&lt;/a&gt; for all third-party domains. &lt;/aside&gt;

&lt;p&gt;The effect of prefetching static assets on performance metrics depends on the resource being prefetched:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prefetching images can significantly lower LCP times for LCP image elements.&lt;/li&gt;
&lt;li&gt;Prefetching stylesheets can improve both FCP and LCP, as the network time to download the style sheet will be eliminated. Since stylesheets are render blocking, they can reduce LCP when prefetched. In cases where the subsequent page&#39;s LCP element is a CSS background image requested using the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;background-image&lt;/code&gt; property, the image will also be prefetched as a dependent resource of the prefetched style sheet.&lt;/li&gt;
&lt;li&gt;Prefetching JavaScript will allow the processing of the prefetched script to occur much sooner than if it were required to be fetched by the network first during navigation. This can have an effect on a page&#39;s &lt;a href=&quot;https://web.dev/articles/inp&quot;&gt;Interaction to Next Paint (INP)&lt;/a&gt;. In cases where markup is rendered on the client using JavaScript, LCP can be improved through reduced resource load delays, and client-side rendering of markup containing a page&#39;s LCP element can occur sooner.&lt;/li&gt;
&lt;li&gt;Prefetching web fonts that are not already used by the current page, can eliminate layout shifts. In cases where &lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/font-display&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;font-display: swap;&lt;/code&gt; is used&lt;/a&gt;, the swap period for the font is eliminated, resulting in faster rendering of the text and eliminating layout shifts. If a future page uses the prefetched font and the page&#39;s LCP element is a block of text using a web font, LCP for that element will also be faster.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;prefetch_on-demand_javascript_chunks&quot; data-text=&quot;Prefetch on-demand JavaScript chunks&quot; tabindex=&quot;-1&quot;&gt;Prefetch on-demand JavaScript chunks&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://web.dev/articles/reduce-javascript-payloads-with-code-splitting&quot;&gt;Code-splitting&lt;/a&gt; your JavaScript bundles lets you initially load only parts of an app and lazy-load the rest. If you&#39;re using this technique, you can apply prefetch to routes or components that are not immediately necessary but will likely be requested soon.&lt;/p&gt;

&lt;p&gt;For example, if you have a page that contains a button that opens a dialog which contains an emoji picker, you can divide it into three JavaScript chunks—home, dialog and picker. Home and dialog could be initially loaded, while the picker could be loaded on-demand. Tools like webpack allow you to instruct the browser to prefetch these on-demand chunks.&lt;/p&gt;

&lt;h2 id=&quot;how_to_implement_relprefetch&quot; data-text=&quot;How to implement rel=prefetch&quot; tabindex=&quot;-1&quot;&gt;How to implement &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;rel=prefetch&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;The simplest way to implement &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;prefetch&lt;/code&gt; is adding a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;link&amp;gt;&lt;/code&gt; tag to the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;head&amp;gt;&lt;/code&gt; of the document:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;HTML&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;head&amp;gt;
  ...
  &amp;lt;link rel=&quot;prefetch&quot; href=&quot;//css/styles.css&quot;&amp;gt;
  ...
&amp;lt;/head&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;You can also initiate prefetching with the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Headers/Link&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;Link&lt;/code&gt; HTTP header&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;Link: &amp;lt;/css/style.css&amp;gt;; rel=prefetch&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;A benefit of specifying a prefetch hint in the HTTP Header is that the browser doesn&#39;t need to parse the document to find the resource hint, which can offer small improvements in some cases.&lt;/p&gt;

&lt;aside class=&quot;note&quot;&gt;&lt;b&gt;Note: &lt;/b&gt; &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;prefetch&lt;/code&gt; is supported in &lt;a href=&quot;https://caniuse.com/#search=prefetch&quot;&gt;all modern browsers except Safari&lt;/a&gt;. You can implement a fallback technique for Safari with &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/XMLHttpRequest&quot;&gt;XHR&lt;/a&gt; requests or the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Fetch_API&quot;&gt;Fetch API&lt;/a&gt;. &lt;/aside&gt;

&lt;h3 id=&quot;prefetch_javascript_modules_with_webpack_magic_comments&quot; data-text=&quot;Prefetch JavaScript modules with webpack magic comments&quot; tabindex=&quot;-1&quot;&gt;Prefetch JavaScript modules with webpack magic comments&lt;/h3&gt;

&lt;p&gt;webpack lets you prefetch scripts for routes or functions you&#39;re reasonably certain users will visit or use soon.&lt;/p&gt;

&lt;p&gt;The following code snippet lazy-loads a sorting function from the &lt;a href=&quot;https://lodash.com/&quot;&gt;lodash&lt;/a&gt; library to sort a group of numbers that will be submitted by a form:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JavaScript&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;form&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;submit&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&amp;gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;preventDefault&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;lodash.sortby&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&amp;gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;sortInput&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;())&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&amp;gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;alert&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;Instead of waiting for the &#39;submit&#39; event to take place to load this function, you can prefetch this resource to increase the chances of having it available in the cache by the time the user submits the form. webpack allows that using the &lt;a href=&quot;https://webpack.js.org/api/module-methods/#magic-comments&quot;&gt;magic comments&lt;/a&gt; inside &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;import()&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;CSS+Lasso&quot; syntax-guessed=&quot;&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;form&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nc&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;submit&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&amp;gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;   &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;e.preventDefault()&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;   &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;import(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-c&quot;&gt;/* webpackPrefetch: true */&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;&#39;lodash.sortby&#39;)&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;         &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;.then(module&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;=&lt;/span&gt;&amp;gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;module.default)&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;         &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;.then(sortInput())&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;         &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;.catch(err&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;=&lt;/span&gt;&amp;gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;alert(err)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-err&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;This tells webpack to inject the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;link rel=&quot;prefetch&quot;&amp;gt;&lt;/code&gt; tag into the HTML document:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;HTML&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;link rel=&quot;prefetch&quot; href=&quot;1.bundle.js&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;The performance benefits of prefetching on-demand chunks is a bit nuanced, but generally speaking, you could expect to see faster responses to interactions which depend on those on-demand chunks, as they will be immediately available. Depending on the nature of the interaction, this could impart a benefit to a page&#39;s INP.&lt;/p&gt;

&lt;p&gt;Prefetching in general also factors into overall &lt;a href=&quot;https://web.dev/articles/prioritize-resources&quot;&gt;resource prioritization&lt;/a&gt;. When a resource is prefetched, it is done so at the lowest possible priority. Thus, any prefetched resources won&#39;t contend for bandwidth for resources needed by the current page.&lt;/p&gt;

&lt;h3 id=&quot;smart_prefetching_with_quicklink_and_guessjs&quot; data-text=&quot;Smart prefetching with quicklink and Guess.js&quot; tabindex=&quot;-1&quot;&gt;Smart prefetching with quicklink and Guess.js&lt;/h3&gt;

&lt;p&gt;You can also implement smarter prefetching with libraries that use &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;prefetch&lt;/code&gt; under the hood:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/GoogleChromeLabs/quicklink&quot;&gt;quicklink&lt;/a&gt; uses &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Intersection_Observer_API&quot;&gt;Intersection Observer API&lt;/a&gt; to detect when links come into the viewport and prefetches linked resources during &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Window/requestIdleCallback&quot;&gt;idle time&lt;/a&gt;. Bonus: quicklink weighs less than 1 KB!&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/guess-js&quot;&gt;Guess.js&lt;/a&gt; uses analytics reports to build a predictive model that is used to &lt;a href=&quot;https://web.dev/articles/predictive-prefetching&quot;&gt;smartly prefetch&lt;/a&gt; only what the user is likely to need.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both quicklink and Guess.js use the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Network_Information_API&quot;&gt;Network Information API&lt;/a&gt; to avoid prefetching if a user is on a slow network or has &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Headers/Save-Data&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;Save-Data&lt;/code&gt;&lt;/a&gt; turned on.&lt;/p&gt;

&lt;h2 id=&quot;prefetch_under_the_hood&quot; data-text=&quot;Prefetch under the hood&quot; tabindex=&quot;-1&quot;&gt;Prefetch under the hood&lt;/h2&gt;

&lt;p&gt;Resource hints are not mandatory instructions and it&#39;s up to the browser to decide if, and when, they get executed.&lt;/p&gt;

&lt;p&gt;You can use prefetch multiple times in the same page. The browser queues up all hints and requests each resource when it&#39;s &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Link_prefetching_FAQ#How_is_browser_idle_time_determined.3F&quot;&gt;idle&lt;/a&gt;. In Chrome, if a prefetch has not finished loading and the user navigates to the destined prefetch resource, the in-flight load is picked up as the navigation by the browser (other browser vendors might implement this differently).&lt;/p&gt;

&lt;p&gt;Prefetching takes place at the &lt;a href=&quot;https://web.dev/articles/fetch-priority#resource-priority&quot;&gt;&#39;Lowest&#39; priority&lt;/a&gt;, so prefetched resources don&#39;t compete for bandwidth with the resources required in the current page.&lt;/p&gt;

&lt;p&gt;Prefetched files are stored in the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Caching&quot;&gt;HTTP Cache&lt;/a&gt; if the resource is cacheable, otherwise it will be discarded and not be used.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot; data-text=&quot;Conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Using &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;prefetch&lt;/code&gt; can greatly improve web performance of a site by downloading future needed resources in advance. &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;prefetch&lt;/code&gt; is supported in most modern browsers. This technique requires loading extra bytes that might not be used, so be mindful when you use it; only do it when necessary, and ideally, only on fast networks.&lt;/p&gt;

  

  
</description><link>https://web.dev/articles/link-prefetch?hl=en</link><guid isPermaLink="false">https://web.dev/articles/link-prefetch?hl=en</guid><pubDate>Sat, 08 Feb 2025 08:00:00 GMT</pubDate></item><item><title>Debug layout shifts</title><description>&lt;p&gt;Learn how to identify and fix layout shifts.&lt;/p&gt;

&lt;p&gt;&lt;style&gt;
  .wd-author__links {
    display: flex;
  }
  &lt;/style&gt;

&lt;/p&gt;&lt;p&gt;&lt;/p&gt;

&lt;p&gt;
&lt;/p&gt;&lt;p class=&quot;wd-pubdates&quot; style=&quot;font-size: 90%; border-bottom: 1px solid var(--devsite-ref-palette--grey200);color: var(--devsite-ref-palette--blue-grey700);&quot;&gt;
  Published: Mar 11, 2021, Last updated: Feb 7, 2025
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;The first part of this article discusses tooling for debugging layout shifts,
while the second part discusses the thought process to use when
identifying the cause of a layout shift.&lt;/p&gt;

&lt;h2 id=&quot;tooling&quot; data-text=&quot;Tooling&quot; tabindex=&quot;-1&quot;&gt;Tooling&lt;/h2&gt;

&lt;p&gt;You can debug Layout Shifts using the Layout Instability API, or with tooling like DevTools that summarizes data from this API in a more easily digestible format.&lt;/p&gt;

&lt;h3 id=&quot;layout_instability_api&quot; data-text=&quot;Layout Instability API&quot; tabindex=&quot;-1&quot;&gt;Layout Instability API&lt;/h3&gt;

&lt;p&gt;The &lt;a href=&quot;https://wicg.github.io/layout-instability/&quot;&gt;Layout Instability API&lt;/a&gt; is the
browser mechanism for measuring and reporting layout shifts. All tools for
debugging layout shifts, including DevTools, are ultimately built upon the
Layout Instability API. However, using the Layout Instability API directly is a
powerful debugging tool due to its flexibility.&lt;/p&gt;

&lt;aside class=&quot;note&quot;&gt;&lt;b&gt;Note: &lt;/b&gt; The Layout Instability API is only &lt;a href=&quot;https://caniuse.com/mdn-api_layoutshift&quot;&gt;supported&lt;/a&gt; by Chromium browsers. At the current time there is no way to measure or debug layout shifts in non-Chromium browsers. &lt;/aside&gt;

&lt;h4 id=&quot;usage&quot; data-text=&quot;Usage&quot; tabindex=&quot;-1&quot;&gt;Usage&lt;/h4&gt;

&lt;p&gt;The same code &lt;a href=&quot;https://web.dev/articles/cls#measure_cls_in_javascript&quot;&gt;snippet&lt;/a&gt; that
measures &lt;a href=&quot;https://web.dev/articles/cls&quot;&gt;Cumulative Layout Shift (CLS)&lt;/a&gt; can also
serve to debug layout shifts. The following snippet logs information about layout
shifts to the console. Inspecting this log will provide you information
about when, where, and how a layout shift occurred.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JavaScript&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-kd&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;cls&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-ow&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;PerformanceObserver&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;entryList&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;=&lt;/span&gt;&amp;gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-kd&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;entryList&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;getEntries&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;())&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;hadRecentInput&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;cls&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;+=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;Current CLS value:&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;cls&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}).&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s1&quot;&gt;&#39;layout-shift&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;buffered&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;When running this script be aware that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;buffered: true&lt;/code&gt; option indicates that the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/PerformanceObserver&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;PerformanceObserver&lt;/code&gt;&lt;/a&gt;
should check the browser&#39;s &lt;a href=&quot;https://www.w3.org/TR/performance-timeline-2/#dfn-performance-entry-buffer&quot;&gt;performance entry
buffer&lt;/a&gt;
for performance entries that were created before the observer&#39;s
initialization. As a result, the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;PerformanceObserver&lt;/code&gt; will report layout
shifts that happened both before and after it was initialized. Keep this in
mind when inspecting the console logs. An initial glut of layout shifts can
reflect a reporting backlog, rather than the sudden occurrence of numerous
layout shifts.&lt;/li&gt;
&lt;li&gt;To avoid impacting performance, the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;PerformanceObserver&lt;/code&gt; waits until the main
thread is idle to report on layout shifts. As a result, depending on how
busy the main thread is, there may be a slight delay between when a layout
shift occurs and when it is logged in the console.&lt;/li&gt;
&lt;li&gt;This script ignores layout shifts that occurred within 500&amp;nbsp;ms of user input
and therefore don&#39;t count towards CLS.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Information about layout shifts is reported using a combination of two APIs: the
&lt;a href=&quot;https://wicg.github.io/layout-instability/#layoutshift&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;LayoutShift&lt;/code&gt;&lt;/a&gt; and
&lt;a href=&quot;https://wicg.github.io/layout-instability/#sec-layout-shift-attribution&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;LayoutShiftAttribution&lt;/code&gt;&lt;/a&gt;
interfaces. Each of these interfaces are explained in more detail in the
following sections.&lt;/p&gt;

&lt;h4 id=&quot;layoutshift&quot; data-text=&quot;LayoutShift&quot; tabindex=&quot;-1&quot;&gt;LayoutShift&lt;/h4&gt;

&lt;p&gt;Each layout shift is reported using the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;LayoutShift&lt;/code&gt; interface. The contents of
an entry look like this:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JavaScript&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;duration&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;0&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-nx&quot;&gt;entryType&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;layout-shift&quot;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-nx&quot;&gt;hadRecentInput&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-nx&quot;&gt;lastInputTime&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;0&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;&quot;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-nx&quot;&gt;sources&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;LayoutShiftAttribution&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;LayoutShiftAttribution&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;LayoutShiftAttribution&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-nx&quot;&gt;startTime&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;11317.934999999125&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mf&quot;&gt;0.17508567530168798&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;The previous entry indicates a layout shift during which three DOM elements changed
position. The layout shift score of this particular layout shift was &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;0.175&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;These are the properties of a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;LayoutShift&lt;/code&gt; instance that are most relevant to
debugging layout shifts:&lt;/p&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;sources&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;sources&lt;/code&gt; property lists the DOM elements that moved during the layout shift. This array can contain up to five sources. In the event that there are more than five elements impacted by the layout shift, the five largest (as measured by impact on layout stability) sources of layout shift are reported. This information is reported using the LayoutShiftAttribution interface (explained in more detail below).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;value&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;value&lt;/code&gt; property reports the &lt;a href=&quot;https://web.dev/articles/cls#layout_shift_score&quot;&gt;layout shift score&lt;/a&gt; for a particular layout shift.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;hadRecentInput&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;hadRecentInput&lt;/code&gt; property indicates whether a layout shift occurred within 500 milliseconds of user input.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;startTime&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;startTime&lt;/code&gt; property indicates when a layout shift occurred. &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;startTime&lt;/code&gt; is indicated in milliseconds and is measured relative to the &lt;a href=&quot;https://www.w3.org/TR/hr-time-2/#sec-time-origin&quot;&gt;time that the page load was initiated&lt;/a&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;duration&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;duration&lt;/code&gt; property will always be set to &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;0&lt;/code&gt;. This property is inherited from the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/PerformanceEntry&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;PerformanceEntry&lt;/code&gt;&lt;/a&gt; interface (the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;LayoutShift&lt;/code&gt; interface extends the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;PerformanceEntry&lt;/code&gt; interface). However, the concept of duration does not apply to layout shift events, so it is set to &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;0&lt;/code&gt;. For information on the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;PerformanceEntry&lt;/code&gt; interface, refer to the &lt;a href=&quot;https://w3c.github.io/performance-timeline/#the-performanceentry-interface&quot;&gt;spec&lt;/a&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;h4 id=&quot;layoutshiftattribution&quot; data-text=&quot;LayoutShiftAttribution&quot; tabindex=&quot;-1&quot;&gt;LayoutShiftAttribution&lt;/h4&gt;

&lt;p&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;LayoutShiftAttribution&lt;/code&gt; interface describes a single shift of a single DOM
element. If multiple elements shift during a layout shift, the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;sources&lt;/code&gt;
property contains multiple entries.&lt;/p&gt;

&lt;p&gt;For example, the following JSON corresponds to a layout shift with one source: the
downward shift of the &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&amp;lt;div id=&#39;banner&#39;&amp;gt;&lt;/code&gt; DOM element from &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;y: 76&lt;/code&gt; to
&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;y:246&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;JSON&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;sources&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;[&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;node&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-s2&quot;&gt;&quot;div#banner&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;previousRect&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;x&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;311&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;y&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;76&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;width&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;height&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;18&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;top&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;76&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;right&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;315&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;bottom&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;94&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;left&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;311&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;currentRect&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;x&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;311&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;y&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;246&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;width&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;height&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;18&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;top&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;246&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;right&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;315&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;bottom&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;264&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nt&quot;&gt;&quot;left&quot;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-mi&quot;&gt;311&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;
&lt;p&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;node&lt;/code&gt; property identifies the HTML element that shifted. Hovering on this
property in DevTools highlights the corresponding page element.&lt;/p&gt;

&lt;p&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;previousRect&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;currentRect&lt;/code&gt; properties report the size and position of
the node.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;x&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;y&lt;/code&gt; coordinates report the x-coordinate and y-coordinate
respectively of the top-left corner of the element&lt;/li&gt;
&lt;li&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;width&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;height&lt;/code&gt; properties report the width and height respectively
of the element.&lt;/li&gt;
&lt;li&gt;The &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;top&lt;/code&gt;, &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;right&lt;/code&gt;, &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;bottom&lt;/code&gt;, and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;left&lt;/code&gt; properties report the x or y
coordinate values corresponding to the given edge of the element. In other
words, the value of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;top&lt;/code&gt; is equal to &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;y&lt;/code&gt;; the value of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;bottom&lt;/code&gt; is equal to
&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;y+height&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If all properties of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;previousRect&lt;/code&gt; are set to 0 this means that the element has
shifted into view. If all properties of &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;currentRect&lt;/code&gt; are set to 0 this means
that the element has shifted out of view.&lt;/p&gt;

&lt;aside class=&quot;caution&quot;&gt;&lt;b&gt;Caution: &lt;/b&gt; Layout Instability API does not ignore elements that shifted but were not visible due to another element being positioned in front of them. Use &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;display: none&lt;/code&gt;, &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;visibility: hidden&lt;/code&gt;, or &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;opacity: 0&lt;/code&gt; to avoid layout shifts in cases where you need to run layout on some elements before you make them visible to the user. &lt;/aside&gt;

&lt;p&gt;One of the most important things to understand when interpreting these outputs
is that elements listed as &lt;em&gt;sources&lt;/em&gt; are the elements that shifted during the
layout shift. However, it&#39;s possible that these elements are only indirectly
related to the &quot;root cause&quot; of layout instability. Here are a few examples.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example #1&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This layout shift would be reported with one source: element B. However, the
root cause of this layout shift is the change in size of element A.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://web.dev/static/articles/debug-layout-shifts/image/example-showing-layout-s-f156cd54ed95a.png&quot; alt=&quot;Example showing a layout shift caused by a change in element dimensions&quot; width=&quot;800&quot; height=&quot;452&quot; srcset=&quot;https://web.dev/static/articles/debug-layout-shifts/image/example-showing-layout-s-f156cd54ed95a_36.png 36w,https://web.dev/static/articles/debug-layout-shifts/image/example-showing-layout-s-f156cd54ed95a_48.png 48w,https://web.dev/static/articles/debug-layout-shifts/image/example-showing-layout-s-f156cd54ed95a_72.png 72w,https://web.dev/static/articles/debug-layout-shifts/image/example-showing-layout-s-f156cd54ed95a_96.png 96w,https://web.dev/static/articles/debug-layout-shifts/image/example-showing-layout-s-f156cd54ed95a_480.png 480w,https://web.dev/static/articles/debug-layout-shifts/image/example-showing-layout-s-f156cd54ed95a_720.png 720w,https://web.dev/static/articles/debug-layout-shifts/image/example-showing-layout-s-f156cd54ed95a_856.png 856w,https://web.dev/static/articles/debug-layout-shifts/image/example-showing-layout-s-f156cd54ed95a_960.png 960w,https://web.dev/static/articles/debug-layout-shifts/image/example-showing-layout-s-f156cd54ed95a_1440.png 1440w,https://web.dev/static/articles/debug-layout-shifts/image/example-showing-layout-s-f156cd54ed95a_1920.png 1920w,https://web.dev/static/articles/debug-layout-shifts/image/example-showing-layout-s-f156cd54ed95a_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example #2&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The layout shift in this example would be reported with two sources: element A
and element B. The root cause of this layout shift is the change in position of
element A.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://web.dev/static/articles/debug-layout-shifts/image/example-showing-layout-s-2746ca34d851b.png&quot; alt=&quot;Example showing a layout shift caused by a change in element position&quot; width=&quot;800&quot; height=&quot;451&quot; srcset=&quot;https://web.dev/static/articles/debug-layout-shifts/image/example-showing-layout-s-2746ca34d851b_36.png 36w,https://web.dev/static/articles/debug-layout-shifts/image/example-showing-layout-s-2746ca34d851b_48.png 48w,https://web.dev/static/articles/debug-layout-shifts/image/example-showing-layout-s-2746ca34d851b_72.png 72w,https://web.dev/static/articles/debug-layout-shifts/image/example-showing-layout-s-2746ca34d851b_96.png 96w,https://web.dev/static/articles/debug-layout-shifts/image/example-showing-layout-s-2746ca34d851b_480.png 480w,https://web.dev/static/articles/debug-layout-shifts/image/example-showing-layout-s-2746ca34d851b_720.png 720w,https://web.dev/static/articles/debug-layout-shifts/image/example-showing-layout-s-2746ca34d851b_856.png 856w,https://web.dev/static/articles/debug-layout-shifts/image/example-showing-layout-s-2746ca34d851b_960.png 960w,https://web.dev/static/articles/debug-layout-shifts/image/example-showing-layout-s-2746ca34d851b_1440.png 1440w,https://web.dev/static/articles/debug-layout-shifts/image/example-showing-layout-s-2746ca34d851b_1920.png 1920w,https://web.dev/static/articles/debug-layout-shifts/image/example-showing-layout-s-2746ca34d851b_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example #3&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The layout shift in this example would be reported with one source: element B.
Changing the position of element B resulted in this layout shift.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://web.dev/static/articles/debug-layout-shifts/image/example-showing-layout-s-3f33956a00db3.png&quot; alt=&quot;Example showing a layout shift caused by a change in element position&quot; width=&quot;800&quot; height=&quot;451&quot; srcset=&quot;https://web.dev/static/articles/debug-layout-shifts/image/example-showing-layout-s-3f33956a00db3_36.png 36w,https://web.dev/static/articles/debug-layout-shifts/image/example-showing-layout-s-3f33956a00db3_48.png 48w,https://web.dev/static/articles/debug-layout-shifts/image/example-showing-layout-s-3f33956a00db3_72.png 72w,https://web.dev/static/articles/debug-layout-shifts/image/example-showing-layout-s-3f33956a00db3_96.png 96w,https://web.dev/static/articles/debug-layout-shifts/image/example-showing-layout-s-3f33956a00db3_480.png 480w,https://web.dev/static/articles/debug-layout-shifts/image/example-showing-layout-s-3f33956a00db3_720.png 720w,https://web.dev/static/articles/debug-layout-shifts/image/example-showing-layout-s-3f33956a00db3_856.png 856w,https://web.dev/static/articles/debug-layout-shifts/image/example-showing-layout-s-3f33956a00db3_960.png 960w,https://web.dev/static/articles/debug-layout-shifts/image/example-showing-layout-s-3f33956a00db3_1440.png 1440w,https://web.dev/static/articles/debug-layout-shifts/image/example-showing-layout-s-3f33956a00db3_1920.png 1920w,https://web.dev/static/articles/debug-layout-shifts/image/example-showing-layout-s-3f33956a00db3_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example #4&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Although element B changes size, there is no layout shift in this example.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://web.dev/static/articles/debug-layout-shifts/image/example-showing-element-927040d6f99ce.png&quot; alt=&quot;Example showing a element changing size but not causing a layout shift&quot; width=&quot;800&quot; height=&quot;446&quot; srcset=&quot;https://web.dev/static/articles/debug-layout-shifts/image/example-showing-element-927040d6f99ce_36.png 36w,https://web.dev/static/articles/debug-layout-shifts/image/example-showing-element-927040d6f99ce_48.png 48w,https://web.dev/static/articles/debug-layout-shifts/image/example-showing-element-927040d6f99ce_72.png 72w,https://web.dev/static/articles/debug-layout-shifts/image/example-showing-element-927040d6f99ce_96.png 96w,https://web.dev/static/articles/debug-layout-shifts/image/example-showing-element-927040d6f99ce_480.png 480w,https://web.dev/static/articles/debug-layout-shifts/image/example-showing-element-927040d6f99ce_720.png 720w,https://web.dev/static/articles/debug-layout-shifts/image/example-showing-element-927040d6f99ce_856.png 856w,https://web.dev/static/articles/debug-layout-shifts/image/example-showing-element-927040d6f99ce_960.png 960w,https://web.dev/static/articles/debug-layout-shifts/image/example-showing-element-927040d6f99ce_1440.png 1440w,https://web.dev/static/articles/debug-layout-shifts/image/example-showing-element-927040d6f99ce_1920.png 1920w,https://web.dev/static/articles/debug-layout-shifts/image/example-showing-element-927040d6f99ce_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/p&gt;

&lt;p&gt;Check out a &lt;a href=&quot;https://desert-righteous-router.glitch.me/&quot;&gt;demo of how DOM changes are reported by the Layout Instability API&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;devtools&quot; data-text=&quot;DevTools&quot; tabindex=&quot;-1&quot;&gt;DevTools&lt;/h3&gt;

&lt;p&gt;DevTools has a number of tools to help debug layout shifts.&lt;/p&gt;

&lt;h4 id=&quot;performance_panel&quot; data-text=&quot;Performance panel&quot; tabindex=&quot;-1&quot;&gt;Performance panel&lt;/h4&gt;

&lt;p&gt;The live metrics view of the &lt;a href=&quot;https://developer.chrome.com/docs/devtools/evaluate-performance&quot;&gt;Performance panel&lt;/a&gt; lets you interact with the page and monitor the CLS score to identify interactions causing large layout shfts.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/debug-layout-shifts/image/live-metrics-cls.png&quot; alt=&quot;Layout Shift records being displayed in the live metrics screen of Chrome DevTools performance panel.&quot; width=&quot;800&quot; height=&quot;615&quot; srcset=&quot;https://web.dev/static/articles/debug-layout-shifts/image/live-metrics-cls_36.png 36w,https://web.dev/static/articles/debug-layout-shifts/image/live-metrics-cls_48.png 48w,https://web.dev/static/articles/debug-layout-shifts/image/live-metrics-cls_72.png 72w,https://web.dev/static/articles/debug-layout-shifts/image/live-metrics-cls_96.png 96w,https://web.dev/static/articles/debug-layout-shifts/image/live-metrics-cls_480.png 480w,https://web.dev/static/articles/debug-layout-shifts/image/live-metrics-cls_720.png 720w,https://web.dev/static/articles/debug-layout-shifts/image/live-metrics-cls_856.png 856w,https://web.dev/static/articles/debug-layout-shifts/image/live-metrics-cls_960.png 960w,https://web.dev/static/articles/debug-layout-shifts/image/live-metrics-cls_1440.png 1440w,https://web.dev/static/articles/debug-layout-shifts/image/live-metrics-cls_1920.png 1920w,https://web.dev/static/articles/debug-layout-shifts/image/live-metrics-cls_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption&gt;
    The live metrics view of the Performance Panel allows monitoring of a web page&#39;s CLS score while interacting with the page.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Once you can reliable reproduce a layout shift, you can perform a trace to get even more details:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/debug-layout-shifts/image/devtools-cls-debugging.png&quot; alt=&quot;Layout Shift records being displayed in the Chrome DevTools performance panel.&quot; width=&quot;800&quot; height=&quot;638&quot; srcset=&quot;https://web.dev/static/articles/debug-layout-shifts/image/devtools-cls-debugging_36.png 36w,https://web.dev/static/articles/debug-layout-shifts/image/devtools-cls-debugging_48.png 48w,https://web.dev/static/articles/debug-layout-shifts/image/devtools-cls-debugging_72.png 72w,https://web.dev/static/articles/debug-layout-shifts/image/devtools-cls-debugging_96.png 96w,https://web.dev/static/articles/debug-layout-shifts/image/devtools-cls-debugging_480.png 480w,https://web.dev/static/articles/debug-layout-shifts/image/devtools-cls-debugging_720.png 720w,https://web.dev/static/articles/debug-layout-shifts/image/devtools-cls-debugging_856.png 856w,https://web.dev/static/articles/debug-layout-shifts/image/devtools-cls-debugging_960.png 960w,https://web.dev/static/articles/debug-layout-shifts/image/devtools-cls-debugging_1440.png 1440w,https://web.dev/static/articles/debug-layout-shifts/image/devtools-cls-debugging_1920.png 1920w,https://web.dev/static/articles/debug-layout-shifts/image/devtools-cls-debugging_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption&gt;
    After recording a new trace in the Performance panel, the &lt;b&gt;Layout Shifts&lt;/b&gt; track of the results is populated with purple bars displaying a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;Layout Shift&lt;/code&gt; clusters. Clicking the diamonds shows an animation of the shift and details in the &lt;b&gt;Summary&lt;/b&gt; panel.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Layout shifts are highlighted in the &lt;strong&gt;Layout shifts&lt;/strong&gt; track. The purple line groups shifts into shift clusters with the diamonds showing individual shifts in that cluster. The size of the diamond is proportional to the size of the shift allowing you to hone in on the largest shifts.&lt;/p&gt;

&lt;p&gt;Clicking on a shift shows a pop up with an animation of the shift and highlights the elements shift in purple.&lt;/p&gt;

&lt;p&gt;Additionally, the &lt;strong&gt;Summary&lt;/strong&gt; view for a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;Layout Shift&lt;/code&gt; record includes the start time, the shift score as well as the elements shifted. This is particularly helpful to get more detail on load CLS issues since this is easily replicated with a reload performance profile.&lt;/p&gt;

&lt;p&gt;This also links to the &lt;strong&gt;Layout shift culprits&lt;/strong&gt; insight displayed in the &lt;strong&gt;Insights&lt;/strong&gt; panel on the left, which shows the total CLS at the top, as well as possible reasons for layout shifts.&lt;/p&gt;

&lt;p&gt;For more information on using the &lt;strong&gt;Performance&lt;/strong&gt; panel, refer to &lt;a href=&quot;https://developer.chrome.com/docs/devtools/evaluate-performance/reference&quot;&gt;Performance
Analysis
Reference&lt;/a&gt;.&lt;/p&gt;

&lt;h4 id=&quot;highlight_layout_shift_regions&quot; data-text=&quot;Highlight layout shift regions&quot; tabindex=&quot;-1&quot;&gt;Highlight layout shift regions&lt;/h4&gt;

&lt;p&gt;Highlighting layout shift regions can be a helpful technique for getting a
quick, at-a-glance feel for the location and timing of the layout shifts
occurring on a page.&lt;/p&gt;

&lt;p&gt;To enable Layout Shift Regions in DevTools, go to &lt;strong&gt;Settings &amp;gt; More Tools &amp;gt;
Rendering &amp;gt; Layout Shift Regions&lt;/strong&gt; then refresh the page that you want to debug.
Areas of layout shift will be briefly highlighted in purple.&lt;/p&gt;

&lt;h2 id=&quot;thought_process_for_identifying_the_cause_of_layout_shifts&quot; data-text=&quot;Thought process for identifying the cause of layout shifts&quot; tabindex=&quot;-1&quot;&gt;Thought process for identifying the cause of layout shifts&lt;/h2&gt;

&lt;p&gt;You can use the following steps to identify the cause of layout shifts
regardless of when or how the layout shift occurs. These steps can be
supplemented with running Lighthouse—however, keep in mind that Lighthouse can
only identify layout shifts that occurred during the initial page load. In
addition, Lighthouse also can only provide suggestions for some causes of layout
shifts—for example, image elements that don&#39;t have explicit width and height.&lt;/p&gt;

&lt;h3 id=&quot;identify_the_cause_of_a_layout_shift&quot; data-text=&quot;Identify the cause of a layout shift&quot; tabindex=&quot;-1&quot;&gt;Identify the cause of a layout shift&lt;/h3&gt;

&lt;p&gt;Layout shifts can be caused by the following events:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Changes to the position of a DOM element&lt;/li&gt;
&lt;li&gt;Changes to the dimensions of a DOM element&lt;/li&gt;
&lt;li&gt;Insertion or removal of a DOM element&lt;/li&gt;
&lt;li&gt;Animations that trigger layout&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In particular, the DOM element immediately preceding the shifted element is the
element most likely to be involved in &quot;causing&quot; layout shift. Thus, when
investigating why a layout shift occurred consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did the position or dimensions of the preceding element change?&lt;/li&gt;
&lt;li&gt;Was a DOM element inserted or removed before the shifted element?&lt;/li&gt;
&lt;li&gt;Was the position of the shifted element explicitly changed?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the preceding element did not cause the layout shift, continue your search by
considering other preceding and nearby elements.&lt;/p&gt;

&lt;p&gt;In addition, the direction and distance of a layout shift can provide hints
about root cause. For example, a large downward shift often indicates the
insertion of a DOM element, whereas a 1&amp;nbsp;px or 2&amp;nbsp;px layout shift often indicates
the application of conflicting CSS styles or the loading and application of a
web font.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://web.dev/static/articles/debug-layout-shifts/image/diagram-showing-layout-s-60ec3d8c95616.png&quot; alt=&quot;Diagram showing a layout shift caused by a font swap&quot; width=&quot;800&quot; height=&quot;452&quot; srcset=&quot;https://web.dev/static/articles/debug-layout-shifts/image/diagram-showing-layout-s-60ec3d8c95616_36.png 36w,https://web.dev/static/articles/debug-layout-shifts/image/diagram-showing-layout-s-60ec3d8c95616_48.png 48w,https://web.dev/static/articles/debug-layout-shifts/image/diagram-showing-layout-s-60ec3d8c95616_72.png 72w,https://web.dev/static/articles/debug-layout-shifts/image/diagram-showing-layout-s-60ec3d8c95616_96.png 96w,https://web.dev/static/articles/debug-layout-shifts/image/diagram-showing-layout-s-60ec3d8c95616_480.png 480w,https://web.dev/static/articles/debug-layout-shifts/image/diagram-showing-layout-s-60ec3d8c95616_720.png 720w,https://web.dev/static/articles/debug-layout-shifts/image/diagram-showing-layout-s-60ec3d8c95616_856.png 856w,https://web.dev/static/articles/debug-layout-shifts/image/diagram-showing-layout-s-60ec3d8c95616_960.png 960w,https://web.dev/static/articles/debug-layout-shifts/image/diagram-showing-layout-s-60ec3d8c95616_1440.png 1440w,https://web.dev/static/articles/debug-layout-shifts/image/diagram-showing-layout-s-60ec3d8c95616_1920.png 1920w,https://web.dev/static/articles/debug-layout-shifts/image/diagram-showing-layout-s-60ec3d8c95616_2880.png 2880w&quot; sizes=&quot;(max-width: 840px) 100vw, 856px&quot; referrerpolicy=&quot;no-referrer&quot;&gt;
  &lt;figcaption class=&quot;wd-caption&quot;&gt;
    In this example, font swapping caused page elements to shift upwards by five pixels.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;These are some of the specific behaviors that most frequently cause layout shift
events:&lt;/p&gt;

&lt;h4 id=&quot;changes_to_the_position_of_an_element_that_arent_due_to_the_movement_of_another_element&quot; data-text=&quot;Changes to the position of an element (that aren&#39;t due to the movement of another element)&quot; tabindex=&quot;-1&quot;&gt;Changes to the position of an element (that aren&#39;t due to the movement of another element)&lt;/h4&gt;

&lt;p&gt;This type of change is often a result of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stylesheets that are loaded late or overwrite previously declared styles.&lt;/li&gt;
&lt;li&gt;Animation and transition effects.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;changes_to_the_dimensions_of_an_element&quot; data-text=&quot;Changes to the dimensions of an element&quot; tabindex=&quot;-1&quot;&gt;Changes to the dimensions of an element&lt;/h4&gt;

&lt;p&gt;This type of change is often a result of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stylesheets that are loaded late or overwrite previously declared styles.&lt;/li&gt;
&lt;li&gt;Images and iframes without &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;width&lt;/code&gt; and &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;height&lt;/code&gt; attributes that load after
their &quot;slot&quot; has been rendered.&lt;/li&gt;
&lt;li&gt;Text blocks without &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;width&lt;/code&gt; or &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;height&lt;/code&gt; attributes that swap fonts after the
text has been rendered.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;the_insertion_or_removal_of_dom_elements&quot; data-text=&quot;The insertion or removal of DOM elements&quot; tabindex=&quot;-1&quot;&gt;The insertion or removal of DOM elements&lt;/h4&gt;

&lt;p&gt;This is often the result of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Insertion of ads and other third-party embeds.&lt;/li&gt;
&lt;li&gt;Insertion of banners, alerts, and modals.&lt;/li&gt;
&lt;li&gt;Infinite scroll and other UX patterns that load additional content above
existing content.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;animations_that_trigger_layout&quot; data-text=&quot;Animations that trigger layout&quot; tabindex=&quot;-1&quot;&gt;Animations that trigger layout&lt;/h4&gt;

&lt;p&gt;Some animation effects can &lt;a href=&quot;https://gist.github.com/paulirish/5d52fb081b3570c81e3a&quot;&gt;trigger
layout&lt;/a&gt;. A common
example of this is when DOM elements are &#39;animated&#39; by incrementing properties
like &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;top&lt;/code&gt; or &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;left&lt;/code&gt; rather than using CSS&#39;s
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/transform&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;transform&lt;/code&gt;&lt;/a&gt;
property. Read &lt;a href=&quot;https://web.dev/articles/animations-guide&quot;&gt;How to create high-performance CSS animations&lt;/a&gt;
for more information.&lt;/p&gt;

&lt;h3 id=&quot;reproduce_layout_shifts&quot; data-text=&quot;Reproduce layout shifts&quot; tabindex=&quot;-1&quot;&gt;Reproduce layout shifts&lt;/h3&gt;

&lt;p&gt;You can&#39;t fix layout shifts that you can&#39;t reproduce. One of the simplest, yet
most effective things you can do to get a better sense of your site&#39;s layout
stability is take 5-10 minutes to interact with your site with the goal
triggering layout shifts. Keep the console open while doing this and use the
Layout Instability API to report on layout shifts.&lt;/p&gt;

&lt;p&gt;For hard to locate layout shifts, consider repeating this exercise with
different devices and connection speeds. In particular, using a slower
connection speed can make it easier to identify layout shifts. In addition,
you can use a &lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;debugger&lt;/code&gt; statement to make it easier to step through layout
shifts.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;&lt;devsite-code&gt;&lt;pre class=&quot;devsite-click-to-copy&quot; translate=&quot;no&quot; dir=&quot;ltr&quot; is-upgraded=&quot;&quot; syntax=&quot;Carbon&quot; syntax-guessed=&quot;&quot;&gt;&lt;code translate=&quot;no&quot; dir=&quot;ltr&quot;&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;PerformanceObserver&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;entryList&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;=&lt;/span&gt;&amp;gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;entryList&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;getEntries&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;())&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(!&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;hadRecentInput&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;cls&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;+=&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;debugger&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;Current&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;CLS&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;cls&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;devsite-syntax-p&quot;&gt;}).&lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;devsite-syntax-k&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;layout&lt;/span&gt;&lt;span class=&quot;devsite-syntax-o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;shift&lt;/span&gt;&lt;span class=&quot;devsite-syntax-err&quot;&gt;&#39;&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-nx&quot;&gt;buffered&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;devsite-syntax-w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;devsite-syntax-kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;devsite-syntax-p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/devsite-code&gt;

  

  
</description><link>https://web.dev/articles/debug-layout-shifts?hl=en</link><guid isPermaLink="false">https://web.dev/articles/debug-layout-shifts?hl=en</guid><pubDate>Fri, 07 Feb 2025 08:00:00 GMT</pubDate></item></channel></rss>