<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kilian Valkhof</title>
    <description>The latest articles on DEV Community by Kilian Valkhof (@kilianvalkhof).</description>
    <link>https://dev.to/kilianvalkhof</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F186494%2Ff0bbb9f1-affe-4896-b10e-5254a109c650.jpg</url>
      <title>DEV Community: Kilian Valkhof</title>
      <link>https://dev.to/kilianvalkhof</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kilianvalkhof"/>
    <language>en</language>
    <item>
      <title>HTML Form Inspector: inspect and debug your forms online</title>
      <dc:creator>Kilian Valkhof</dc:creator>
      <pubDate>Tue, 01 Jul 2025 13:23:21 +0000</pubDate>
      <link>https://dev.to/kilianvalkhof/html-form-inspector-inspect-and-debug-your-forms-online-m0p</link>
      <guid>https://dev.to/kilianvalkhof/html-form-inspector-inspect-and-debug-your-forms-online-m0p</guid>
      <description>&lt;p&gt;HTML forms are notoriously hard to get right. They require a lot of boilerplate HTML and even small mistakes can lead to frustrating user experiences like not being able to click a label to toggle a checkbox.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding issues in forms
&lt;/h2&gt;

&lt;p&gt;Because forms are so verbose, it can also be hard to discover issues in them just by looking at the code. For example, how long does it take you to spot the problem in this form?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"post"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"username"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;E-mail&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"username"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"username"&lt;/span&gt; &lt;span class="na"&gt;required=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;autocomplete=&lt;/span&gt;&lt;span class="s"&gt;"username"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Password&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;required=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;autocomplete=&lt;/span&gt;&lt;span class="s"&gt;"current-password"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&amp;gt;&lt;/span&gt;Sign in&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you think it's the empty &lt;code&gt;action&lt;/code&gt; attribute, you're wrong: an empty action attribute will submit the form to the current page.&lt;/p&gt;

&lt;p&gt;If you think it's the button without a &lt;code&gt;type="submit&lt;/code&gt;, that's also fine: if there's a single button in a form, it will be treated as a submit button by default.&lt;/p&gt;

&lt;p&gt;In this case, our password input is missing an &lt;code&gt;id&lt;/code&gt; attribute, which means that the label above it is not associated with the input. This means the input has no accessible name, and clicking the label will not focus the input.&lt;/p&gt;

&lt;p&gt;It's easy to overlook because as you can see, the line with the input contains the word "password" &lt;strong&gt;three times&lt;/strong&gt;. How much more does a browser need to tell you that this input is a password?&lt;/p&gt;

&lt;p&gt;Now take a look at the following image. How long does it take you to spot the problem?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foxp0o0hymw5rd8vvutir.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foxp0o0hymw5rd8vvutir.png" alt="Three rectangles listing the form configuration, the fields where the input with type password has no label, and a group listing the button with a type of submit." width="800" height="732"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's much quicker, right? You instantly see that the password input has no label, and now you can fix it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing the HTML Form Inspector
&lt;/h2&gt;

&lt;p&gt;The screenshot above is how the form above is shown in our new &lt;a href="https://polypane.app/form-inspector/" rel="noopener noreferrer"&gt;HTML Form Inspector&lt;/a&gt;, a free online tool that helps you inspect and debug your HTML forms.&lt;/p&gt;

&lt;p&gt;As we were building out the &lt;a href="https://polypane.app/docs/outline/#forms" rel="noopener noreferrer"&gt;Forms Outline&lt;/a&gt; feature in Polypane and subsequently finding out that &lt;em&gt;all our forms&lt;/em&gt; had issues easily picked up, we realized that this would be a great tool to have online.&lt;/p&gt;

&lt;p&gt;So we took the core functionality of our Forms Outline, retooled it from using the Live DOM (which we use in Polypane) to using the HTML source code, and made it &lt;a href="https://polypane.app/form-inspector/" rel="noopener noreferrer"&gt;available online for everyone to use&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How does it work?
&lt;/h2&gt;

&lt;p&gt;The HTML Form Inspector takes the HTML source code of your form and shows it in a structured way, splitting out the various parts of the form:&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;The inspector starts with a summary, so can at a glance see how many fields in total, how many required fields, how many fieldsets and how many buttons there are in the form. This already helps you spot issues like a missing &lt;code&gt;required&lt;/code&gt; attribute or a missing button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa2mw009hb90s1ubqq2f2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa2mw009hb90s1ubqq2f2.png" alt="4 numbers showing the total fields, required fields, fieldsets and buttons." width="800" height="96"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Form configuration
&lt;/h3&gt;

&lt;p&gt;The form configuration shows the &lt;code&gt;action&lt;/code&gt; and &lt;code&gt;method&lt;/code&gt; attributes of the form so you know where it submits to and how it submits.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3tdsxk7y5qxe5lwrjb0e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3tdsxk7y5qxe5lwrjb0e.png" alt="Form configuration overview showing an action and method" width="800" height="166"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Fields
&lt;/h3&gt;

&lt;p&gt;The fields section lists all the fields in the form that aren't part of a fieldset and shows their element, type, name, label, value/checked status and whether they are required and disabled. You can click open the details of each field to see any other attributes like &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;autocomplete&lt;/code&gt;, &lt;code&gt;placeholder&lt;/code&gt; and more.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ujz2hmb99a36he18ro6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ujz2hmb99a36he18ro6.png" alt="Field overview showing a table of fields and their properties" width="800" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Fieldsets
&lt;/h3&gt;

&lt;p&gt;Each fieldset in the form is shown in its own section, with the &lt;code&gt;legend&lt;/code&gt; and all the fields that are part of that fieldset. This makes it easy to see which fields are grouped together and under what title.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg7uy3owdk9cmkg64vf66.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg7uy3owdk9cmkg64vf66.png" alt="Fieldet overview showing the fieldset name and a table of fields and their properties" width="800" height="516"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Buttons
&lt;/h3&gt;

&lt;p&gt;The last section lists all the buttons in the form, showing their type, name, value and whether they are disabled. This is useful to see if you have a submit button and if the label makes sense.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy8o29yinv5ucpsdwgcz5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy8o29yinv5ucpsdwgcz5.png" alt="Button overview showing a table of buttons and their properties" width="800" height="243"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it yourself!
&lt;/h2&gt;

&lt;p&gt;You can find the HTML Form Inspector at &lt;a href="https://polypane.app/form-inspector/" rel="noopener noreferrer"&gt;polypane.app/form-inspector&lt;/a&gt;, or click the image below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://polypane.app/form-inspector/" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/p&gt;

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



&lt;p&gt;We know that making this tool freely available might not be the smartest business move, but we couldn't find anything else like it out there.&lt;/p&gt;

&lt;p&gt;And honestly, we've discovered so many forms with easily detectable issues that we felt compelled to share it with everyone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Go even deeper with Polypane
&lt;/h2&gt;

&lt;p&gt;In Polypane we use the live DOM to check forms which means we can pick up even more issues but also provide you with more context, and suggestions on how to improve your forms.&lt;/p&gt;

&lt;p&gt;For example, we can tell you if the &lt;code&gt;autocomplete&lt;/code&gt; attribute has an invalid value, when the label is set by something other than a label (like ARIA, or a placeholder), if your radio groups are structured correctly, and so much more.&lt;/p&gt;

&lt;p&gt;When you've tested your forms with the HTML Form Inspector, you can use Polypane to go even deeper and fix any remaining issues.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frm8omcmxkn63ricvozty.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frm8omcmxkn63ricvozty.png" alt="Form Outline in Polypane showing missing labels" width="551" height="242"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Polypane has a free 14 day trial (no credit card needed) that will let you test all your forms, along with dozens of other parts of your sites: &lt;strong&gt;&lt;a href="https://dashboard.polypane.app/register" rel="noopener noreferrer"&gt;Try Polypane for free&lt;/a&gt;&lt;/strong&gt;!&lt;/p&gt;

</description>
      <category>html</category>
      <category>webdev</category>
      <category>a11y</category>
      <category>tooling</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Kilian Valkhof</dc:creator>
      <pubDate>Fri, 18 Apr 2025 07:04:53 +0000</pubDate>
      <link>https://dev.to/kilianvalkhof/-1dok</link>
      <guid>https://dev.to/kilianvalkhof/-1dok</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/sachagreif/launching-the-first-ever-state-of-devs-survey-23il" class="crayons-story__hidden-navigation-link"&gt;Launching the first ever State of Devs survey&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/sachagreif" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1441%2Fj8ehsrukq7v6bh6tswfc.png" alt="sachagreif profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/sachagreif" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Sacha Greif
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Sacha Greif
                
              
              &lt;div id="story-author-preview-content-2415381" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/sachagreif" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1441%2Fj8ehsrukq7v6bh6tswfc.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Sacha Greif&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/sachagreif/launching-the-first-ever-state-of-devs-survey-23il" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Apr 18 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/sachagreif/launching-the-first-ever-state-of-devs-survey-23il" id="article-link-2415381"&gt;
          Launching the first ever State of Devs survey
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/surveys"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;surveys&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/sachagreif/launching-the-first-ever-state-of-devs-survey-23il" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;15&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/sachagreif/launching-the-first-ever-state-of-devs-survey-23il#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              2&lt;span class="hidden s:inline"&gt; comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            3 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

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

&lt;/div&gt;


</description>
      <category>surveys</category>
    </item>
    <item>
      <title>Black Friday 2024: Save 25% on Polypane</title>
      <dc:creator>Kilian Valkhof</dc:creator>
      <pubDate>Mon, 25 Nov 2024 09:46:05 +0000</pubDate>
      <link>https://dev.to/polypane/black-friday-2024-save-25-on-polypane-2gk7</link>
      <guid>https://dev.to/polypane/black-friday-2024-save-25-on-polypane-2gk7</guid>
      <description>&lt;p&gt;As developers, we're always looking for tools that can streamline our workflow and make responsive design testing less painful. If you've been eyeing Polypane - the powerful browser built specifically for web developers - now's the perfect time to grab it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Deal
&lt;/h2&gt;

&lt;p&gt;Polypane is offering a massive 25% discount on all yearly plans for Black Friday. This deal runs until Monday, December 2nd, giving you a limited window to secure a year of faster, more efficient web development at a significant discount.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Polypane Matters
&lt;/h2&gt;

&lt;p&gt;For those unfamiliar with Polypane, it's a specialized browser that transforms how you develop and debug websites. Instead of constantly resizing your browser window or switching between different device views, Polypane lets you see, inspect and edit all your breakpoints simultaneously. This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instant visual feedback across multiple screen sizes&lt;/li&gt;
&lt;li&gt;Built-in accessibility checks and debugging tools&lt;/li&gt;
&lt;li&gt;Synchronized actions across all panes&lt;/li&gt;
&lt;li&gt;Advanced CSS debugging and performance insights&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Beyond that, Polypane helps you gain insight into your meta tag info (including previews of how your site looks shared on nine different social media sites), accessibility, cookies and other storage and your web vitals performance. There's tooling for all aspects of your web development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Developer Productivity Impact
&lt;/h2&gt;

&lt;p&gt;The real value of Polypane comes from the time it saves. By seeing all your layouts at once, you catch responsive design issues immediately rather than discovering them later in testing. This translates to faster development cycles and more robust websites.&lt;/p&gt;

&lt;h2&gt;
  
  
  Grab the Deal
&lt;/h2&gt;

&lt;p&gt;If you're ready to upgrade your development workflow, head over to &lt;a href="https://polypane.app/black-friday/" rel="noopener noreferrer"&gt;Polypane's Black Friday page&lt;/a&gt; to claim your 25% discount. Remember, this offer ends Monday, December 2nd.&lt;/p&gt;

&lt;p&gt;Your future self will thank you for making the investment in better development tools. Happy coding!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>blackfriday</category>
      <category>tooling</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Polypane 20: Browser features and performance</title>
      <dc:creator>Kilian Valkhof</dc:creator>
      <pubDate>Wed, 19 Jun 2024 13:16:32 +0000</pubDate>
      <link>https://dev.to/polypane/polypane-20-browser-features-and-performance-3i1e</link>
      <guid>https://dev.to/polypane/polypane-20-browser-features-and-performance-3i1e</guid>
      <description>&lt;p&gt;Polypane 20 improves the features and performance of the Elements and Outline panel, as well as improving general browser features and stability. It's also running the latest version of Chromium, 126.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What's &lt;a href="https://polypane.app"&gt;Polypane&lt;/a&gt;?&lt;/strong&gt; Polypane is the web browser for ambitious web developers. It's a stand-alone browser that shows&lt;br&gt;
sites in multiple fully synced &lt;em&gt;panes&lt;/em&gt; and helps you make your site more responsive, more accessible and faster.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With &lt;a href="https://polypane.app/blog/polypane-19-workflow-improvements/"&gt;Polypane 19&lt;/a&gt; being only a few weeks old we weren't planning on releasing a major version so soon, but we wanted to get the new Chromium version out to you as soon as possible.&lt;/p&gt;

&lt;p&gt;Because of that this release is a little light on new features, but it does include a lot of smaller improvements and fixes that make a real difference in day-to-day usage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Elements panel
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://polypane.app/docs/elements-panel"&gt;Elements panel&lt;/a&gt; is where you can inspect and edit the DOM and CSS across all opened panes at once.&lt;/p&gt;

&lt;p&gt;As more CSS features become available we work hard to implement them (often before other devtools do, like &lt;code&gt;@layer&lt;/code&gt; support in Polypane 8 or native CSS nesting in Polypane 13) and to make sure the panel is still as fast as possible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance
&lt;/h3&gt;

&lt;p&gt;In this release we made a significant improvement in how we index the CSS styles for each pane. In some situations that is now an order of magnitude faster.&lt;/p&gt;

&lt;p&gt;This means that the initial load of the Elements panel is faster, and that the panel is much more responsive when switching between DOM nodes even in more complex pages.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;@starting-style&lt;/code&gt; support
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;@starting-style&lt;/code&gt; at-rule is really cool: it lets you define the initial style for an element when adding it to the DOM. This means that you can now animate elements as you add them to the DOM without needing JS. In Polypane you can now access that &lt;code&gt;@starting-style&lt;/code&gt; like any other CSS rule.&lt;/p&gt;

&lt;p&gt;You won't find this in the Chromium devtools yet so if you want to experiment with it, Polypane is the place to do it.&lt;/p&gt;

&lt;h3&gt;
  
  
  A11y panel updates
&lt;/h3&gt;

&lt;p&gt;In the accessibility panel we now have two new checks for the accessible name.&lt;/p&gt;

&lt;h4&gt;
  
  
  Warning for missing accessible names
&lt;/h4&gt;

&lt;p&gt;Firstly, Polypane now knows which elements should have an accessible name (like headings, links, buttons etc) and will warn you if the selected element is missing one. Just a quick little test that will catch small issues. Note that this can also easily be checked in the Outline panel.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9hfajgeoo1v67vrlo44q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9hfajgeoo1v67vrlo44q.png" alt="A warning in the a11y panel when the accessible name is missing" width="411" height="255"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Warning for repetition in the accessible name
&lt;/h4&gt;

&lt;p&gt;An often found accessibility issue is the following scenario:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"home-icon.svg"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Home"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  Home
&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Superficially this looks pretty good:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it's a real link;&lt;/li&gt;
&lt;li&gt;it has readable text;&lt;/li&gt;
&lt;li&gt;the image has an alt text.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But when this is sent to a screen reader, it will read out "Home Home" because the accessible name for the link is the combination of the alt text and the text content.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmcfvio69ig93l14qm3cp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmcfvio69ig93l14qm3cp.png" alt="A warning in the a11y panel when the accessible name has repeating parts" width="411" height="312"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Polypane now check for repetition in accessible names and warns you when it finds them, and will also work with more complex repetition.&lt;/p&gt;

&lt;p&gt;Writing the function/algorithm for this was a lot of fun, and we'll explain how we did it in a future blog post.&lt;/p&gt;

&lt;h3&gt;
  
  
  Other Elements panel improvements
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;In this release we've improved the sorting of autocompletion suggestions when adding new CSS properties. In Polypane 19 we made suggestions purely on frequency (so if you types &lt;code&gt;fo&lt;/code&gt; that would be autocompleted to &lt;code&gt;font-style&lt;/code&gt;, as that's more likely than &lt;code&gt;font&lt;/code&gt; or &lt;code&gt;font-display&lt;/code&gt;) and in Polypane 20 this is now expanded to prefer the shortest property name when the frequencies are close. Long story short, it should make the suggestion feel even more natural.&lt;/li&gt;
&lt;li&gt;When you have a top level &lt;code&gt;&amp;amp;&lt;/code&gt; in your CSS that applies the styling to &lt;code&gt;:root&lt;/code&gt;. Polypane now correctly shows this selector in the Elements panel.&lt;/li&gt;
&lt;li&gt;The Elements panel now supports the &lt;code&gt;round()&lt;/code&gt;, &lt;code&gt;mod()&lt;/code&gt; and &lt;code&gt;rem()&lt;/code&gt; CSS functions.&lt;/li&gt;
&lt;li&gt;Along with &lt;code&gt;aria-hidden&lt;/code&gt;, the DOM view now also highlights the &lt;code&gt;inert&lt;/code&gt; attribute.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Outline panel
&lt;/h2&gt;

&lt;p&gt;We added some new checks to the &lt;a href="https://polypane.app/docs/outline-panel/"&gt;Outline panel&lt;/a&gt;, and improved its performance too.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance improvements
&lt;/h3&gt;

&lt;p&gt;Instead of getting accessibility data directly from the rendered page, we now get it from the accessibility tree that Chromium generates. This moves the work out of the main process and into a separate process, which makes the panel faster and more stable.&lt;/p&gt;

&lt;h4&gt;
  
  
  Looking ahead: full accessibility tree view
&lt;/h4&gt;

&lt;p&gt;In a future release, this means we'll also introduce a full accessibility tree view. If you have suggestions on how we can make that the most useful for you, let us know!&lt;/p&gt;

&lt;h3&gt;
  
  
  Cap on automatic link testing
&lt;/h3&gt;

&lt;p&gt;The Link overview in the panel will &lt;a href="https://polypane.app/docs/outline-panel/#broken-link-checking"&gt;automatically check all links on a page&lt;/a&gt; to see if they don't go to broken pages, so you catch broken URLs without having to click each one.&lt;/p&gt;

&lt;p&gt;This is great when you have a few links, but when you have a lot of links this takes a lot of processing as well as bandwidth (for you and your server).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frc73uq57eb8wcz7tz1uv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frc73uq57eb8wcz7tz1uv.png" alt="a check status button next to the 'link' heading in the outline panel" width="404" height="179"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So we've added a cap. When there are more than 100 links on a page, Polypane shows a "Check status" button that you have to click before we go and check them all.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accessible name vs visible name
&lt;/h3&gt;

&lt;p&gt;The Outline panel now also checks the visible name of an element against the accessible name.&lt;br&gt;
The visible text should be the same as the accessible name, or a substring off it. When it's not this could be a violation of WCAG success criterion 2.5.3, so we flag that for you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv61nz9w3aex1ptvvspr4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv61nz9w3aex1ptvvspr4.png" alt="an exclamation point icon next to the words 'visual content', showing an unexpected difference between the visible and accessible name" width="401" height="171"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For example, on the homepage of Polypane we have a title with some fancy styling, and that styling interferes with the accessible name (removing spaces) so we overwrite that with a &lt;code&gt;aria-label&lt;/code&gt; attribute. Then we (let's not hide behind "we". It was &lt;em&gt;I&lt;/em&gt;, Kilian) changed the visible text to a much cooler title, but forgot to update the &lt;code&gt;aria-label&lt;/code&gt; attribute. Polypane now catches that for me.&lt;/p&gt;

&lt;p&gt;If the visible text is a substring of the accessible name, we show the difference but this isn't considered a violation (on its own).&lt;/p&gt;
&lt;h3&gt;
  
  
  Repetition in accessible names
&lt;/h3&gt;

&lt;p&gt;Like in the Elements panel, we now also check for repetition in accessible names in the Outline panel. Here too we show the repeating parts so you can quickly validate if it's a problem or not.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1bkukuo7ohko33dlt476.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1bkukuo7ohko33dlt476.png" alt="A warning about repeating text in the outline panel" width="411" height="312"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Support for heading levels 7 through 9
&lt;/h3&gt;

&lt;p&gt;When it comes to accessibility, there's always more to know. Turns out that &lt;code&gt;aria-level&lt;/code&gt; doesn't just match up to H1 through H6, but you can actually go all the way up to level 9 and browsers will still understand it.&lt;/p&gt;

&lt;p&gt;...Except you probably shouldn't be using them, so we now flag it as a warning. If you find your structure needs them you're probably better off splitting that page or restructuring your content.&lt;/p&gt;

&lt;p&gt;Heading levels beyond 9 however are not supported at all by browsers and aren't communicated as such to assistive technology. Those we will flag as an error.&lt;/p&gt;
&lt;h2&gt;
  
  
  Load failure messages
&lt;/h2&gt;

&lt;p&gt;The earliest few version of Polypane had a load error state but we lost them during a refactor. It took us a while but now they're back, and updated!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqcdji5sfb6hm8q6tjgna.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqcdji5sfb6hm8q6tjgna.png" alt="unreachable server message in the panes" width="800" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When a page fails to load, Polypane will show a message in the pane that failed to load, so you know what's going on. Depending on the type of failure it tries to be a little helpful. Most often it's either a typo, a network issue or the server is gone.&lt;/p&gt;

&lt;p&gt;&amp;lt;svg&lt;br&gt;
  xmlns="&lt;a href="http://www.w3.org/2000/svg"&gt;http://www.w3.org/2000/svg&lt;/a&gt;"&lt;br&gt;
  width="128"&lt;br&gt;
  height="128"&lt;br&gt;
  viewBox="0 0 24 24"&lt;br&gt;
  fill="none"&lt;br&gt;
  stroke="currentColor"&lt;br&gt;
  stroke-width="2"&lt;br&gt;
  stroke-linecap="round"&lt;br&gt;
  stroke-linejoin="round"&lt;br&gt;
  class="icon icon-tabler icons-tabler-outline icon-tabler-device-gamepad-2"&lt;br&gt;
  style="display: block;margin-inline: auto;"&lt;/p&gt;

&lt;blockquote&gt;



&lt;/blockquote&gt;

&lt;p&gt;Like any &lt;em&gt;serious&lt;/em&gt; browser, this error message should also have some sort of game in it. &lt;em&gt;Obviously&lt;/em&gt;. So I'm asking you:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What kind of game would you like to see in Polypane?&lt;/strong&gt; A snake game where the snake travels across all panes? A pong or breakout game where you control the paddle in all panes and you're playing multiple games at once? A game where you have to find the differences between two panes? Let me know (on Twitter, via email or the chat)! ...Bonus points if I don't have to build something from scratch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Screenshotting improvements
&lt;/h2&gt;

&lt;p&gt;We've made various small improvements to the screenshotting functionality in Polypane.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dashed and dotted lines in the screenshot editor
&lt;/h3&gt;

&lt;p&gt;You can now draw dashed and dotted lines in the screenshot editor. Thanks Rik for enabling this in &lt;a href="https://pqina.nl/pintura/?aff=xLXrx&amp;amp;ref=polypane"&gt;Pintura&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F20rv4bo0nva1jymy833j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F20rv4bo0nva1jymy833j.png" alt="Dashed lines in the screenshot editor with the opened option showing the different line styles" width="800" height="511"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Easy access to the storage folder
&lt;/h3&gt;

&lt;p&gt;When you take a screenshot and quick-save it, it will automatically be saved to the last used folder. We've now added a link to open that folder directly from the context menu of screenshot buttons.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7shzeoeaoyg0bhopg58v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7shzeoeaoyg0bhopg58v.png" alt="open folder button" width="385" height="236"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Overview screenshot distortion (fix)
&lt;/h3&gt;

&lt;p&gt;In vertical layout, sometimes the panes in an overview screenshot would get distorted, depending on things like multiple monitors with different pixel ratios and other fun situations. This is now fixed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Address bar
&lt;/h2&gt;

&lt;p&gt;The address bar in Polypane can match URLs and page titles based on fragments. So you can type &lt;code&gt;po blo 20&lt;/code&gt; to get to e.g. &lt;code&gt;https://&lt;strong&gt;po&lt;/strong&gt;lypane.app/&lt;strong&gt;blo&lt;/strong&gt;g/&lt;strong&gt;po&lt;/strong&gt;lypane-&lt;strong&gt;20&lt;/strong&gt;-browser-features-and-performance/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The algorithm behind it now prefers perfect matches over multiple partial matches. So if you type "sign in" it will put urls with "sign in" in the url or title higher than other URLs that might contain both "sign" and "in" at different points in the URL.&lt;/p&gt;

&lt;p&gt;It's a small tweak but it makes the address bar feel much more dependable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chromium 126
&lt;/h2&gt;

&lt;p&gt;Polypane 20 includes an updated Chromium version, 126.0.6478.36. This lands cross-document view transitions and the Chromium devtools performance panel got a lot of improvements.&lt;/p&gt;

&lt;p&gt;For an overview of the new experimental features enabled in 126, head over to our experimental features overview: &lt;a href="https://dev.to/experimental-web-platform-features/"&gt;Experimental Chromium Web Platform Features&lt;/a&gt; and for additional info, head over to the &lt;a href="https://dev.to/docs/experimental-chromium-features/"&gt;experimental chromium features docs&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Polypane 20
&lt;/h2&gt;

&lt;p&gt;Polypane is available for Windows, Mac and Linux (.deb or AppImage) in both 64 bit and ARM versions.&lt;/p&gt;

&lt;p&gt;Polypane automatically updates on Mac, Windows and on Linux when using the AppImage. Otherwise, go to&lt;br&gt;
&lt;a href="https://polypane.app/download/"&gt;the download page&lt;/a&gt; to download the latest version!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't have Polypane yet? There is a 14 day trial available. &lt;a href="https://dashboard.polypane.app/register"&gt;Try it for free&lt;/a&gt;.&lt;/strong&gt; No credit card needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Polypane 20 Changelog
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;New&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;New&lt;/strong&gt; Elements panel: &lt;code&gt;@starting-style&lt;/code&gt; support&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New&lt;/strong&gt; Elements panel: Accessible name missing warnings (Thanks Torrance!)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New&lt;/strong&gt; Outline panel: check visible name against accessible name (WCAG SC 2.5.3) Thanks Markus!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New&lt;/strong&gt; Accessible names check for repeating substrings (Thanks Eric!)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New&lt;/strong&gt; Screenshot editor: dotted and dashed line support (Thanks Eric and Rik!)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New&lt;/strong&gt; Page load failure messages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New&lt;/strong&gt; Chromium 126&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Improved&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Elements panel: better sorting of suggested CSS properties&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Elements panel: Support for &lt;code&gt;round()&lt;/code&gt;, &lt;code&gt;mode()&lt;/code&gt; and &lt;code&gt;rem()&lt;/code&gt; CSS functions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Elements panel: Support for top level &lt;code&gt;&amp;amp;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Elements panel: Support for nested at-rules&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Elements panel: increase the number of CSS rules Polypane can process at once&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Elements panel: significant performance improvements for nested styles&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Elements panel: Highlight the &lt;code&gt;inert&lt;/code&gt; attribute&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Outline panel: With more than 100 links, testing them is now an explicit action&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Address bar: Prefer perfect matches in suggestions when typing fragments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Screenshots: add 'open folder' link in context menus of screenshot buttons&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Command bar: Allow matching by category (Thanks Artem!)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Show a error message for unsupported state files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Outline panel: support for heading levels 7 through 9&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Performance: Get and use accessibility tree out-of-process&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Live CSS panel: Support folding CSS code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Touch emulation: Easier toggle between touch in pane and normal mouse outside of pane&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Add cmd+shift+brackets to nagivate between tabs (Thanks Jerod!)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Responsiveness of resizing the devtools and browser panels&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Updated Google Fonts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Fixes&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt; Elements panel: pressing : when adding a css property no longer applies the suggestion.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt; Elements panel: Only add anonymous scoping roots when style elements contains them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt; Workspace panel: preview for 100% height panes are now visible again (Thanks Artem!)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt; Command bar: Lorem Ipsum command now works again&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt; Command bar: Show correct shortcuts for next/previous tab on mac&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt; Screenshot menu no longer hidden under devtools (Thanks Ahmad!)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt; 100% height panes in vertical layout now have a minimum height of 800px.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt; A11y checks: Prevent &lt;code&gt;input type="hidden"&lt;/code&gt; from being marked as focusable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt; Visible focus styles during signup flow (Thanks Novella!)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt; Prevent error when failing to write detached panel window state (Thanks John!)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt; Prevent error when devtools reference is destroyed (Thanks Mike!)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt; Overview screenshot distortion in vertical layout (Thanks Travis!)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt; Pane emulation: Prevent auto dark mode from incorrectly showing as on for certain configurations&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>css</category>
      <category>news</category>
    </item>
    <item>
      <title>How to check your site in different languages and writing modes</title>
      <dc:creator>Kilian Valkhof</dc:creator>
      <pubDate>Thu, 23 May 2024 09:56:51 +0000</pubDate>
      <link>https://dev.to/polypane/how-to-check-your-site-in-different-languages-and-writing-modes-5f87</link>
      <guid>https://dev.to/polypane/how-to-check-your-site-in-different-languages-and-writing-modes-5f87</guid>
      <description>&lt;p&gt;Building sites that support multiple writing modes and languages is challenging. You don't always know the language or even the script, and changing your OS to trigger certain conditions is cumbersome.&lt;/p&gt;

&lt;p&gt;Even if you're willing to make that change on your device, during development you might not even have the other languages available to test with.&lt;/p&gt;

&lt;p&gt;This post will show you how to test your site in different languages and writing modes without changing your OS settings or having access to your content in other languages (and without having to learn a new language).&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick terms
&lt;/h2&gt;

&lt;p&gt;When we talk about multi-lingual sites, there are a few terms you should be familiar with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Writing mode&lt;/strong&gt;: The direction in which text is written. For example, English is written from left to right, while Arabic is written from right to left.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Page language&lt;/strong&gt;: The language in which the content of the page is written.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browser Locale&lt;/strong&gt;: This defines the user's language and region.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of these fall under two terms that are also worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Localization&lt;/strong&gt; (often shortened to l10n): The process of adapting a product or content to a specific locale or market: using the right spelling, number formatting, currency, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Internationalization&lt;/strong&gt; (often shortened to i18n): The process of designing and developing a product or content in a way that it can be easily localized: using the right encoding, designing for multiple text directions, having a resilient layout, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This post dives into how you can test your site in different languages and writing modes without changing your OS settings, so it focuses on the &lt;em&gt;i18n part&lt;/em&gt; of the process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges
&lt;/h2&gt;

&lt;p&gt;When building a site that supports multiple languages and writing modes, you need to consider the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Language-dependent Browser APIs&lt;/strong&gt; like the I18n API, which is used to format dates, numbers, and currencies according to the user's (browser) locale.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Text direction&lt;/strong&gt;: Some languages are written from left to right, while others are written from right to left. Your site can adapt to this when you take it into account during development.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Text length&lt;/strong&gt;: Compared to English, some languages have much longer words and sentences while others have much shorter words and sentences. This can cause your layout to break if you don't account for it.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The basics
&lt;/h2&gt;

&lt;p&gt;To make sure your site adapts well to multiple languages and writing modes, you need to make sure that you have the following in place:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The &lt;code&gt;lang&lt;/code&gt; attribute&lt;/strong&gt;: This attribute is used to specify the language of the content of the page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The &lt;code&gt;dir&lt;/code&gt; attribute&lt;/strong&gt;: This attribute is used to specify the direction of the text of the page.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both of these attributes go on your &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; tag. From these two attributes, the browser can infer the language and writing mode of the page and adjust accordingly.&lt;/p&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;lang&lt;/code&gt; attribute
&lt;/h3&gt;

&lt;p&gt;The lang attribute should have as its value the language code of the language you are using. It's important to set because it helps search engines know what language your page is in, and assistive technology like screen readers use it to pick the right voice and to make sure pronunciations are correct.&lt;/p&gt;

&lt;p&gt;For example, for English you would use &lt;code&gt;en&lt;/code&gt;, for Arabic you would use &lt;code&gt;ar&lt;/code&gt;, and for Japanese you would use &lt;code&gt;ja&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you're targeting a specific dialect, you can use the language code followed by a dash and the dialect code. For example, for American English, you would use &lt;code&gt;en-US&lt;/code&gt; while for British English you would use &lt;code&gt;en-GB&lt;/code&gt;. The general advice though is to use the language code &lt;em&gt;without&lt;/em&gt; the region code unless there is a reason why the specific regional dialect is important for this page.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Keep in mind that this doesn't mean you're targeting the USA or the UK specifically, since other countries also use either American English (like Canada) or British English (like Australia).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Occasionally you'll see the language code using an underscore to separate the language and region code, like &lt;code&gt;en_GB&lt;/code&gt;. While Firefox normalizes this for you, both Safari and Chrome will not recognize this as a valid language code and so also not expose it to assistive technology.&lt;/p&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;dir&lt;/code&gt; attribute
&lt;/h3&gt;

&lt;p&gt;The dir attribute should have as value either &lt;code&gt;ltr&lt;/code&gt; (left-to-right) or &lt;code&gt;rtl&lt;/code&gt; (right-to-left) and is used to specify the direction of the text of the page.&lt;/p&gt;

&lt;p&gt;The default value on the web is &lt;code&gt;ltr&lt;/code&gt;, so you usually only see this added for languages that are written from right to left as &lt;code&gt;&amp;lt;html dir="rtl"&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;With these two in place, you've told the browser everything it needs to know about your page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing different languages and writing modes
&lt;/h2&gt;

&lt;p&gt;Now to test for different writing modes and languages, you can change the &lt;code&gt;lang&lt;/code&gt; and &lt;code&gt;dir&lt;/code&gt; attributes of your page. You could open the element inspector and change these manually but in Polypane, the emulations panel gives you easy access to these features along with the ability to change the browser locale.&lt;/p&gt;

&lt;h3&gt;
  
  
  Changing the reading direction
&lt;/h3&gt;

&lt;p&gt;To change the reading direction of your page, you can use the &lt;code&gt;dir&lt;/code&gt; attribute.&lt;/p&gt;

&lt;p&gt;Changing this from &lt;code&gt;ltr&lt;/code&gt; to &lt;code&gt;rtl&lt;/code&gt; will update the default styling in browsers such that text becomes right-aligned and the "start" value (for example, for flexbox and grid alignment) evaluates to the right. It does this even if your content is still in English.&lt;/p&gt;

&lt;p&gt;In Polypane, you can change the reading direction of your page by selecting the desired direction from the emulations dropdown.&lt;/p&gt;

&lt;p&gt;With the &lt;code&gt;dir&lt;/code&gt; attribute set to &lt;code&gt;rtl&lt;/code&gt;, you can test how your site looks when the text is right-aligned. If we check out the screenshot comparing the left-to-right and right-to-left versions we can see that it has done a pretty good job: the navigation is now on the right-hand side and the text mostly still has the correct padding, but we can spot a few things that we should fix:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1hlg2q28w5yqmql5c8n1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1hlg2q28w5yqmql5c8n1.png" alt="Two panes, one with ltr and one with rtl." width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Visual issues here are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The 100% bar is still filled to the right.&lt;/li&gt;
&lt;li&gt;The "dismiss" button is still on the right side, and the "hide this block" image is no longer pointing at it.&lt;/li&gt;
&lt;li&gt;The checkmarks next to each list item are flush against the text and have too much space.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Almost all of these are the result of me using non-logical margins and alignments. Instead of &lt;code&gt;text-align: right&lt;/code&gt; on the 100% in the bar, I should be using &lt;code&gt;text-align: end&lt;/code&gt;. And instead of &lt;code&gt;margin-right&lt;/code&gt; on the checkmarks, I should be using &lt;code&gt;margin-inline-end&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Lastly, the dismiss button is positioned using a &lt;code&gt;float: right&lt;/code&gt;. Instead of that, I should be using &lt;code&gt;float: inline-end&lt;/code&gt;. The "hide this block" image is a little more involved since the text is baked into the graphic. You would probably switch this out for a different image or decouple the arrow from the text and position them separately.&lt;/p&gt;

&lt;p&gt;All in all, the changes you would make here are mostly small and easy to test.&lt;/p&gt;

&lt;h3&gt;
  
  
  Changing the language
&lt;/h3&gt;

&lt;p&gt;While the &lt;code&gt;lang&lt;/code&gt; attribute tells you what language the page is in, it won't change how your page is being displayed.&lt;/p&gt;

&lt;p&gt;Browser APIs like the I18n API by default use the browser locale to determine how to format dates, numbers, and currencies. When you emulate the browser locale in Polypane it will update the places where that locale is being used. For example, here's an excerpt with a date from the Polypane blog in English and in Spanish:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjsbjqzrfek3hpuq89r1w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjsbjqzrfek3hpuq89r1w.png" alt="Two panes, one with an English date and one with a Spanish date." width="800" height="271"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To set the language and locale in Polypane you can use the dropdowns in the emulations dropdown. Polypane will autosuggest shortcodes as well as language names so that, for example, you don't have to remember that the language tag for Greenlandic is &lt;code&gt;kl&lt;/code&gt;. You type "greenlandic" and Polypane sorts the rest.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw7hm9j5jkcrf7kqd64up.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw7hm9j5jkcrf7kqd64up.png" alt="The emulations popup with a focused locale input. 'Gree' is typed into it and 'greek' and 'greenlandic' are provided as suggestions." width="800" height="569"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing your layout
&lt;/h2&gt;

&lt;p&gt;Changing the browser locale or the page &lt;code&gt;lang&lt;/code&gt; attribute doesn't change the text itself though. If you want to test how your layout behaves with different text lengths, you'll need to change the text itself.&lt;/p&gt;

&lt;p&gt;English text is often shorter than other languages also using the Latin script (like German), but longer than languages like Chinese or Japanese where the characters used often represent more than a single letter in latin script, and sometimes a single character can even be an entire word (For example, 母 is "mother" in Chinese).&lt;/p&gt;

&lt;h3&gt;
  
  
  Emulating text length
&lt;/h3&gt;

&lt;p&gt;Rather than doing a full-page translation, potentially in a language you can't read, you can use the &lt;a href="https://dev.to/docs/debug-tools/#content-chaos-test"&gt;Content Chaos feature&lt;/a&gt; in Polypane to replace all text on your page with random text. This way you can test how your layout behaves with different text lengths without translating the entire page.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Content Chaos testing is not just useful for testing different languages, but also for making sure that once the perfectly designed text is replaced with real content, your layout still holds up.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To check if your layout handles longer titles well, you can use the "More text" option:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F68e50b06zgr8bcxwfrz1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F68e50b06zgr8bcxwfrz1.png" alt="A page with long content." width="800" height="511"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see that the list of buttons on the left side responds well to more text: the content wraps and stays centered in the button, without having a weird line-height.&lt;/p&gt;

&lt;p&gt;The main content is overflowing though, and that's because the button is not constrained in width. I can fix this by adding a &lt;code&gt;max-width&lt;/code&gt; to the button, or by using &lt;code&gt;overflow-wrap: break-word&lt;/code&gt; on the text.&lt;/p&gt;

&lt;p&gt;If you want to test how your layout behaves with shorter text, you can use the "Less text" option:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkjs2ur19v7vju7e5vb8z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkjs2ur19v7vju7e5vb8z.png" alt="A page with short content." width="800" height="511"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here you can see that the layout is still working well. Even with little text the buttons are still easily clickable and the layout doesn't break. I don't have to fix anything here to accommodate for languages with shorter text.&lt;/p&gt;

&lt;h3&gt;
  
  
  Checking the line length
&lt;/h3&gt;

&lt;p&gt;Another thing to keep in mind is the number of characters in a line. While the specific recommendations differ quite widely between sources, the general consensus is that you want to keep the line length short enough that it's easy to read, but long enough that you're not wasting space: between 45 to 70 characters per line, or (roughly) 10 words for English. To check these values, highlight a line and right-click to &lt;a href="https://dev.to/docs/measure-text-length/"&gt;see the number of characters and words in that line&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0tfzxdfxv0azm5xp747p.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0tfzxdfxv0azm5xp747p.jpg" alt="Measure text length context menu" width="795" height="275"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This feature is aware of the language the page is in so will correctly detect real characters and words, as well as emoji and sentences.&lt;/p&gt;

&lt;p&gt;for CJK (Chinese, Japanese and Korean) characters you want to aim for roughly half the number of characters per line as you would for Latin characters. This is because CJK characters are about twice as wide as those in the Latin script. This advice comes from the &lt;a href="https://www.w3.org/WAI/WCAG21/Understanding/visual-presentation.html"&gt;WCAG 1.4.8 Visual Presentation Understanding page&lt;/a&gt;. Those are not part of WCAG itself, but meant to help you understand them.&lt;/p&gt;

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

&lt;p&gt;To check your layout and implementation for multiple languages, you'll want to check with different &lt;code&gt;dir&lt;/code&gt;, &lt;code&gt;lang&lt;/code&gt;, and browser locales. You can change these on an OS level, or emulate them in Polypane.&lt;/p&gt;

&lt;p&gt;Rather than translating your entire page into a language you don't understand, you can use the Content Chaos feature to test how your layout behaves with different text lengths and make sure there aren't any errors when the text is longer or shorter than you expected.&lt;/p&gt;

</description>
      <category>css</category>
      <category>webdev</category>
      <category>a11y</category>
      <category>i18n</category>
    </item>
    <item>
      <title>The gotchas of CSS Nesting</title>
      <dc:creator>Kilian Valkhof</dc:creator>
      <pubDate>Tue, 13 Jun 2023 10:53:40 +0000</pubDate>
      <link>https://dev.to/kilianvalkhof/the-gotchas-of-css-nesting-58m9</link>
      <guid>https://dev.to/kilianvalkhof/the-gotchas-of-css-nesting-58m9</guid>
      <description>&lt;p&gt;I've &lt;a href="https://kilianvalkhof.com/2021/css-html/css-nesting-specificity-and-you/"&gt;written before&lt;/a&gt; about the problems you can run into with CSS nesting (keep in mind that article uses an older syntax but the point still stands) and the question that &lt;a href="https://twitter.com/ChallengesCss/status/1668536462906621953"&gt;@ChallengeCSS&lt;/a&gt; tweeted out today made me realize there's actually a few more gotcha's. Here's what &lt;a href="https://twitter.com/ChallengesCss/status/1668536462906621953"&gt;they tweeted&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Everyone is exited about CSS Nesting but are you ready for it? Answer the below quiz 👇&lt;/p&gt;

&lt;p&gt;What would be the result of the following code (without cheating! 😈)&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;body {
 @​media all {
   background: red;
 }
 background: blue;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;Take a moment to come up with your own answer (or vote), then read on.&lt;/p&gt;

&lt;p&gt;Now, I initially got it wrong. Here was my thinking pattern:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; @media doesn't add specificity, so both declarations have a specificity of 0,0,1&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;background: blue&lt;/code&gt; comes later, so it wins&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;But no, the background is red! It turns out that has to do with the way browsers transform your nested CSS rules to individual rules it can apply. So lets dive into how browsers do that.&lt;/p&gt;

&lt;h3&gt;
  
  
  A related gotcha: &lt;code&gt;:is()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Last week was CSS Day (which was &lt;em&gt;amazing&lt;/em&gt;) and of course a bunch of the presentations mentioned CSS Nesting. Unfortunately, some had a simplified explanation of how rules get resolved. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;&amp;amp;&lt;/code&gt; in nested CSS isn't just replaced by the ancestor, which is what you might think, but it's ancestor is also wrapped in &lt;code&gt;:is()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;&amp;amp;&lt;/span&gt; &lt;span class="err"&gt;div&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
        &lt;span class="err"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* Doesn't become this: */&lt;/span&gt;
&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="err"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* It becomes this: */&lt;/span&gt;
&lt;span class="nd"&gt;:is&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="err"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that doesn't sound like there is much of a difference between &lt;code&gt;body div&lt;/code&gt; and &lt;code&gt;:is(body) div&lt;/code&gt;, indeed both have a specificity of 0,0,2, but remember that&lt;code&gt;:is()&lt;/code&gt; takes on the highest specificity of the selectors in it. So when you have the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;#intro&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;&amp;amp;&lt;/span&gt; &lt;span class="err"&gt;div&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
        &lt;span class="err"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The resulting selector, even when targeting a &lt;code&gt;div&lt;/code&gt; in &lt;code&gt;main&lt;/code&gt;, ends up as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:is&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;#intro&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="err"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which makes it go from 0,0,1 for &lt;code&gt;main div&lt;/code&gt; to 1,0,1 making it vastly more specific. That gotcha gets lost when examples fail to include the way ancestors are wrapped in &lt;code&gt;:is()&lt;/code&gt; (and yes, they also nest :&lt;code&gt;is()&lt;/code&gt;!)&lt;/p&gt;

&lt;h2&gt;
  
  
  Back to the original gotcha
&lt;/h2&gt;

&lt;p&gt;So back to the challenge up top. You can intermingle properties and nesting. You shouldn't to keep your code readable, but the following CSS works just fine and applies all the styling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;blur&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="err"&gt;@media&lt;/span&gt; &lt;span class="err"&gt;all&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;red&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nt"&gt;background&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;blue&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;deeppink&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nt"&gt;rotate&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;20&lt;/span&gt;&lt;span class="nt"&gt;deg&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's when the browser parses this CSS into individual rules that the sneaky thing happens:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The browser adds a new ruleset, &lt;code&gt;body&lt;/code&gt;, and starts adding the properties to it/&lt;/li&gt;
&lt;li&gt;The browser then adds another new ruleset for the nested media query and starts adding its properties to it/&lt;/li&gt;
&lt;li&gt;When it exits the nested media query, it adds the rest of the properties to the original ruleset again until that is exited.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So if we look at this CSS again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;@media&lt;/span&gt; &lt;span class="err"&gt;all&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;red&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;background&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;blue&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That actually resolves to these two rules in this specific order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;:is&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;red&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now from this CSS, it makes more sense that red wins. While it has the same specificity, it comes after the first rule so it wins. And that's the gotcha.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post originally claimed that only &lt;code&gt;@-rules&lt;/code&gt; could be intermingled with properties but this was incorrect. Thanks &lt;a href="https://twitter.com/ChallengesCss/status/1668577684585164801"&gt;@ChallengesCSS&lt;/a&gt; for correcting me.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>css</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>:root isn't global</title>
      <dc:creator>Kilian Valkhof</dc:creator>
      <pubDate>Wed, 03 May 2023 09:50:47 +0000</pubDate>
      <link>https://dev.to/kilianvalkhof/root-isnt-global-2km8</link>
      <guid>https://dev.to/kilianvalkhof/root-isnt-global-2km8</guid>
      <description>&lt;p&gt;Most developers prefer to keep all their CSS custom properties in one place, and a pattern that has emerged in recent years is to put those on &lt;code&gt;:root&lt;/code&gt;, a pseudo-element that targets the topmost element in your document (so that's always &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; on web pages). But just because they're in one place and in the topmost element, it doesn't mean they're global.&lt;/p&gt;

&lt;p&gt;I first encountered this issue with &lt;code&gt;::backdrop&lt;/code&gt;: &lt;a href="https://kilianvalkhof.com/2023/css-html/backdrop-doesnt-inherit-from-anywhere/"&gt;Backdrop doesn't inherit from anywhere&lt;/a&gt; but after a recent rendering engine update to &lt;a href="https://polypane.app"&gt;Polypane&lt;/a&gt; I noticed that all my custom selection colors (also powered by CSS custom properties) &lt;a href="https://twitter.com/kilianvalkhof/status/1649056013495205903"&gt;suddenly stopped working&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Turns out, &lt;code&gt;::selection&lt;/code&gt; is also not supposed to inherit styles, and Chromium 111+ is running an experiment to see what effect changing that has. Polypane runs with experimental features turned on, and so my selection styles became broken.&lt;/p&gt;

&lt;p&gt;This is going to catch a lot of people off-guard because I, like many others, expect CSS Custom properties defined on &lt;code&gt;:root&lt;/code&gt; to just be available everywhere. I guess I would also expect &lt;code&gt;::selection&lt;/code&gt; and &lt;code&gt;::backdrop&lt;/code&gt; to inherit from their "parent" element to allow more dynamic styling, but the spec writes apparently don't want this.&lt;/p&gt;

&lt;h2&gt;
  
  
  So if &lt;code&gt;:root&lt;/code&gt; isn't global, what is?
&lt;/h2&gt;

&lt;p&gt;Well, the jury's still out. &lt;/p&gt;

&lt;p&gt;Discussions are happening in this GitHub issue: &lt;a href="https://github.com/w3c/csswg-drafts/issues/6641"&gt;Custom properties on :root&lt;/a&gt; with a few options being discussed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use @property with an initial value (not cross-browser supported yet, only uses the initial value).&lt;/li&gt;
&lt;li&gt;Make &lt;code&gt;:root&lt;/code&gt; special.&lt;/li&gt;
&lt;li&gt;Create a new &lt;code&gt;:document&lt;/code&gt; pseudo-element that does propagate custom properties.&lt;/li&gt;
&lt;li&gt;Create an new at-rule called &lt;code&gt;@global&lt;/code&gt;, &lt;code&gt;@root&lt;/code&gt; or &lt;code&gt;@document&lt;/code&gt;  that you could define custom properties in.&lt;/li&gt;
&lt;li&gt;Make &lt;code&gt;::selection&lt;/code&gt; etc inherit from their originating element (e.g. "their parent").&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last item would solve both the problems people run into (it not inheriting, and it potentially inheriting directly from &lt;code&gt;:root&lt;/code&gt; so you can't overwrite custom properties in the cascade). I hope spec writers choose to do this regardless. &lt;/p&gt;

&lt;p&gt;Specifically, I want/expect this to work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;--selection-bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#0f0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="err"&gt;&amp;amp;::selection&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--selection-bg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When it comes to "a place to store global variables" I have no strong opinion, though I think it's interesting to keep in mind that in JavaScript there is now &lt;code&gt;window&lt;/code&gt;, &lt;code&gt;global&lt;/code&gt; and &lt;code&gt;globalThis&lt;/code&gt; because the naming across contexts didn't work. &lt;/p&gt;

&lt;p&gt;In that light, &lt;code&gt;:document&lt;/code&gt; or &lt;code&gt;@document&lt;/code&gt; seem potentially problematic. For that reason, I like &lt;code&gt;@global&lt;/code&gt; or &lt;code&gt;:global&lt;/code&gt; (I haven't actually seen global as a pseudo-element suggested yet, but it seems to be closest to how people expect things to work now). &lt;/p&gt;

&lt;p&gt;In the mean time, you can use the suggestion I made in my &lt;code&gt;::backdrop&lt;/code&gt; post and replace &lt;code&gt;:root&lt;/code&gt; with &lt;code&gt;:root ::backdrop ::selection&lt;/code&gt;. Sorry about that.&lt;/p&gt;

</description>
      <category>css</category>
      <category>webdev</category>
    </item>
    <item>
      <title>A small JavaScript pattern I enjoy using</title>
      <dc:creator>Kilian Valkhof</dc:creator>
      <pubDate>Thu, 06 Apr 2023 12:06:32 +0000</pubDate>
      <link>https://dev.to/kilianvalkhof/a-small-javascript-pattern-i-enjoy-using-1i0j</link>
      <guid>https://dev.to/kilianvalkhof/a-small-javascript-pattern-i-enjoy-using-1i0j</guid>
      <description>&lt;p&gt;There is a JavaScript pattern that I enjoy using that I don’t see a lot around the web, so I figured it would be worth sharing.&lt;/p&gt;

&lt;p&gt;When you have a piece of business logic, frequently that depends on a a certain value being true. An if statement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;active&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="err"&gt;…&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is easy to parse for me and I can quickly see what it does. What often ends up happening though is that the business logic gets a second possibility, for example status being active or status being trialing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;active&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;trialing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="err"&gt;…&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is still relatively easy to read but there’s repetition with ‘status’ and I always need a second to think about the difference between || and &amp;amp;&amp;amp; to understand the behavior. So whenever I move from checking from one value to more than one, I switch to a different pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;active&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;trialing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="err"&gt;…&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I make an array of all possible values and then use Array.includes() to check if the status is contained in that array.&lt;/p&gt;

&lt;p&gt;This pattern comfortably grows to many items and can also more easily be read aloud, helping understanding.&lt;/p&gt;

&lt;p&gt;There is no repetition so as a small bonus it’s shorter. It has also helped me learn the difference between &lt;a href="https://kilianvalkhof.com/2021/javascript/includes-contains-or-has-finding-things-in-iterables-lists-in-javascript/"&gt;includes, contains and has&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Partial string matching
&lt;/h2&gt;

&lt;p&gt;I get a lot of mileage out of the above pattern, but it only works when you’re matching the full string. Sometimes you need to check just the beginning or the end of a string and then .includes() won’t cut it because it only accepts values, not a function.&lt;/p&gt;

&lt;p&gt;We don’t want to go back to multiple checks and the repetition that gives so if we need to check for a part of the string we need to change the function we use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;file://&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;www.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;clipboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="err"&gt;…&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Array.some() takes a function and returns a Boolean. It’s a little longer but we’re still not repeating ourselves. A nice benefit is that like includes() it will stop evaluating when it returns true, so we usually don’t have to loop over all the elements.&lt;/p&gt;

&lt;p&gt;In the examples above I inlined the array but of course you can also store it in a global variable. That gives you the additional benefit of only instantiating a single array that you can reuse and that lets you update many checks in one go, should the business requirements change.&lt;/p&gt;

&lt;p&gt;As I said, I get a lot of use out of this pattern. I hope this helps you recognize when it’ll be useful in your code in the future!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>functional</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>The breakpoints we tested in 2021, and the ones to test in 2022</title>
      <dc:creator>Kilian Valkhof</dc:creator>
      <pubDate>Wed, 19 Jan 2022 16:23:26 +0000</pubDate>
      <link>https://dev.to/polypane/the-breakpoints-we-tested-in-2021-and-the-ones-to-test-in-2022-2mll</link>
      <guid>https://dev.to/polypane/the-breakpoints-we-tested-in-2021-and-the-ones-to-test-in-2022-2mll</guid>
      <description>&lt;p&gt;Which screen sizes to design, build and test on is a perennial topic in web development. While well-built responsive websites will work at any size, during development it's easier to use a small number of sizes to check with (even in Polypane!)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But what sizes to use?&lt;/strong&gt; There's a number of different approaches we'll go over in this article, all updated to be relevant in 2022.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Base it off device groups: mobile, tablet, laptop and desktop.&lt;/li&gt;
&lt;li&gt;Use the breakpoints from &lt;a href="https://polypane.app/blog/css-breakpoints-used-by-popular-css-frameworks/"&gt;popular CSS frameworks&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Using &lt;a href="https://polypane.app/create-workspace/"&gt;the dimensions your visitors use&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Using the most used breakpoints of 2021.&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  Based on device sizes
&lt;/h3&gt;

&lt;p&gt;There's mobile phones, tablets, laptops and desktops, so if you use size for each of that category, you cover them:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Width&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Mobile&lt;/td&gt;
&lt;td&gt;375px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tablet&lt;/td&gt;
&lt;td&gt;768px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Laptop&lt;/td&gt;
&lt;td&gt;1280px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Desktop&lt;/td&gt;
&lt;td&gt;1920px&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This gives a good overview, but you run the risk of &lt;a href="https://polypane.app/blog/overlooked-breakpoints-in-responsive-design/"&gt;missing the in-between sizes&lt;/a&gt;, smaller than 375px and between 900 and 1000px wide.&lt;/p&gt;

&lt;p&gt;Both of those are often forgotten, but still see quite a bit of real-life usage. The first one in older mobile phones, and the second one on tablets, laptops and desktops in unmaximized browser windows.&lt;/p&gt;




&lt;h3&gt;
  
  
  Based on popular CSS frameworks
&lt;/h3&gt;

&lt;p&gt;We've written about &lt;a href="https://polypane.app/blog/css-breakpoints-used-by-popular-css-frameworks/"&gt;the breakpoints that popular CSS frameworks use&lt;/a&gt; before. These are excellent when you're also using the rest of the framework, or as a starting-off point. For example, here's the breakpoints in Tailwind CSS 3.0:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Width&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;sm&lt;/td&gt;
&lt;td&gt;640px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;md&lt;/td&gt;
&lt;td&gt;768px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;lg&lt;/td&gt;
&lt;td&gt;1024px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;xl&lt;/td&gt;
&lt;td&gt;1280px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2xl&lt;/td&gt;
&lt;td&gt;1536px&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This gives a nice spread, though with a relatively high starting point you run the risk of having to do extra work after implementing the design to make sure everything fit on mobile devices.&lt;/p&gt;




&lt;h3&gt;
  
  
  Based on your visitor data
&lt;/h3&gt;

&lt;p&gt;If you use Google Analytics, your visitors browser dimensions are stored as well. This means you can retrieve them from the Google Analytics dashboard and use those to test on.&lt;/p&gt;

&lt;p&gt;For example, here's the 5 most used dimension on this site:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Width&lt;/th&gt;
&lt;th&gt;Height&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;4.55%&lt;/td&gt;
&lt;td&gt;1900px&lt;/td&gt;
&lt;td&gt;940px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2.13%&lt;/td&gt;
&lt;td&gt;1900px&lt;/td&gt;
&lt;td&gt;970px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2.02%&lt;/td&gt;
&lt;td&gt;1520px&lt;/td&gt;
&lt;td&gt;750px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.99%&lt;/td&gt;
&lt;td&gt;1350px&lt;/td&gt;
&lt;td&gt;660px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.95%&lt;/td&gt;
&lt;td&gt;1350px&lt;/td&gt;
&lt;td&gt;630px&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;While you can use the above it's important to realize that &lt;strong&gt;our&lt;/strong&gt; audience probably isn't &lt;strong&gt;your&lt;/strong&gt; audience. Polypane is a tool for web developers, so the vast majority of our visitors are viewing our site on a desktop device, and fairly large ones at that.&lt;/p&gt;

&lt;p&gt;Notice too that none of the most popular sizes here map to the traditional device widths mentioned above. Every audience is different, and it's best to cater to yours.&lt;/p&gt;

&lt;h4&gt;
  
  
  Create a Polypane workspace based on Google Analytics
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;You can also use &lt;a href="https://polypane.app/create-workspace/"&gt;our GA workspace creator&lt;/a&gt; to automate the process, select how many panes you want and how to sort them, and then opening them in Polypane with a single click.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  The breakpoints we tested in 2021
&lt;/h3&gt;

&lt;p&gt;That leaves us with the last method: using the breakpoints that have been used the most in 2021. For that we can use two sources: the Web Almanac and Polypane.&lt;/p&gt;




&lt;h4&gt;
  
  
  From the Web Almanac
&lt;/h4&gt;

&lt;p&gt;The &lt;a href="https://almanac.httparchive.org/en/2021/"&gt;Web Almanac&lt;/a&gt; analysed 8.2 million websites, old and new, to make an overview of the most used web features, including breakpoints. That also includes all the sites in the web almanacs testing data that existed (well) before this year.&lt;/p&gt;

&lt;p&gt;It has &lt;a href="https://almanac.httparchive.org/en/2021/css"&gt;a chapter on CSS&lt;/a&gt; that includes a list of &lt;a href="https://almanac.httparchive.org/en/2021/css#common-breakpoints"&gt;common breakpoints&lt;/a&gt;. It's worth reading the analysis: they make a split between &lt;code&gt;min-width&lt;/code&gt; and &lt;code&gt;max-width&lt;/code&gt; media queries because you can see a clear difference in sizes used (&lt;code&gt;min-width&lt;/code&gt; is more often used for &lt;code&gt;767px&lt;/code&gt; while &lt;code&gt;max-width&lt;/code&gt; for &lt;code&gt;768px&lt;/code&gt;, for example). We're splitting those out in the overview below as well, as they give a nice contrast.&lt;/p&gt;

&lt;h4&gt;
  
  
  Min-width
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Width&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;57%&lt;/td&gt;
&lt;td&gt;768px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;44%&lt;/td&gt;
&lt;td&gt;1200px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;40%&lt;/td&gt;
&lt;td&gt;992px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;31%&lt;/td&gt;
&lt;td&gt;600px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25%&lt;/td&gt;
&lt;td&gt;782px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;24%&lt;/td&gt;
&lt;td&gt;480px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16%&lt;/td&gt;
&lt;td&gt;1024px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9%&lt;/td&gt;
&lt;td&gt;767px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8%&lt;/td&gt;
&lt;td&gt;800px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3%&lt;/td&gt;
&lt;td&gt;991px&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  Max-width
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Width&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;52%&lt;/td&gt;
&lt;td&gt;767px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;38%&lt;/td&gt;
&lt;td&gt;600px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;38%&lt;/td&gt;
&lt;td&gt;768px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;36%&lt;/td&gt;
&lt;td&gt;480px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;30%&lt;/td&gt;
&lt;td&gt;991px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;27%&lt;/td&gt;
&lt;td&gt;1024px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;26%&lt;/td&gt;
&lt;td&gt;800px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18%&lt;/td&gt;
&lt;td&gt;1200px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13%&lt;/td&gt;
&lt;td&gt;992px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;td&gt;782px&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When it comes to the difference between &lt;code&gt;min-width&lt;/code&gt; and &lt;code&gt;max-width&lt;/code&gt;, apart from the 1px differences here and there, is&lt;br&gt;
that the spread for &lt;code&gt;max-width&lt;/code&gt; is much more diverse. For &lt;code&gt;min-width&lt;/code&gt;, the top 10 goes from 57% usage to 3%, while max-width starts lower, at 52%, and ends higher, at 10%.&lt;/p&gt;

&lt;p&gt;Min-widths means &lt;a href="https://polypane.app/blog/responsive-design-ground-rules/#rule-2-mobile-first"&gt;you're &lt;em&gt;adding&lt;/em&gt; styling as you get wider&lt;/a&gt;, while max-widths usually disable styling as they get smaller. Because of this, you frequently need less min-widths, and your styling works for a longer time before you need a new breakpoint.&lt;/p&gt;




&lt;h4&gt;
  
  
  The most-used breakpoints in Polypane in 2021
&lt;/h4&gt;

&lt;p&gt;We can also look at the most used pane sizes in Polypane in 2021. The sizes that &lt;strong&gt;real developers have used the most in the past year&lt;/strong&gt; to develop websites with.&lt;/p&gt;

&lt;p&gt;This gives the most accurate look at what sizes modern sites are optimized for. Here is the top 10 for 2021:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Width&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;5.48%&lt;/td&gt;
&lt;td&gt;320px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4.49%&lt;/td&gt;
&lt;td&gt;1280px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3.41%&lt;/td&gt;
&lt;td&gt;768px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2.41%&lt;/td&gt;
&lt;td&gt;1920px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2.19%&lt;/td&gt;
&lt;td&gt;500px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.89%&lt;/td&gt;
&lt;td&gt;568px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.86%&lt;/td&gt;
&lt;td&gt;800px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.81%&lt;/td&gt;
&lt;td&gt;375px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.69%&lt;/td&gt;
&lt;td&gt;1024px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.25%&lt;/td&gt;
&lt;td&gt;1440px&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If we look at these sizes we see a nice spread between device sizes, breakpoints from CSS Frameworks and the many &lt;a href="https://polypane.app/docs/workspace-management/"&gt;presets&lt;/a&gt; that Polypane ships with.&lt;/p&gt;




&lt;h3&gt;
  
  
  The breakpoints to develop on in 2022
&lt;/h3&gt;

&lt;p&gt;As you can see from the relatively low percentages, there are thousands of other sizes being used in Polypane. There is no single right set of dimensions.&lt;/p&gt;

&lt;p&gt;So the best answer? Pick a set you like and adapt it. Any of the ones on this page are a good choice.&lt;/p&gt;

&lt;p&gt;If you notice you often make errors at a size you're not currently developing for, make sure to add it. And if you find yourself fixing the same issue at multiple sizes, maybe you can hide one of them during development.&lt;/p&gt;

&lt;p&gt;Regardless of which default sizes you choose to develop on, &lt;strong&gt;it's best to see all of them in one overview.&lt;/strong&gt; That's what Polypane lets you do. All the other parts of your browser are synced as well: your interactions happen in all panes, our elements panel lets you edit and inspect elements in all&lt;br&gt;
panes at the same time, and our console intelligently combines messages from each pane.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://polypane.app"&gt;Polypane has a free 14 trial available&lt;/a&gt;. Check it out!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>css</category>
      <category>ux</category>
      <category>beginners</category>
    </item>
    <item>
      <title>The EyeDropper API: Pick colors from anywhere on your screen</title>
      <dc:creator>Kilian Valkhof</dc:creator>
      <pubDate>Thu, 11 Nov 2021 13:52:32 +0000</pubDate>
      <link>https://dev.to/polypane/the-eyedropper-api-pick-colors-from-anywhere-on-your-screen-21o2</link>
      <guid>https://dev.to/polypane/the-eyedropper-api-pick-colors-from-anywhere-on-your-screen-21o2</guid>
      <description>&lt;p&gt;With the new EyeDropper API in Chromium, websites can let visitors pick colors from anywhere on their screen, adding another&lt;br&gt;
feature to the web that used to require hacky solutions and is now just a few lines of code.  The API is&lt;br&gt;
clean and modern and easy to use. In this article we'll discuss how to set it up, handle edge cases and additional features&lt;br&gt;
we hope will land in future updates.&lt;/p&gt;

&lt;p&gt;We've been following the EyeDropper API since it was first proposed and have been experimenting with it as different&lt;br&gt;
parts became available as well as providing input while the feature was being developed. In &lt;a href="https://dev.to/blog/polypane-7"&gt;Polypane 7&lt;/a&gt;&lt;br&gt;
we started using it extensively for the new color picker and new palettes.&lt;/p&gt;
&lt;h2&gt;
  
  
  How to use the EyeDropper API
&lt;/h2&gt;

&lt;p&gt;The API adds a new global, &lt;code&gt;EyeDropper&lt;/code&gt; (or &lt;code&gt;window.EyeDropper&lt;/code&gt;) that you can use to set up a new eyedropper object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;eyeDropper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;EyeDropper&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This eyeDropper object has one function, &lt;code&gt;eyeDropper.open()&lt;/code&gt;. This starts the color picker and changes the users cursor&lt;br&gt;
into a color picker, complete with magnified area and a highlighted pixel. This function returns a promise, so you can&lt;br&gt;
use it either with &lt;code&gt;await&lt;/code&gt; or as a promise.&lt;/p&gt;

&lt;p&gt;One gotcha is that it only works when called from &lt;strong&gt;a user-initiated event&lt;/strong&gt;. This is part of the security model, to&lt;br&gt;
prevent websites from potentially scraping pixels without the user knowing.&lt;/p&gt;
&lt;h3&gt;
  
  
  Detecting support for the EyeDropper API
&lt;/h3&gt;

&lt;p&gt;Because the API is only available in Chromium you will need to check for support before using it. The most straightforward&lt;br&gt;
way to do that is to only offer your color picking UI when &lt;code&gt;window.EyeDropper&lt;/code&gt; is not undefined:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EyeDropper&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Okay to use EyeDropper&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Hide the UI&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;await&lt;/code&gt; based version
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// won't work&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;eyeDropper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// works&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queryselector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.colorbutton&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;eyeDropper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;eyeDropper.open()&lt;/code&gt; call will resolve in two situations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The user clicks anywhere on the screen.&lt;/li&gt;
&lt;li&gt;The user pressed the Esc key.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the last situation the eyeDropper will throw an exception, but in the first situation you will get a &lt;code&gt;ColorSelectionResult&lt;/code&gt;&lt;br&gt;
object, which has an &lt;code&gt;sRGBHex&lt;/code&gt; property containing the picked color in hexadecimal format. In your code you can check if&lt;br&gt;
&lt;code&gt;result.sRGBHex&lt;/code&gt; is defined and then do with it what you want.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queryselector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.colorbutton&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;eyeDropper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sRGBHex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sRGBHex&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You don't &lt;em&gt;have&lt;/em&gt; to handle the exception but if you wanted to provide the user feedback that they cancelled the eyedropper,&lt;br&gt;
you need to add a &lt;code&gt;try .. catch&lt;/code&gt; to the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queryselector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.colorbutton&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;eyeDropper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sRGBHex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sRGBHex&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="c1"&gt;// "DOMException: The user canceled the selection."&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Promise based version
&lt;/h3&gt;

&lt;p&gt;You don't have to use the &lt;code&gt;await&lt;/code&gt; version. &lt;code&gt;eyeDropper.open()&lt;/code&gt; returns a promise, so adding a &lt;code&gt;.then()&lt;/code&gt; and &lt;code&gt;.catch()&lt;/code&gt; also works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queryselector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.colorbutton&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;eyeDropper&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sRGBHex&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// "DOMException: The user canceled the selection."&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Things to keep in mind when using the EyeDropper API
&lt;/h2&gt;

&lt;p&gt;There are two gotchas with the API, at least as it's currently implemented in Chromium that we've found that you should&lt;br&gt;
be aware of.&lt;/p&gt;

&lt;h3&gt;
  
  
  Color picking does not use the live screen
&lt;/h3&gt;

&lt;p&gt;At least in the current implementation, the color picker get the pixels as shown on the screen when you call &lt;code&gt;.open()&lt;/code&gt;.&lt;br&gt;
This means that if you're playing video the color picker will show the pixels of the frame that was visible then, not the&lt;br&gt;
live video.&lt;/p&gt;

&lt;p&gt;This is dependent on the implementation and we hope a future update of Chromium will allow for live data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Color picking only works as the result of a user action
&lt;/h3&gt;

&lt;p&gt;As mentioned earlier you need a user initiated event to open the eye dropper. This is to prevent sites from opening the&lt;br&gt;
eyedropper UI to start scraping your screen right on load. Instead the user needs to perform an action for the API to work,&lt;br&gt;
like a click or keypress.&lt;/p&gt;

&lt;h2&gt;
  
  
  Features we want to see added
&lt;/h2&gt;

&lt;p&gt;The EyeDropper API is still very young and minimal. During our implementation we encountered a number of features that we&lt;br&gt;
would like to see added to the API in future updates.&lt;/p&gt;

&lt;h3&gt;
  
  
  Live preview of the hovered color
&lt;/h3&gt;

&lt;p&gt;A major component of many eye droppers, like those in design tools, is that they also show a preview swatch of the&lt;br&gt;
hovered color. You can use this to compare it to another swatch or quickly check a HEX code. The current API does not&lt;br&gt;
offer this over security concerns. We have filed an issue against the EyeDropper API on GitHub for this: &lt;a href="https://github.com/WICG/eyedropper-api/issues/6"&gt;#6 Live feedback is needed&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  A more extensive color model
&lt;/h3&gt;

&lt;p&gt;Currently, all colors are returned in the sRGB color model. This means the API won't accurately return colors outside&lt;br&gt;
the sRGB spectrum, for example those on Apple's P3 screens. How to deal with this is &lt;a href="https://github.com/WICG/eyedropper-api/issues/3"&gt;an open issue&lt;/a&gt;.&lt;br&gt;
Work is also happening on a &lt;a href="https://github.com/WICG/color-api"&gt;new Color API for the web&lt;/a&gt;. The EyeDropper API could use&lt;br&gt;
this Color API when it lands in future versions of browsers.&lt;/p&gt;

&lt;h3&gt;
  
  
  A more natural way to select multiple colors
&lt;/h3&gt;

&lt;p&gt;Because of the current security model, each time a user picks a color they need to re-initiate a user action which can be tedious.&lt;br&gt;
For example if you want to create a palette of colors in one go, you want to start picking colors, click on all the colors you&lt;br&gt;
want to add and then close out of the eye dropper. We similarly filed an issue for this on Github: &lt;a href="https://github.com/WICG/eyedropper-api/issues/9"&gt;#9 Do we expect multiselect to work?&lt;/a&gt; and this feature is currently being considered.&lt;/p&gt;

&lt;p&gt;For this it would be nice if we could designate a part of the page (like a button) as an area where the EyeDropper&lt;br&gt;
doesn't work, that instead functions as a "done" button. This way users can select multiple colors and then click that&lt;br&gt;
button when they're done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other browsers
&lt;/h2&gt;

&lt;p&gt;For now, the API is only available in Chromium based browsers from version 95 on and there has not been a signal from&lt;br&gt;
Safari and Firefox yet. If you want those browsers to support the EyeDropper API as well, add your support to the open issues:&lt;br&gt;
&lt;a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1728527"&gt;Issue #1728527 for Firefox&lt;/a&gt; and &lt;a href="https://bugs.webkit.org/show_bug.cgi?id=229755"&gt;Issue #229755 for Safari&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The EyeDropper API is a nice addition to the browser that we hope to see land in more browsers. We make good use of it&lt;br&gt;
in Polypane and would like to see it be developed further.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>ux</category>
    </item>
    <item>
      <title>Polypane 6.2: HTML validation, robots.txt support, RTL emulation and more</title>
      <dc:creator>Kilian Valkhof</dc:creator>
      <pubDate>Mon, 26 Jul 2021 13:48:32 +0000</pubDate>
      <link>https://dev.to/polypane/polypane-6-2-html-validation-robots-txt-support-rtl-emulation-and-more-375a</link>
      <guid>https://dev.to/polypane/polypane-6-2-html-validation-robots-txt-support-rtl-emulation-and-more-375a</guid>
      <description>&lt;p&gt;In Polypane 6.2 we focused on improving the app performance and consistency, particularly around updating, pane resizing, tab handling and dark mode.&lt;/p&gt;

&lt;p&gt;We still added a fair few new features, including some that many of you have been requesting. Here's everything new in Polypane 6.2:&lt;/p&gt;

&lt;h2&gt;
  
  
  HTML validation
&lt;/h2&gt;

&lt;p&gt;The source panel in Polypane now automatically validates your HTML for you. A lot of you have been asking for this, and now it's here!&lt;/p&gt;

&lt;p&gt;The HTML validation in Polypane is 100% local so you're not uploading your HTML source (potentially with user data!) to an online validator, and we check the generated source (which the source panel also displays) so you can check the HTML generated by frameworks as well.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--362evnZE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://polypane.app/blogs/polypane-6-2/htmlvalidation.png" class="article-body-image-wrapper"&gt;&lt;img alt="HTML validation of this page" src="https://res.cloudinary.com/practicaldev/image/fetch/s--362evnZE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://polypane.app/blogs/polypane-6-2/htmlvalidation.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For any of the issues you can hover over the element to highlight the elements in all panes. The source panel itself will also scroll to the issue in the HTML and highlight it. Clicking the element takes you to the Elements panel so you can fix the issue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Toybox systems integration
&lt;/h2&gt;

&lt;p&gt;We've been in contact with the folks that built &lt;a href="https://www.toyboxsystems.com/"&gt;Toybox systems&lt;/a&gt; for a while now and a few months ago we set out to create an integration between Toybox Systems and Polypane. Today, we release it!&lt;/p&gt;

&lt;p&gt;Toybox System is a bug reporting tool that makes it really easy to take a screenshot of a part of the page and then share a message with your team. The integration in Polypane lets you create screenshots, drop pins and leave comments and inspect everyone else's comments right inside a pane.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ojhKM67u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://polypane.app/blogs/polypane-6-2/toybox.png" class="article-body-image-wrapper"&gt;&lt;img alt="Toybox Systems overview" src="https://res.cloudinary.com/practicaldev/image/fetch/s--ojhKM67u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://polypane.app/blogs/polypane-6-2/toybox.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We're very excited about this integration! It makes Polypane an even better tool for QA, since you find issues much faster with Polypane, and now report them super fast using Toybox Systems.&lt;/p&gt;

&lt;p&gt;This is the first of a few integrations we plan on releasing. If there are other tools you'd like to see integrated into Polypane, let us know!&lt;/p&gt;

&lt;h2&gt;
  
  
  Robots.txt support
&lt;/h2&gt;

&lt;p&gt;The Meta panel now shows your site's robots.txt file, ordered by user agent. If you have a robots meta tag, we also show that here so you get the full overview.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4rXLVU8X--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://polypane.app/blogs/polypane-6-2/robotstxt.png" class="article-body-image-wrapper"&gt;&lt;img alt="Robots.txt overview" src="https://res.cloudinary.com/practicaldev/image/fetch/s--4rXLVU8X--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://polypane.app/blogs/polypane-6-2/robotstxt.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Polypane automatically checks if the current page is blocked for each user agent and if you have settings that aren't supported by that user-agent.&lt;/p&gt;

&lt;p&gt;As with all other entries in the Meta panel, you can copy each Robots.txt declaration and we'll automatically format it correctly for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Other meta panel improvements
&lt;/h3&gt;

&lt;p&gt;The social media previews in the Meta panel have always had support for the light and dark themes of different social media (provided they had one, looking at you LinkedIn) but you had to switch the theme of the entire browser to see them. We've now added a toggle to the previews so you don't have to do that anymore.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FtE2UA5K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://polypane.app/blogs/polypane-6-2/toggle.png" class="article-body-image-wrapper"&gt;&lt;img alt="Toggle for social media previews" src="https://res.cloudinary.com/practicaldev/image/fetch/s--FtE2UA5K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://polypane.app/blogs/polypane-6-2/toggle.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We've also improved the accuracy of the Twitter, Facebook and Google previews.&lt;/p&gt;

&lt;p&gt;Now that Safari 15 is going to support multiple theme colors, Polypane has been updated to show these in a list together with their media value. Clicking the color preview now copies the color.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--P3B2-Wkz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://polypane.app/blogs/polypane-6-2/themecolor.png" class="article-body-image-wrapper"&gt;&lt;img alt="Robots.txt overview" src="https://res.cloudinary.com/practicaldev/image/fetch/s--P3B2-Wkz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://polypane.app/blogs/polypane-6-2/themecolor.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lastly, we've improved many of the checks and warnings in the Meta panel. We now warn about canonical urls that dont have the right format, viewports that limit the user, missing alt attributes and incorrect formatting for image URLs.&lt;/p&gt;

&lt;h2&gt;
  
  
  RTL emulation
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ighLjRoB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://polypane.app/blogs/polypane-6-2/rtl.png" class="article-body-image-wrapper"&gt;&lt;img alt="RTL emulation" src="https://res.cloudinary.com/practicaldev/image/fetch/s--ighLjRoB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://polypane.app/blogs/polypane-6-2/rtl.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most of the web is in English, a language that reads from left-to-right. But there are many languages that you read from right-to-left, like Arabic, Hebrew, Farsi and Urdu. Websites in these languages can be given an RTL (right-to-left) direction that tells the browser to render everything in that reading mode. It flips the text-alignment as well as moves the position of markers and other browser-native things.&lt;/p&gt;

&lt;p&gt;For people developing bidirectional websites, switching between LTR and RTL often involved editing the source in devtools, updating a cookie or updating the code elsewhere. With the new RTL emulation in Polypane, doing this takes a single click.&lt;/p&gt;

&lt;p&gt;Even if you don't create bidirectional websites, you can also use this to check if your logical properties are configured correctly. Properties like &lt;code&gt;margin-inline-end&lt;/code&gt; will automatically flip from the right-side to the left-side when switching from LTR to RTL rendering.&lt;/p&gt;

&lt;h2&gt;
  
  
  Message bus
&lt;/h2&gt;

&lt;p&gt;As a result of our work on supporting Web Components we've been working with a few beta testers to built a new system for developers to communicate between panes, the Polypane Message Bus.&lt;/p&gt;

&lt;p&gt;With this message bus you can send and handle messages between panes, letting you implement your own syncing logic where Polypane can't, like for canvas-based websites (like games) or web components with a closed root.&lt;/p&gt;

&lt;p&gt;The message bus is very lightweight while unlocking a whole new way for developers and QA to test their sites across viewports and emulated devices. We can't wait to see what you'll do with it!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oLmZo4Ec--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://polypane.app/blogs/polypane-6-2/polybus.jpg" class="article-body-image-wrapper"&gt;&lt;img alt="Polypane Message Bus examples" src="https://res.cloudinary.com/practicaldev/image/fetch/s--oLmZo4Ec--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://polypane.app/blogs/polypane-6-2/polybus.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Check out the &lt;a href="https://polypane.app/docs/message-bus/"&gt;documentation on the Message Bus&lt;/a&gt; for the API specification.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Rewritten update logic
&lt;/h3&gt;

&lt;p&gt;The updating logic in Polypane hasn't been touched in a fair few releases and had bugs that prevented some users from automatically updating. For 6.2 we completely rewrote our updating logic. If you experienced issues updating those should be gone from 6.2 onwards.&lt;/p&gt;

&lt;h3&gt;
  
  
  Disable CSP (Content-security-policy) headers
&lt;/h3&gt;

&lt;p&gt;Polypane does not touch CSP headers in default mode. Unfortunately that means that with very strict CSP settings, certain Polypane functionality is also blocked from running. If this happens on one of your sites, you can now disable CSP in the Edit menu.&lt;/p&gt;

&lt;h3&gt;
  
  
  New devices
&lt;/h3&gt;

&lt;p&gt;We added new Android devices (based on popularity) and a new UHD (4K) preset. If you're missing a device you want Polypane to emulate, please let us know!&lt;/p&gt;

&lt;h3&gt;
  
  
  Live reload improvements
&lt;/h3&gt;

&lt;p&gt;Live reload now automatically ignores dotfolders (it already ignored dotfiles) and will ignore any custom query parameters when hot reloading CSS files. This will cause updates to be more performant and CSS updates in particular to happen faster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Outline panel warnings
&lt;/h3&gt;

&lt;p&gt;We've greatly expanded the number and type of warnings we give in the outline panel, as well as add more support for outlines created with aria attributes.&lt;/p&gt;

&lt;p&gt;For the Landmarks overview, we'll now warn about missing-but-expected element, elements of which we expect just one but see multiple, and when landmarks are nested in other landmarks in an unexpected way.&lt;/p&gt;

&lt;p&gt;The Links overview now warns you about empty content or href attributes that point to using links for JS logic.&lt;/p&gt;

&lt;p&gt;The Focus order overview incorrectly ignored summary elements, but now reports them in the overview.&lt;/p&gt;

&lt;p&gt;For the Image overview we made the &lt;strong&gt;first step towards helping you create better alt text&lt;/strong&gt;. Polypane will now warn you when you use redundant language in your alt text, like "image of...".&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Of course after implementing that last feature, we promptly found (and fixed) an image on our own site with exactly that alt text!&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Color contrast checker
&lt;/h3&gt;

&lt;p&gt;We've improved the color contrast checker under the hood, making it more accurate and implementing an updated design that makes it easier to see which element is checked. We also fixed an issue where some sites didn't allow you to copy the suggestion on click.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quality-of-life improvements
&lt;/h3&gt;

&lt;p&gt;There's many other Quality-of-life improvements in this release. Interacting with tabs is now more similar to other browsers, the performance of pane resizing has drastically improved, you can right-click a single pane to reload just that pane, we improved the performance of animations across the application and much more.&lt;/p&gt;

&lt;p&gt;Check the changelog below for the full list of updates, and of course every new and improved feature has been added to &lt;a href="https://polypane.app/docs/"&gt;the documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Polypane 6.2
&lt;/h2&gt;

&lt;p&gt;Polypane is available for Windows, Mac (with versions for Intel and M1) and Linux (.deb or AppImage).&lt;/p&gt;

&lt;p&gt;Polypane automatically updates on Mac and Windows. Linux users need to download the new version from&lt;br&gt;
&lt;a href="https://dev.to/download/"&gt;the download page&lt;/a&gt;. You can find the Mac and Windows versions on that page too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't have Polypane yet? There is a 14 day trial available. &lt;a href="https://dashboard.polypane.app/register"&gt;Try it for free&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Full Changelog
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;New Features&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;New&lt;/strong&gt; HTML validation in the Source panel&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New&lt;/strong&gt; Toybox Systems integration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New&lt;/strong&gt; Robots.txt support in Meta panel&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New&lt;/strong&gt; RTL emulation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New&lt;/strong&gt; Disable CSP option&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New&lt;/strong&gt; New Samsung and Redmi devices and UHD presets&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New&lt;/strong&gt; Polypane Message Bus&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Improvements&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Rewritten update logic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Significant improvement to pane resize performance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Meta Panel previews can now be toggled between light and dark mode&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Meta panel warns about incorrectly formatted canonical urls&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Meta panel warns about viewports that limit scaling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Meta panel supports multiple theme colors and displays their media attribute&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Meta panel click a theme color to copy it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Meta panel checks for missing image alt text&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Meta panel checks image url formatting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Meta panel rendering of Twitter preview&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Meta panel rendering of Facebook preview&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Updated Electron&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Updated accessibility rules&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Updated Google Fonts list&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Live reload now ignored all dotfolders&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Live reload ignores query params when injecting CSS (Thanks Winston!)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Color contrast labels have updated design&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Color contrast checking can now detect contrast issues for floated elements&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Color contrast can now copy colors regardless of site settings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Source panel now includes doctype&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Outline panel Headings now includes aria-role headings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Outline panel Landmarks now warn about missing elements&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Outline panel Landmarks now warn about duplicate elements&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Outline panel Landmarks now warn about illegal nesting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Outline panel Links now warn about href or content being empty&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Outline panel Focus order now supports summary elements&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Outline panel Images warns about redundant text in the alt attribute&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Outline panel shows messages when no elements can be found&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; DOM Treemap devtools extension added (Thanks Christian!)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Support for web component syncing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Clearer active state for inspect button in dark mode (Thanks John!)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Add 'reload and 'reload this pane' options to context menu&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Elements panel now supports complex specificity calculations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Improve consistency of colors in dark mode UI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Pane load performance improvements&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Improve help text for undocked devtools in Devtools panel&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Animation performance across the app&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Double click or middle mouse click the tab bar to open a new tab&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Click sync for inputs in labels now syncs correct state&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Webvitals logic updated, CLS now live-updates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Hover tooltip now shows font weight (Thanks Stephan!)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved&lt;/strong&gt; Clarify wording in a11y panel (Thanks Roel!)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Fixes&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt; Middle mouse clicking a tab now closes it without first focusing it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt; Element normalization causing issues with Svelte updating (Thanks Richard!)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt; Overview screenshot on Windows had incorrect dimensions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt; 'open with Polypane' for HTML files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt; Touch emulation toggling applies immediately&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt; Outline Panel Focus elements now match other outlines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt; Design issue with viewport/device size toggle&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt; CSS button in address bar opened the wrong panel&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt; Aspect ratios no longer sort largest first&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt; Resolve syntax error when emulating a user agent&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt; Reset Window dimensions on launch if launching on different screen configuration (Thanks Claudia!)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt; Resolve issue in Outline panel when sites have images without SRC attribute&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt; Elements panel color editor can now set opacity again&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt; Elements panel height of add attribute form now correct&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt; 'nodeName of undefined' error message&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt; Meta panel now supports objects in oEmbed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt; opening URLs from browser extension on Linux&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt; Release notes overlay displays release notes again&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt; overflow issue in a11y panel with large text size (Thanks Hidde!)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt; Ctrl + needed shift to zoom in (Thanks Alex!)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>css</category>
      <category>javascript</category>
      <category>showdev</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Detecting media query support in CSS and JavaScript</title>
      <dc:creator>Kilian Valkhof</dc:creator>
      <pubDate>Tue, 13 Jul 2021 13:48:18 +0000</pubDate>
      <link>https://dev.to/kilianvalkhof/detecting-media-query-support-in-css-and-javascript-2ngk</link>
      <guid>https://dev.to/kilianvalkhof/detecting-media-query-support-in-css-and-javascript-2ngk</guid>
      <description>&lt;p&gt;Recently I needed a way to detect support for a media query in CSS and JavaScript. To detect if a browser supports a certain CSS feature, you can use &lt;code&gt;@supports () { ... }&lt;/code&gt;, but that doesn't work for media queries. In this article I'll show you how you can.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why I needed this
&lt;/h3&gt;

&lt;p&gt;For &lt;a href="https://kilianvalkhof.com/2021/accessibility/increasing-access-to-your-website-with-prefers-reduced-data/"&gt;a presentation I did on &lt;code&gt;prefers-reduced-data&lt;/code&gt;&lt;/a&gt; I wanted to apply something in one of two situations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There was no support for &lt;code&gt;prefers-reduced-data&lt;/code&gt; at all&lt;/li&gt;
&lt;li&gt;There was support for &lt;code&gt;prefers-reduced-data&lt;/code&gt; and the value was "no-preference".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For this, I couldn't use just &lt;code&gt;@media (prefers-reduced-data: no-preference)&lt;/code&gt; because that would be false if either there was no support (since the browser wouldn't understand the media query) or if it &lt;em&gt;was&lt;/em&gt; supported but the user wanted to preserve data.&lt;/p&gt;

&lt;p&gt;What I needed was a test for the media feature regardless of it's value. To do that, we can use &lt;a href="https://polypane.app/blog/the-complete-guide-to-css-media-queries/#the-or-operator--a-comma"&gt;the or notation&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Detecting media query support in CSS
&lt;/h3&gt;

&lt;p&gt;To detect if a media query is supported in CSS at all, you can use the following CSS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="n"&gt;not&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefers-reduced-data&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefers-reduced-data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That looks like a bit of weird, so lets dissect what it actually says. Firstly, let's split the two media features and begin with the second one:&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;(prefers-reduced-data)&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;This one looks straightforward but there's something weird: the media feature is missing a value! usually, media features come with a value, like "min-width: 400px", but this one doesn't have a value.&lt;/p&gt;

&lt;p&gt;That's because some media features have a "shorthand" when they only have two options and prefers-reduced-data does, it only has "no-preference" (off) and "reduce" (on). When you omit the value, it tests for it being on. &lt;/p&gt;

&lt;p&gt;So here's how this will resolve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no preference: false&lt;/li&gt;
&lt;li&gt;reduce: true&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But if the browser doesn't support a media feature, it will automatically change to "not all", which resolves to false, so we end with this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no support: false&lt;/li&gt;
&lt;li&gt;no preference: false&lt;/li&gt;
&lt;li&gt;reduce: true&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;not all and (prefers-reduced-data)&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;The notable thing here is &lt;code&gt;not all and&lt;/code&gt;. "all" is the default media type, and it applies to both &lt;code&gt;screen&lt;/code&gt; and &lt;code&gt;print&lt;/code&gt;. You can omit it (and likely you usually do), but if you add it you need to add "and" in between it and the media &lt;em&gt;feature&lt;/em&gt;  (which is the part between parentheses). &lt;/p&gt;

&lt;p&gt;&lt;code&gt;not&lt;/code&gt; is how you can negate a media query. For example, &lt;code&gt;@media not print {...}&lt;/code&gt; would apply everywhere except print.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;all&lt;/code&gt; being the default, what we're really checking here for is "not (prefers-reduced-data)". Unfortunately that's invalid notation until supports for Media Queries level 4 lands, so we need to add the "all and" here.&lt;/p&gt;

&lt;p&gt;Here's how this resolves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no support: still false, since the browser doesn't understand it&lt;/li&gt;
&lt;li&gt;support but off: true (its the negation of it being on)&lt;/li&gt;
&lt;li&gt;support but on: false&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Combined
&lt;/h4&gt;

&lt;p&gt;So when the browser then recombined these values using the OR, meaning only one of them has to be true for the media declaration to be applied:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No support&lt;/strong&gt;: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;not all and (prefers-reduced-data)&lt;/code&gt;: false&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;(prefers-reduced-data)&lt;/code&gt;: false&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Combined: false&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Support, but off&lt;/strong&gt;: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;not all and (prefers-reduced-data)&lt;/code&gt;: true&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;(prefers-reduced-data)&lt;/code&gt;: false&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Combined: true&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Support, and on&lt;/strong&gt;: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;not all and (prefers-reduced-data)&lt;/code&gt;: false&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;(prefers-reduced-data)&lt;/code&gt;: true&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Combined: true&lt;/p&gt;

&lt;p&gt;Anything in the media query will now be applied if the feature is supported, regardless of what its value is.&lt;/p&gt;

&lt;h3&gt;
  
  
  Detecting media query support in JavaScript
&lt;/h3&gt;

&lt;p&gt;We can use the same media query in JavaScript using the &lt;code&gt;window.matchMedia&lt;/code&gt; API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isSupported&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matchMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;`not all and (prefers-reduced-data), (prefers-reduced-data)`&lt;/span&gt;
  &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;window.matchMedia returns an object with a "matches" boolean property that is either true or false. For more on the API, check out the &lt;a href="https://polypane.app/blog/the-complete-guide-to-css-media-queries/#using-media-queries-in-javascript"&gt;using media queries in JavaScript&lt;/a&gt; section of my guide on media queries.&lt;/p&gt;

&lt;p&gt;After I shared the above out on Twitter, &lt;a href="https://twitter.com/mathias/status/1412433744930258950"&gt;Mathias pointed out a different method&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;(prefers-reduced-data)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resolvedMediaQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matchMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;media&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isSupported&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;resolvedMediaQuery&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;window.matchMedia&lt;/code&gt; api also returns a "media" property, which is the normalized and resolved string representation of the query you tested. If matchMedia encounters something it doesn't understand, that changed to "not all", and if it does support the query it will return that, regardless of if it matches (you can use the matches property for that).&lt;/p&gt;

&lt;p&gt;So by comparing your input to the media, you either get:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No support&lt;/strong&gt;: &lt;br&gt;
'(prefers-reduced-data)' === 'not all' which is false.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Support&lt;/strong&gt;:&lt;br&gt;
'(prefers-reduced-data)' === '(prefers-reduced-data)' which is true.&lt;/p&gt;

&lt;h4&gt;
  
  
  Which one to use?
&lt;/h4&gt;

&lt;p&gt;What I like about the first option, with the complex media query, is that all the logic happens inside CSS. I also like how you get a boolean, and don't have to do string comparison.&lt;/p&gt;

&lt;p&gt;The second can be a little bit easier to understand at a glance, but you need to make sure that your query input is the same as the browser normalizes it. &lt;/p&gt;

&lt;p&gt;For example, if you test &lt;code&gt;(prefers-reduced-data )&lt;/code&gt; (notice the space), that would resolve "matches" to true in supported browsers because the white space is not important, but comparing the normalized media query would return false, since that normalization has removed that extra space. So string comparison can be tricky depending on your input.&lt;/p&gt;

&lt;h3&gt;
  
  
  When to use this?
&lt;/h3&gt;

&lt;p&gt;We're set to get &lt;a href="https://polypane.app/blog/the-complete-guide-to-css-media-queries/#upcoming-media-query-features"&gt;a whole lot of new media features&lt;/a&gt; in the coming years, like &lt;code&gt;prefers-reduced-data&lt;/code&gt;, &lt;code&gt;prefers-contrast&lt;/code&gt;, &lt;code&gt;screen-spanning&lt;/code&gt; and more. &lt;/p&gt;

&lt;p&gt;While transitioning to all browsers supporting this, you'll often want to turn on extra features for browsers that support it without causing issues in older browsers since the new default might not always be the best experience in older browsers. With this media feature you can split the behavior in older browsers without support for newer browsers with support.&lt;/p&gt;

</description>
      <category>css</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
