Sorry

This feed does not validate.

In addition, interoperability with the widest range of feed readers could be improved by implementing the following recommendations.

Source: https://css-tricks.com/feed/

  1. <?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
  2. xmlns:content="http://purl.org/rss/1.0/modules/content/"
  3. xmlns:wfw="http://wellformedweb.org/CommentAPI/"
  4. xmlns:dc="http://purl.org/dc/elements/1.1/"
  5. xmlns:atom="http://www.w3.org/2005/Atom"
  6. xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
  7. xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
  8. xmlns:media="http://search.yahoo.com/mrss/"
  9. xmlns:georss="http://www.georss.org/georss"
  10. xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"
  11. >
  12.  
  13. <channel>
  14. <title>CSS-Tricks</title>
  15. <atom:link href="https://css-tricks.com/feed/" rel="self" type="application/rss+xml" />
  16. <link>https://css-tricks.com</link>
  17. <description>Tips, Tricks, and Techniques on using Cascading Style Sheets.</description>
  18. <lastBuildDate>Thu, 25 Apr 2024 13:31:34 +0000</lastBuildDate>
  19. <language>en-US</language>
  20. <sy:updatePeriod>
  21. hourly </sy:updatePeriod>
  22. <sy:updateFrequency>
  23. 1 </sy:updateFrequency>
  24. <generator>https://wordpress.org/?v=6.5.2</generator>
  25.  
  26. <image>
  27. <url>https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/07/star.png?fit=32%2C32&#038;ssl=1</url>
  28. <title>CSS-Tricks</title>
  29. <link>https://css-tricks.com</link>
  30. <width>32</width>
  31. <height>32</height>
  32. </image>
  33. <site xmlns="com-wordpress:feed-additions:1">45537868</site> <item>
  34. <title>Demystifying Screen Readers: Accessible Forms &#038; Best Practices</title>
  35. <link>https://css-tricks.com/demystifying-screen-readers-accessible-forms-best-practices/</link>
  36. <comments>https://css-tricks.com/demystifying-screen-readers-accessible-forms-best-practices/#comments</comments>
  37. <dc:creator><![CDATA[Chris DeMars]]></dc:creator>
  38. <pubDate>Fri, 19 Apr 2024 14:26:47 +0000</pubDate>
  39. <category><![CDATA[Article]]></category>
  40. <category><![CDATA[accessibility]]></category>
  41. <category><![CDATA[forms]]></category>
  42. <guid isPermaLink="false">https://css-tricks.com/?p=377733</guid>
  43.  
  44. <description><![CDATA[<p>This is the 3rd post in a small series we did on form accessibility. If you missed the 2nd post, check out <a href="https://css-tricks.com/managing-user-focus-with-focus-visible/">Managing User Focus with :focus-visible</a>. In this post we are going to look at using a screen &#8230;</p>
  45. <hr />
  46. <p><small><a rel="nofollow" href="https://css-tricks.com/demystifying-screen-readers-accessible-forms-best-practices/">Demystifying Screen Readers: Accessible Forms &#038; Best Practices</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
  47. ]]></description>
  48. <content:encoded><![CDATA[
  49. <p>This is the 3rd post in a small series we did on form accessibility. If you missed the 2nd post, check out <a href="https://css-tricks.com/managing-user-focus-with-focus-visible/">Managing User Focus with :focus-visible</a>. In this post we are going to look at using a screen reader when navigating a form, and also some best practices.</p>
  50.  
  51.  
  52.  
  53. <p>If you have ideas and feedback to build on this post, please let us know!</p>
  54.  
  55.  
  56. <h2 class="wp-block-heading" id="what-is-a-screen-reader"><strong>What is a Screen Reader?</strong></h2>
  57.  
  58.  
  59. <p>You may have heard the term “screen reader” as you have been moving around the web. You might even be using a screen reader at this moment to run manual accessibility tests on the experiences you are building. A screen reader is a type of AT or assistive technology.</p>
  60.  
  61.  
  62.  
  63. <p>A screen reader converts digital text into synthesized speech or Braille output, commonly seen with a Braille reader.</p>
  64.  
  65.  
  66. <div class="wp-block-image">
  67. <figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="768" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/braille-machine.jpg?resize=1024%2C768&#038;ssl=1" alt="A user using a braille machine with a computer." class="wp-image-377734" style="object-fit:cover" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/braille-machine-scaled.jpg?resize=1024%2C768&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/braille-machine-scaled.jpg?resize=300%2C225&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/braille-machine-scaled.jpg?resize=768%2C576&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/braille-machine-scaled.jpg?resize=1536%2C1152&amp;ssl=1 1536w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/braille-machine-scaled.jpg?resize=2048%2C1536&amp;ssl=1 2048w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /><figcaption class="wp-element-caption"><a href="https://do.co/3vQTmoW" rel="noopener">https://do.co/3vQTmoW</a></figcaption></figure></div>
  68.  
  69.  
  70. <p>In this example, I will be using Mac VO. Mac VO (VoiceOver) is built-in to all Mac devices; iOS, iPadOS, and macOS systems. Depending on the type of device you are running macOS on, opening VO could differ. The Macbook Pro that is running VO I am writing this on doesn’t have the touch bar, so I will be using the shortcut keys according to the hardware.</p>
  71.  
  72.  
  73. <h2 class="wp-block-heading" id="spinning-up-vo-on-macos"><strong>Spinning Up VO on macOS</strong></h2>
  74.  
  75.  
  76. <p>If you are using an updated Macbook Pro, the keyboard on your machine will look something like the image below.</p>
  77.  
  78.  
  79.  
  80. <p>You will start by holding down the <code>cmd</code> key and then pressing the Touch ID three times quickly.</p>
  81.  
  82.  
  83.  
  84. <figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="650" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/mbp-keyboard.png?resize=1024%2C650&#038;ssl=1" alt="MacBook Pro Keyboard with steps on how to start mac voiceover." class="wp-image-377735" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/mbp-keyboard.png?resize=1024%2C650&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/mbp-keyboard.png?resize=300%2C190&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/mbp-keyboard.png?resize=768%2C487&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/mbp-keyboard.png?w=1417&amp;ssl=1 1417w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /></figure>
  85.  
  86.  
  87.  
  88. <p>If you are on a MBP (MacBook Pro) with a TouchBar, you will use the shortcut <code>cmd+fn+f5</code> to turn on VO. If you are using a traditional keyboard with your desktop or laptop, the keys should be the same or you will have to toggle VO on in the Accessibility settings.. Once VO is turned on, you will be greeted with this dialog along with a vocalized introduction to VO.</p>
  89.  
  90.  
  91. <div class="wp-block-image">
  92. <figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="447" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/welcome-to-vo.png?resize=1024%2C447&#038;ssl=1" alt="Welcome to VoiceOver dialog when opening up voiceover." class="wp-image-377736" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/welcome-to-vo.png?resize=1024%2C447&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/welcome-to-vo.png?resize=300%2C131&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/welcome-to-vo.png?resize=768%2C335&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/welcome-to-vo.png?w=1140&amp;ssl=1 1140w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /></figure></div>
  93.  
  94.  
  95. <p>If you click the “Use VoiceOver” button you are well on your way to using VO to test your websites and apps. One thing to keep in mind is that VO is optimized for use with Safari. That being said, make sure when you are running your screen reader test that Safari is the browser you are using. That goes for the iPhone and iPad as well.</p>
  96.  
  97.  
  98.  
  99. <p>There are two main ways you can use VO from the start. The way I personally use it is by navigating to a website and using a combination of the <code>tab, control, option, shift</code> and arrow keys, I can navigate through the experience efficiently with these keys alone.</p>
  100.  
  101.  
  102.  
  103. <p>Another common way to navigate the experience is by using the <a href="https://support.apple.com/guide/voiceover/with-the-voiceover-rotor-mchlp2719/mac" rel="noopener">VoiceOver Rotor</a>. The Rotor is a feature designed to navigate directly to where you want to be in the experience. By using the Rotor, you eliminate having to traverse through the whole site, think of it as a “Choose Your Own Adventure”.</p>
  104.  
  105.  
  106. <h3 class="wp-block-heading" id="modifier-keys"><strong>Modifier Keys</strong></h3>
  107.  
  108.  
  109. <p>Modifier keys are the way you use the different features in VO. The default modifier key or <code>VO</code> is <code>control</code> + <code>option</code> but you can change it to caps lock or choose both options to use interchangeably.</p>
  110.  
  111.  
  112.  
  113. <figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="585" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/vo-mod-keys.png?resize=1024%2C585&#038;ssl=1" alt="VoiceOver utility to change the modifier keys." class="wp-image-377737" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/vo-mod-keys.png?resize=1024%2C585&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/vo-mod-keys.png?resize=300%2C172&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/vo-mod-keys.png?resize=768%2C439&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/vo-mod-keys.png?resize=1536%2C878&amp;ssl=1 1536w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/vo-mod-keys.png?w=1826&amp;ssl=1 1826w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /></figure>
  114.  
  115.  
  116. <h2 class="wp-block-heading" id="using-the-rotor"><strong>Using the Rotor</strong></h2>
  117.  
  118.  
  119. <p>In order to use the Rotor you have to use a combination of your modifier key(s) and the letter “U”. For me, my modifier key is <code>caps lock</code>. I press <code>caps lock</code> + <code>U</code> and the Rotor spins up for me. Once the Rotor comes up I can navigate to any part of the experience that I want using the left and right arrows.</p>
  120.  
  121.  
  122. <div class="wp-block-image">
  123. <figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="594" height="686" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/rotor-headings.png?resize=594%2C686&#038;ssl=1" alt="VoiceOver rotor feature showing the Headings navigation." class="wp-image-377738" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/rotor-headings.png?w=594&amp;ssl=1 594w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/rotor-headings.png?resize=260%2C300&amp;ssl=1 260w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /></figure></div>
  124.  
  125.  
  126. <figure class="wp-block-embed aligncenter is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
  127. <iframe loading="lazy" title="Using the Rotor in VoiceOver" width="500" height="281" src="https://www.youtube.com/embed/3oRqTDX6VuU?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
  128. </div><figcaption class="wp-element-caption"><strong><em>Using the Rotor in VoiceOver</em></strong></figcaption></figure>
  129.  
  130.  
  131. <h2 class="wp-block-heading" id="navigating-by-heading-level"><strong>Navigating By Heading Level</strong></h2>
  132.  
  133.  
  134. <p>Another neat way to navigate the experience is by heading level. If you use the combination of your modifier keys + <code>cmd</code> + <code>H </code>you can traverse the document structure based on heading levels. You can also move back up the document by pressing <code>shift</code> in the sequence like so, modifier keys + <code>shift</code> + <code>cmd</code> + <code>H</code>.</p>
  135.  
  136.  
  137.  
  138. <figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
  139. <iframe loading="lazy" title="Using the Heading Level Shortcut with VoiceOver" width="500" height="281" src="https://www.youtube.com/embed/Arp8NsNQpLo?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
  140. </div><figcaption class="wp-element-caption"><strong><em>Using the Heading Level Shortcut with VoiceOver</em></strong></figcaption></figure>
  141.  
  142.  
  143. <h2 class="wp-block-heading" id="history-amp-best-practices"><strong>History &amp; Best Practices</strong></h2>
  144.  
  145.  
  146. <p>Forms are one of the most powerful native elements we have in HTML. Whether you are searching for something on a page, submitting a form to purchase something or submit a survey. Forms are a cornerstone of the web, and were a catalyst that introduced interactivity to our experiences.</p>
  147.  
  148.  
  149.  
  150. <p>The history of the web form dates back to September 1995 when it was introduced in the <a href="https://www.w3.org/MarkUp/html-spec/html-spec_toc.html" rel="noopener">HTML 2.0 spec</a>. Some say the good ole days of the web, at least I say that. Stephanie Stimac wrote an awesome article on <a href="https://www.smashingmagazine.com/" rel="noopener">Smashing Magazine</a> titled, “<a href="https://www.smashingmagazine.com/2020/11/standardizing-select-native-html-form-controls/" rel="noopener">Standardizing Select And Beyond: The Past, Present And Future Of Native HTML Form Controls</a>”.</p>
  151.  
  152.  
  153.  
  154. <p><strong>The following are 5 best practices according to myself, to follow when building an accessible form for the web.</strong></p>
  155.  
  156.  
  157.  
  158. <ol>
  159. <li>Make sure that you are using a form element. Forms are accessible by default and should be used over div’s at all times. It announces to a screen reader that the element is a form.</li>
  160. </ol>
  161.  
  162.  
  163.  
  164. <pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;form>
  165.  &lt;!-- Form controls are nested here. -->
  166. &lt;/form>
  167. </code></pre>
  168.  
  169.  
  170.  
  171. <ol start="2">
  172. <li>Be sure to use the <code>for</code> and <code>id</code> attributes on <code>label</code>’s and <code>input</code>’s so that they are linked. This way, if you click/tap the label, focus will shift to the input and you can start typing.</li>
  173. </ol>
  174.  
  175.  
  176.  
  177. <pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;label for="name">Name:&lt;/label>
  178. &lt;input type="text" id="name" name="name" required/></code></pre>
  179.  
  180.  
  181.  
  182. <ol start="3">
  183. <li>If a field is required in order for the form to be complete, use the required attribute.</li>
  184. </ol>
  185.  
  186.  
  187.  
  188. <pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;input type="text" id="name" name="name" required /></code></pre>
  189.  
  190.  
  191.  
  192. <ol start="4">
  193. <li>Use the, <code>:focus</code>, <code>:focus-within</code> and <code>:focus-visible</code> CSS pseudo classes to manage and customize how a user receives focus if not using a screen reader.</li>
  194. </ol>
  195.  
  196.  
  197.  
  198. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">form:focus-within {
  199. background-color: #cfffcf;
  200. }
  201.  
  202. input:focus-within {
  203. border: 10px solid #000000;
  204. }
  205.  
  206. input:focus-visible,
  207. select:focus-visible,
  208. textarea:focus-visible {
  209. outline: 2px solid crimson;
  210. border-radius: 3px;
  211. }</code></pre>
  212.  
  213.  
  214.  
  215. <ol start="5">
  216. <li>A <code>button</code> is used to invoke an action, like submitting a form. Use it! Don’t create buttons using <code>div</code>’s. A <code>div</code> by definition is a division in the page. It has no semantic meaning by itself.</li>
  217. </ol>
  218.  
  219.  
  220. <h2 class="wp-block-heading" id="demo"><strong>Demo</strong></h2>
  221.  
  222.  
  223. <figure class="wp-block-embed aligncenter is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
  224. <iframe loading="lazy" title="Navigating a Web Form with VoiceOver" width="500" height="281" src="https://www.youtube.com/embed/OU0efpgfgUU?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
  225. </div><figcaption class="wp-element-caption"><strong><em>Navigating a Web Form with VoiceOver</em></strong></figcaption></figure>
  226.  
  227.  
  228.  
  229. <p>If you want to check out the code, navigate to the <a href="https://github.com/chrisdemars/voiceover-demo?utm_team=devrel&amp;utm_source=web&amp;utm_content=csstricks" rel="noopener"><strong>VoiceOver Demo GitHub repo</strong></a>. If you want to try out the demo above with your screen reader of choice, check out <a href="http://Navigating a Web Form with VoiceOver"><strong>Navigating a Web Form with VoiceOver</strong></a>.</p>
  230.  
  231.  
  232. <h2 class="wp-block-heading" id="screen-reader-software"><strong>Screen Reader Software</strong></h2>
  233.  
  234.  
  235. <p>Below is a list of various types of screen reader software you can use on your given operating system. If a Mac is not your machine of choice, there are options out there for Windows and Linux, as well as for Android devices.</p>
  236.  
  237.  
  238. <h3 class="wp-block-heading" id="nvda"><strong>NVDA</strong></h3>
  239.  
  240.  
  241. <p>NVDA is a screen reader from NV Access. It is currently only supported on PC’s running Microsoft Windows 7 SP1 and later. For more access, check out the <a href="https://www.nvaccess.org/download/" rel="noopener">NVDA version 2024.1 download page on the NV Access website</a>!</p>
  242.  
  243.  
  244. <h3 class="wp-block-heading" id="jaws">JAWS</h3>
  245.  
  246.  
  247. <figure class="wp-block-pullquote ticss-06f30f3e" style="border-style:none;border-width:0px"><blockquote><p><strong><em>“We need a better screen reader”</em></strong></p><cite><strong><em>&#8211; Anonymous</em></strong></cite></blockquote></figure>
  248.  
  249.  
  250.  
  251. <p>If you understood the reference above, you are in good company. According to the JAWS website, this is what it is in a nutshell:</p>
  252.  
  253.  
  254.  
  255. <figure class="wp-block-pullquote ticss-aae9d924" style="border-style:none;border-width:0px"><blockquote><p><strong><em>“JAWS, Job Access With Speech, is the world’s most popular screen reader, developed for computer users whose vision loss prevents them from seeing screen content or navigating with a mouse. JAWS provides speech and Braille output for the most popular computer applications on your PC. You will be able to navigate the Internet, write a document, read an email and create presentations from your office, remote desktop, or from home.”</em></strong></p><cite><strong><a href="https://www.freedomscientific.com/products/software/jaws/" rel="noopener">JAWS website</a></strong></cite></blockquote></figure>
  256.  
  257.  
  258.  
  259. <p><a href="https://www.freedomscientific.com/products/software/jaws/" rel="noopener">Check out JAWS for yourself</a> and if that solution fits your needs, definitely give it a shot!</p>
  260.  
  261.  
  262. <h3 class="wp-block-heading" id="narrator">Narrator</h3>
  263.  
  264.  
  265. <p>Narrator is a built-in screen reader solution that ships with WIndows 11. If you choose to use this as your screen reader of choice, the link below is for support documentation on its usage.</p>
  266.  
  267.  
  268.  
  269. <p><a href="https://support.microsoft.com/en-us/windows/complete-guide-to-narrator-e4397a0d-ef4f-b386-d8ae-c172f109bdb1" rel="noopener">Complete guide to Narrator</a></p>
  270.  
  271.  
  272. <h3 class="wp-block-heading" id="orca">Orca</h3>
  273.  
  274.  
  275. <p>Orca is a screen reader that can be used on different Linux distributions running GNOME.</p>
  276.  
  277.  
  278.  
  279. <figure class="wp-block-pullquote" style="border-style:none;border-width:0px"><blockquote><p><strong><em>“Orca is a free, open source, flexible, and extensible screen reader that provides access to the graphical desktop via speech and refreshable braille.</em></strong><br><br><strong><em>Orca works with applications and toolkits that support the Assistive Technology Service Provider Interface (AT-SPI), which is the primary assistive technology infrastructure for Linux and Solaris. Applications and toolkits supporting the AT-SPI include the GNOME Gtk+ toolkit, the Java platform&#8217;s Swing toolkit, LibreOffice, Gecko, and WebKitGtk. AT-SPI support for the KDE Qt toolkit is being pursued.”</em></strong><br></p><cite><a href="https://help.gnome.org/users/orca/stable/introduction.html.en" rel="noopener"><strong>Orca Website</strong></a></cite></blockquote></figure>
  280.  
  281.  
  282. <h3 class="wp-block-heading" id="talkback">TalkBack</h3>
  283.  
  284.  
  285. <p>Google TalkBack is the screen reader that is used on Android devices. For more information on turning it on and using it, <a href="https://support.google.com/accessibility/android/answer/6007100?hl=en" rel="noopener">check out this article on the Android Accessibility Support Site</a>.</p>
  286.  
  287.  
  288. <h2 class="wp-block-heading" id="browser-support">Browser Support</h2>
  289.  
  290.  
  291. <p>If you are looking for actual browser support for HTML elements and ARIA (Accessible Rich Internet Application) attributes, I suggest <a href="http://caniuse.com" rel="noopener">caniuse.com for HTML</a> and <a href="https://a11ysupport.io/" rel="noopener">Accessibility Support for ARIA</a> to get the latest 4-1-1 on browser support. Remember, if the browser doesn’t support the tech, chances are the screen reader won’t either.</p>
  292.  
  293.  
  294.  
  295. <p><a href="https://www.digitala11y.com/" rel="noopener">DigitalA11Y</a> can help summarize browser and screen reader info with their article,&nbsp; <a href="https://www.digitala11y.com/screen-readers-browsers-which-is-the-best-combination-for-accessibility-testing/" rel="noopener">Screen Readers and Browsers! Which is the Best Combination for Accessibility Testing?</a></p>
  296.  
  297.  
  298. <h2 class="wp-block-heading" id="links">Links</h2>
  299.  
  300.  
  301. <p><a href="https://support.apple.com/guide/voiceover/with-the-voiceover-rotor-mchlp2719/mac" rel="noopener">https://support.apple.com/guide/voiceover/with-the-voiceover-rotor-mchlp2719/mac</a></p>
  302.  
  303.  
  304.  
  305. <p><a href="https://www.w3.org/TR/wai-aria/" rel="noopener">https://www.w3.org/TR/wai-aria/</a></p>
  306.  
  307.  
  308.  
  309. <p><a href="https://www.w3.org/WAI/standards-guidelines/aria/" rel="noopener">https://www.w3.org/WAI/standards-guidelines/aria/</a></p>
  310.  
  311.  
  312.  
  313. <p><a href="https://support.google.com/accessibility/android/answer/6283677?hl=en" rel="noopener">https://support.google.com/accessibility/android/answer/6283677?hl=en</a></p>
  314.  
  315.  
  316.  
  317. <p><a href="https://support.google.com/accessibility/android/answer/6283677?hl=en" rel="noopener">https://support.google.com/accessibility/android/answer/6283677?hl=en</a></p>
  318. <hr />
  319. <p><small><a rel="nofollow" href="https://css-tricks.com/demystifying-screen-readers-accessible-forms-best-practices/">Demystifying Screen Readers: Accessible Forms &#038; Best Practices</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
  320. ]]></content:encoded>
  321. <wfw:commentRss>https://css-tricks.com/demystifying-screen-readers-accessible-forms-best-practices/feed/</wfw:commentRss>
  322. <slash:comments>4</slash:comments>
  323. <media:content url="https://www.youtube.com/embed/3oRqTDX6VuU" medium="video" width="1280" height="720">
  324. <media:player>https://www.youtube.com/embed/3oRqTDX6VuU</media:player>
  325. <media:title type="plain">Using the Rotor in VoiceOver</media:title>
  326. <media:description type="html"><![CDATA[To learn more about DigitalOcean: https://www.digitalocean.com/ Follow us on Twitch: https://www.twitch.tv/digitaloceantvFollow us on Twitter: https://twitte...]]></media:description>
  327. <media:thumbnail url="https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/braille-machine-scaled.jpg?fit=2560%2C1920&#038;ssl=1" />
  328. <media:rating scheme="urn:simple">nonadult</media:rating>
  329. </media:content>
  330. <post-id xmlns="com-wordpress:feed-additions:1">377733</post-id> </item>
  331. <item>
  332. <title>Managing User Focus with :focus-visible</title>
  333. <link>https://css-tricks.com/managing-user-focus-with-focus-visible/</link>
  334. <comments>https://css-tricks.com/managing-user-focus-with-focus-visible/#comments</comments>
  335. <dc:creator><![CDATA[Chris DeMars]]></dc:creator>
  336. <pubDate>Fri, 05 Apr 2024 22:13:47 +0000</pubDate>
  337. <category><![CDATA[Article]]></category>
  338. <category><![CDATA[accessibility]]></category>
  339. <category><![CDATA[forms]]></category>
  340. <guid isPermaLink="false">https://css-tricks.com/?p=377702</guid>
  341.  
  342. <description><![CDATA[<p>This is going to be the 2nd post in a small series we are doing on form accessibility. If you missed the first post, check out <a href="https://css-tricks.com/accessible-forms-with-pseudo-classes/?utm_team=devrel&#38;utm_source=twitter&#38;utm_content=csstricks">Accessible Forms with Pseudo Classes</a>. In this post we are going to look &#8230;</p>
  343. <hr />
  344. <p><small><a rel="nofollow" href="https://css-tricks.com/managing-user-focus-with-focus-visible/">Managing User Focus with :focus-visible</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
  345. ]]></description>
  346. <content:encoded><![CDATA[
  347. <p>This is going to be the 2nd post in a small series we are doing on form accessibility. If you missed the first post, check out <a href="https://css-tricks.com/accessible-forms-with-pseudo-classes/?utm_team=devrel&amp;utm_source=twitter&amp;utm_content=csstricks">Accessible Forms with Pseudo Classes</a>. In this post we are going to look at :focus-visible and how to use it in your web sites!</p>
  348.  
  349.  
  350. <h2 class="wp-block-heading" id="focus-touchpoint"><strong>Focus Touchpoint</strong></h2>
  351.  
  352.  
  353. <p>Before we move forward with <code><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-black-color">:focus-visible</mark></code>, let’s revisit how <code>:focus</code> works in your CSS. Focus is the visual indicator that an element is being interacted with via keyboard, mouse, trackpad, or assistive technology. Certain elements are naturally interactive, like links, buttons, and form elements. We want to make sure that our users know where they are and the interactions they are making.</p>
  354.  
  355.  
  356.  
  357. <p><strong><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">Remember don’t do this in your CSS</mark></strong><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">!</mark></p>
  358.  
  359.  
  360.  
  361. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">:focus {
  362.  outline: 0;
  363. }
  364.  
  365. /*** OR ***/
  366.  
  367. :focus {
  368.  outline: none;
  369. }</code></pre>
  370.  
  371.  
  372.  
  373. <p>When you remove focus, you remove it for<strong> EVERYONE! </strong>We want to make sure that we are preserving the focus.</p>
  374.  
  375.  
  376.  
  377. <p>If for any reason you do need to remove the focus, make sure there is also fallback <code>:focus</code> styles for your users. That fallback can match your branding colors, but make sure those colors are also accessible. If marketing, design, or branding doesn’t like the default focus ring styles, then it is time to start having conversations and collaborate with them on the best way of adding it back in.</p>
  378.  
  379.  
  380. <h2 class="wp-block-heading" id="what-is-focusvisible"><strong>What is <code>focus-visible?</code></strong></h2>
  381.  
  382.  
  383. <p>The pseudo class, <code>:focus-visible</code>, is just like our default <code>:focus</code> pseudo class. It gives the user an indicator that something is being focused on the page. The way you write<code> :focus-visible</code> is cut and dry:</p>
  384.  
  385.  
  386.  
  387. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">:focus-visible {
  388.  /* ... */
  389. }</code></pre>
  390.  
  391.  
  392.  
  393. <p>When using <code>:focus-visible</code> with a specific element, the syntax looks something like this:</p>
  394.  
  395.  
  396.  
  397. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.your-element:focus-visible {
  398.  /*...*/
  399. }</code></pre>
  400.  
  401.  
  402.  
  403. <p>The great thing about using <code>:focus-visible</code> is you can make your element stand out,<strong> bright and bold!</strong> No need to worry about it showing if the element is clicked/tapped. If you choose not to implement the class, the default will be the user agent focus ring which to some is undesirable.</p>
  404.  
  405.  
  406. <h2 class="wp-block-heading" id="backstory-of-focusvisible"><strong>Backstory of <code>focus-visible</code></strong></h2>
  407.  
  408.  
  409. <p>Before we had the <code>:focus-visible</code>, the user agent styling would apply <code>:focus</code> to most elements on the page; buttons, links, etc. It would apply an outline or “focus ring” to the focusable element. This was deemed to be ugly, most didn’t like the default focus ring the browser provided. As a result of the focus ring being unfavorable to look at, most authors removed it… without a fallback. Remember, when you remove <code>:focus</code>, it decreases usability and makes the experience inaccessible for keyboard users.</p>
  410.  
  411.  
  412.  
  413. <p><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible" rel="noopener">In the current state of the web, the browser no longer visibly indicates focus around various elements when they have focus.</a> The browser instead uses varying heuristics to determine when it would help the user, providing a focus ring in return. According to <a href="https://www.khanacademy.org/computing/ap-computer-science-principles/algorithms-101/solving-hard-problems/a/using-heuristics" rel="noopener">Khan Academy</a>, a heuristic is, <strong><em>“a technique that guides an algorithm to find good choices.”</em></strong></p>
  414.  
  415.  
  416.  
  417. <p>What this means is that the browser can detect whether or not the user is interacting with the experience from a keyboard, mouse, or trackpad and based on that input type, it adds or removes the focus ring. The example in this post highlights the input interaction.</p>
  418.  
  419.  
  420.  
  421. <p>In the early days of <code>:focus-visible</code> we were using a <a href="https://github.com/WICG/focus-visible" rel="noopener">polyfill</a> to handle the focus ring created by Alice Boxhall and Brian Kardell, Mozilla also came out with their own pseudo class, <code>:moz-focusring</code>, before the official specification. If you want to learn more about the early days of the focus-ring, check out <a href="https://youtu.be/ilj2P5-5CjI?feature=shared" rel="noopener">A11y Casts</a> with Rob Dodson.</p>
  422.  
  423.  
  424. <h2 class="wp-block-heading" id="focus-importance"><strong>Focus Importance</strong></h2>
  425.  
  426.  
  427. <p>There are plenty of reasons why focus is important in your application. For one, like I stated above, we as ambassadors of the web have to make sure we are providing the best, accessible experience we can. We don’t want any of our users guessing where they are while they are navigation through the experience.</p>
  428.  
  429.  
  430.  
  431. <p>One example that always comes to mind is the <a href="https://twoblindbrothers.com/" rel="noopener">Two Blind Brothers website</a>. If you go to the website and click/tap (this works on mobile), the closed eye in the bottom left corner, you will see the eye open and a simulation begins. Both the brothers, Bradford and Bryan Manning, were diagnosed at a young age with Stargardt&#8217;s Disease. Stargardt&#8217;s disease is a form of macular degeneration of the eye. Over time both brothers will be completely blind. Visit the site and click the eye to see how they see.</p>
  432.  
  433.  
  434.  
  435. <p>If you were in their shoes and you had to navigate through a page, you would want to make sure you knew exactly where you were throughout the whole experience. A focus ring gives you that power.</p>
  436.  
  437.  
  438. <div class="wp-block-image is-style-default ticss-7902f8d6">
  439. <figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="586" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/two-blind-brothers-2.png?resize=1024%2C586&#038;ssl=1" alt="Image of the home page from the Two Blind Brothers website." class="wp-image-377710" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/two-blind-brothers-2.png?resize=1024%2C586&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/two-blind-brothers-2.png?resize=300%2C172&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/two-blind-brothers-2.png?resize=768%2C440&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/two-blind-brothers-2.png?resize=1536%2C879&amp;ssl=1 1536w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/two-blind-brothers-2.png?resize=2048%2C1172&amp;ssl=1 2048w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/two-blind-brothers-2.png?w=3000&amp;ssl=1 3000w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /></figure></div>
  440.  
  441. <h2 class="wp-block-heading" id="demo">Demo</h2>
  442.  
  443.  
  444. <p>The demo below shows how <code>:focus-visible</code> works when added to your CSS. The first part of the video shows the experience when navigating through with a mouse the second shows navigating through with just my keyboard. I recorded myself as well to show that I did switch from using my mouse, to my keyboard.</p>
  445.  
  446.  
  447. <div class="wp-block-image">
  448. <figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="576" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/focus-visible-heuristics.gif?resize=1024%2C576&#038;ssl=1" alt="Video showing how the heuristics of the browser works based on input and triggering the focus visible pseudo class." class="wp-image-377716" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/focus-visible-heuristics.gif?resize=1024%2C576&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/focus-visible-heuristics.gif?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/focus-visible-heuristics.gif?resize=768%2C432&amp;ssl=1 768w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /><figcaption class="wp-element-caption"><strong><em>Video showing how the heuristics of the browser works based on input and triggering the focus visible pseudo class.</em></strong></figcaption></figure></div>
  449.  
  450.  
  451. <p>The browser is predicting what to do with the focus ring based on my input (keyboard/mouse), and then adding a focus ring to those elements. In this case, when I am navigating through this example with the keyboard, everything receives focus. When using the mouse, only the input gets focus and the buttons don’t. If you remove <code>:focus-visible</code>, the browser will apply the default focus ring.</p>
  452.  
  453.  
  454.  
  455. <p>The code below is applying <code>:focus-visible</code> to the focusable elements.</p>
  456.  
  457.  
  458.  
  459. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">:focus-visible {
  460.  outline-color: black;
  461.  font-size: 1.2em;
  462.  font-family: serif;
  463.  font-weight: bold;
  464. }</code></pre>
  465.  
  466.  
  467.  
  468. <p>If you want to specify the <code>label</code> or the button to receive <code>:focus-visible</code> just prepend the class with <code>input</code> or <code>button</code> respectively.</p>
  469.  
  470.  
  471.  
  472. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">button:focus-visible {
  473.  outline-color: black;
  474.  font-size: 1.2em;
  475.  font-family: serif;
  476.  font-weight: bold;
  477. }
  478.  
  479. /*** OR ***/
  480.  
  481. input:focus-visible {
  482.  outline-color: black;
  483.  font-size: 1.2em;
  484.  font-family: serif;
  485.  font-weight: bold;
  486. }</code></pre>
  487.  
  488.  
  489. <h2 class="wp-block-heading" id="support"><strong>Support</strong></h2>
  490.  
  491.  
  492. <p>If the browser does not support <code>:focus-visible</code> you can have a fall back in place to handle the interaction. The code below is from the <a href="https://developer.mozilla.org/en-US/play" rel="noopener">MDN Playground</a>. You can use the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@supports" rel="noopener">@supports</a> at-rule or <strong>“feature query”</strong> to check support. One thing to keep in mind, the rule should be placed at the top of the code or nested inside another group at-rule.</p>
  493.  
  494.  
  495.  
  496. <pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;button class="button with-fallback" type="button">Button with fallback&lt;/button>
  497. &lt;button class="button without-fallback" type="button">Button without fallback&lt;/button></code></pre>
  498.  
  499.  
  500.  
  501. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.button {
  502.  margin: 10px;
  503.  border: 2px solid darkgray;
  504.  border-radius: 4px;
  505. }
  506.  
  507. .button:focus-visible {
  508.  /* Draw the focus when :focus-visible is supported */
  509.  outline: 3px solid deepskyblue;
  510.  outline-offset: 3px;
  511. }
  512.  
  513. @supports not selector(:focus-visible) {
  514.  .button.with-fallback:focus {
  515.    /* Fallback for browsers without :focus-visible support */
  516.    outline: 3px solid deepskyblue;
  517.    outline-offset: 3px;
  518.  }
  519. }</code></pre>
  520.  
  521.  
  522. <h2 class="wp-block-heading" id="further-accessibility-concerns"><strong>Further Accessibility Concerns</strong></h2>
  523.  
  524.  
  525. <p>Accessibility concerns to keep in mind when building out your experience:</p>
  526.  
  527.  
  528.  
  529. <ul>
  530. <li>Make sure the colors you choose for your focus indicator, if at all, are still accessible according to the information documented in the <a href="https://www.w3.org/WAI/WCAG22/Understanding/non-text-contrast.html" rel="noopener">WCAG 2.2 Non-text Contrast (Level AA)</a></li>
  531.  
  532.  
  533.  
  534. <li>Cognitive overload can cause a user distress. Make sure to keep styles on varying interactive elements consistent</li>
  535. </ul>
  536.  
  537.  
  538. <h2 class="wp-block-heading" id="browser-support"><strong>Browser Support</strong></h2>
  539.  
  540. <div class="caniuse"><div class="caniuse-header"><p>This browser support data is from <a href="http://caniuse.com/#feat=css-focus-visible" rel="noopener">Caniuse</a>, which has more detail. A number indicates that browser supports the feature at that version and up.</p></div><div class="caniuse-section"><h4>Desktop</h4><table class="browser-support-table"><thead><tr><th class="chrome"><span>Chrome</span></th><th class="firefox"><span>Firefox</span></th><th class="ie"><span>IE</span></th><th class="edge"><span>Edge</span></th><th class="safari"><span>Safari</span></th></tr></thead><tbody><tr><td class="y yep" title="Chrome - "><span class="caniuse-agents-version version">86</span></td><td class="y yep" title="Firefox - "><span class="caniuse-agents-version version">4*</span></td><td class="n nope" title="IE - "><span class="caniuse-agents-version version">No</span></td><td class="y yep" title="Edge - "><span class="caniuse-agents-version version">86</span></td><td class="y yep" title="Safari - "><span class="caniuse-agents-version version">15.4</span></td></tr></table></div><div class="caniuse-section"><h4>Mobile / Tablet</h4><table class="browser-support-table"><thead><tr><th class="and_chr"><span>Android Chrome</span></th><th class="and_ff"><span>Android Firefox</span></th><th class="android"><span>Android</span></th><th class="ios_saf"><span>iOS Safari</span></th></tr></thead><tbody><tr><td class="y yep" title="Android Chrome - "><span class="caniuse-agents-version version">123</span></td><td class="y yep" title="Android Firefox - "><span class="caniuse-agents-version version">124</span></td><td class="y yep" title="Android - "><span class="caniuse-agents-version version">123</span></td><td class="y yep" title="iOS Safari - "><span class="caniuse-agents-version version">15.4</span></td></tr></table></div></div>
  541.  
  542.  
  543. <h2 class="wp-block-heading" id="links"><strong>Links</strong></h2>
  544.  
  545.  
  546. <ul>
  547. <li><a href="https://daverupert.com/2024/01/focus-visible-love/" rel="noopener">https://daverupert.com/2024/01/focus-visible-love/</a></li>
  548.  
  549.  
  550.  
  551. <li><a href="https://css-tricks.com/almanac/selectors/f/focus-visible/">https://css-tricks.com/almanac/selectors/f/focus-visible/</a></li>
  552. </ul>
  553. <hr />
  554. <p><small><a rel="nofollow" href="https://css-tricks.com/managing-user-focus-with-focus-visible/">Managing User Focus with :focus-visible</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
  555. ]]></content:encoded>
  556. <wfw:commentRss>https://css-tricks.com/managing-user-focus-with-focus-visible/feed/</wfw:commentRss>
  557. <slash:comments>5</slash:comments>
  558. <post-id xmlns="com-wordpress:feed-additions:1">377702</post-id> </item>
  559. <item>
  560. <title>The Power of :has() in CSS</title>
  561. <link>https://css-tricks.com/the-power-of-has-in-css/</link>
  562. <comments>https://css-tricks.com/the-power-of-has-in-css/#comments</comments>
  563. <dc:creator><![CDATA[Chris DeMars]]></dc:creator>
  564. <pubDate>Sat, 30 Mar 2024 02:07:57 +0000</pubDate>
  565. <category><![CDATA[Article]]></category>
  566. <category><![CDATA[:has]]></category>
  567. <category><![CDATA[CSS]]></category>
  568. <guid isPermaLink="false">https://css-tricks.com/?p=377635</guid>
  569.  
  570. <description><![CDATA[<p>Hey all you wonderful developers out there! In this post we are going to explore the use of <code>:has()</code> in your next web project. <code>:has() </code>is relatively newish but has gained popularity in the front end community by delivering control &#8230;</p>
  571. <hr />
  572. <p><small><a rel="nofollow" href="https://css-tricks.com/the-power-of-has-in-css/">The Power of :has() in CSS</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
  573. ]]></description>
  574. <content:encoded><![CDATA[
  575. <p>Hey all you wonderful developers out there! In this post we are going to explore the use of <code>:has()</code> in your next web project. <code>:has() </code>is relatively newish but has gained popularity in the front end community by delivering control over various elements in your UI. Let’s take a look at what the pseudo class is and how we can utilize it.</p>
  576.  
  577.  
  578.  
  579. <span id="more-377635"></span>
  580.  
  581.  
  582. <h2 class="wp-block-heading" id="syntax">Syntax</h2>
  583.  
  584.  
  585. <p>The <code>:has()</code> CSS pseudo-class helps style an element if any of the things we&#8217;re searching for inside it are found and accounted for. It&#8217;s like saying, <strong><em>&#8220;If there&#8217;s something specific inside this box, then style the box this way AND only this way.&#8221;</em></strong></p>
  586.  
  587.  
  588.  
  589. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">:has(&lt;direct-selector>) {
  590.  /* ... */
  591. }</code></pre>
  592.  
  593.  
  594.  
  595. <figure class="wp-block-pullquote ticss-0cbb53f0" style="border-style:none;border-width:0px"><blockquote><p><strong><em>“The functional <code>:has()</code> CSS pseudo-class represents an element if any of the relative selectors that are passed as an argument match at least one element when anchored against this element. This pseudo-class presents a way of selecting a parent element or a previous sibling element with respect to a reference element by taking a relative selector list as an argument.”</em></strong></p><cite>For a more robust explanation, <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:has" rel="noopener">MDN</a> does it perfectly</cite></blockquote></figure>
  596.  
  597.  
  598. <h2 class="wp-block-heading" id="the-styling-problem">The Styling Problem</h2>
  599.  
  600.  
  601. <p>In years past we had no way of styling a parent element based on a direct child of that parent with CSS or an element based on another element. In the chance we had to do that, we would need to use some JavaScript and toggle classes on/off based on the structure of the HTML. <code>:has()</code> solved that problem.</p>
  602.  
  603.  
  604.  
  605. <p><br>Let’s say that you have a heading level 1 element (<code>h1</code>) that is the title of a post or something of that nature on a blog list page, and then you have a heading level 2 (<code>h2</code>) that directly follows it. This h2 could be a sub-heading for the post. If that <code>h2</code> is present, important, and directly after the <code>h1</code>, you might want to make that h1 stand out. Before you would have had to write a JS function.</p>
  606.  
  607.  
  608. <h2 class="wp-block-heading" id="old-school-way-javascript">Old School Way &#8211; JavaScript</h2>
  609.  
  610.  
  611. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">const h1Elements = document.querySelectorAll('h1');
  612.  
  613. h1Elements.forEach((h1) => {
  614.  const h2Sibling = h1.nextElementSibling;
  615.  if (h2Sibling &amp;&amp; h2Sibling.tagName.toLowerCase() === 'h2') {
  616.    h1.classList.add('highlight-content');
  617.  }
  618. });
  619. </code></pre>
  620.  
  621.  
  622.  
  623. <p>This JS function is looking for all the h1’s that have a <code>h2</code> proceeding it, and applying a class of highlight-content to make the <code>h1</code> stand out as an important article.</p>
  624.  
  625.  
  626.  
  627. <p>New and improved with modern day CSS coming in hot! The capabilities of what we can do in the browser have come a long way. We now can take advantage of CSS to do things that we traditionally would have to do with JavaScript, not everything, but some things.</p>
  628.  
  629.  
  630. <h2 class="wp-block-heading" id="new-school-way-css">New School Way &#8211; CSS</h2>
  631.  
  632.  
  633. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">h1:has(+ h2) {
  634.    color: blue;
  635. }
  636. </code></pre>
  637.  
  638.  
  639. <h2 class="wp-block-heading" id="throw-some-has-on-it"><strong>Throw Some :has() On It!</strong></h2>
  640.  
  641.  
  642. <p>Now you can use <code>:has()</code> to achieve the same thing that the JS function did. This CSS is checking for any h1 and using the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:has#with_the_sibling_combinator" rel="noopener">sibling combinator</a> checking for an h2 that immediately follows it, and adds the color of blue to the text. Below are a couple use cases of when <code>:has()</code> can come in handy.</p>
  643.  
  644.  
  645. <h2 class="wp-block-heading" id="has-selector-example-1">:has Selector Example 1</h2>
  646.  
  647. <h3 class="wp-block-heading" id="html">HTML</h3>
  648.  
  649.  
  650. <pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;h1>Lorem, ipsum dolor.&lt;/h1>
  651. &lt;h2>Lorem ipsum dolor sit amet.&lt;/h2>
  652. &lt;p>Lorem, ipsum dolor sit amet consectetur adipisicing elit. Eius, odio voluptatibus est vero iste ad?&lt;/p>
  653. &lt;!-- WITHOUT HAS BELOW -->
  654.  
  655. &lt;h1>This is a test&lt;/h1>
  656. &lt;p>Lorem, ipsum dolor sit amet consectetur adipisicing elit. Eius, odio voluptatibus est vero iste ad?&lt;/p></code></pre>
  657.  
  658.  
  659. <h3 class="wp-block-heading" id="css">CSS</h3>
  660.  
  661.  
  662. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">h1:has(+ h2) {
  663.    color: blue;
  664. }</code></pre>
  665.  
  666.  
  667. <div class="wp-block-image ticss-44c66855">
  668. <figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="808" height="470" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/example-1.png?resize=808%2C470&#038;ssl=1" alt="Example of showing :has being used with pseudo classes." class="wp-image-377693" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/example-1.png?w=808&amp;ssl=1 808w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/example-1.png?resize=300%2C175&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/04/example-1.png?resize=768%2C447&amp;ssl=1 768w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /></figure></div>
  669.  
  670. <h2 class="wp-block-heading" id="has-selector-example-2">:has Selector Example 2</h2>
  671.  
  672.  
  673. <p>A lot of times we as workers on the web are manipulating or working with images. We could be using tools that <a href="https://cloudinary.com/" rel="noopener">Cloudinary</a> provides to make use of various transformations on our images, but usually we want to add drop shadows, border-radii, and captions (not to be confused with alternative text in an alt attribute).</p>
  674.  
  675.  
  676.  
  677. <p><br>The example below is using <code>:has()</code> to see if a figure or image has a figcaption element and if it does, it applies some background and a border radius to make the image stand out.</p>
  678.  
  679.  
  680. <h3 class="wp-block-heading" id="html">HTML</h3>
  681.  
  682.  
  683. <pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;section>
  684.  &lt;figure>
  685.    &lt;img src="https://placedog.net/500/280" alt="My aunt sally's dog is a golden retreiver." />
  686.    &lt;figcaption>My Aunt Sally's Doggo&lt;/figcaption>
  687.  &lt;/figure>
  688. &lt;/section></code></pre>
  689.  
  690.  
  691. <h3 class="wp-block-heading" id="css">CSS</h3>
  692.  
  693.  
  694. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">figure:has(figcaption) {
  695.  background: #c3baba;
  696.  padding: 0.6rem;
  697.  max-width: 50%;
  698.  border-radius: 5px;
  699. }</code></pre>
  700.  
  701.  
  702. <div class="wp-block-image">
  703. <figure class="aligncenter size-large"><a href="https://codepen.io/chrisdemars/pen/oNOGqbK" rel="noopener"><img loading="lazy" decoding="async" width="1024" height="691" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/03/has-selector-example2-1024x691.png?resize=1024%2C691&#038;ssl=1" alt="Example of :has selector highlighting background an image with an caption vs one that does not." class="wp-image-377651" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/03/has-selector-example2.png?resize=1024%2C691&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/03/has-selector-example2.png?resize=300%2C203&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/03/has-selector-example2.png?resize=768%2C519&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/03/has-selector-example2.png?resize=1536%2C1037&amp;ssl=1 1536w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/03/has-selector-example2.png?w=1946&amp;ssl=1 1946w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /></a></figure></div>
  704.  
  705. <h2 class="wp-block-heading" id="can-i-has-that"><strong>Can I <code>:has()</code> that?</strong></h2>
  706.  
  707.  
  708. <p>You can see that <code>:has()</code> has great support across modern browsers.</p>
  709.  
  710.  
  711. <div class="caniuse"><div class="caniuse-header"><p>This browser support data is from <a href="http://caniuse.com/#feat=css-has" rel="noopener">Caniuse</a>, which has more detail. A number indicates that browser supports the feature at that version and up.</p></div><div class="caniuse-section"><h4>Desktop</h4><table class="browser-support-table"><thead><tr><th class="chrome"><span>Chrome</span></th><th class="firefox"><span>Firefox</span></th><th class="ie"><span>IE</span></th><th class="edge"><span>Edge</span></th><th class="safari"><span>Safari</span></th></tr></thead><tbody><tr><td class="y yep" title="Chrome - "><span class="caniuse-agents-version version">105</span></td><td class="y yep" title="Firefox - "><span class="caniuse-agents-version version">121</span></td><td class="n nope" title="IE - "><span class="caniuse-agents-version version">No</span></td><td class="y yep" title="Edge - "><span class="caniuse-agents-version version">105</span></td><td class="y yep" title="Safari - "><span class="caniuse-agents-version version">15.4</span></td></tr></table></div><div class="caniuse-section"><h4>Mobile / Tablet</h4><table class="browser-support-table"><thead><tr><th class="and_chr"><span>Android Chrome</span></th><th class="and_ff"><span>Android Firefox</span></th><th class="android"><span>Android</span></th><th class="ios_saf"><span>iOS Safari</span></th></tr></thead><tbody><tr><td class="y yep" title="Android Chrome - "><span class="caniuse-agents-version version">123</span></td><td class="y yep" title="Android Firefox - "><span class="caniuse-agents-version version">124</span></td><td class="y yep" title="Android - "><span class="caniuse-agents-version version">123</span></td><td class="y yep" title="iOS Safari - "><span class="caniuse-agents-version version">15.4</span></td></tr></table></div></div>
  712.  
  713.  
  714. <h2 class="wp-block-heading" id="has-in-the-community"><strong><code>:has()</code> in the Community!</strong></h2>
  715.  
  716.  
  717. <p>I reached out to my network on Twitter to see how my peers were using <code>:has()</code> in their day-to-day work and this is what they had to say about it.</p>
  718.  
  719.  
  720.  
  721. <figure class="wp-block-pullquote alignleft has-text-align-center ticss-fa09fde6" style="border-style:none;border-width:0px"><blockquote><p><strong><em>“One example I have is styling a specific SVG from a 3rd party package in </em></strong><a href="https://github.com/open-sauced/app/blob/d0f6e2a1dbe879d6d95a84e1951142425604fb54/styles/globals.css#L207-L210" rel="noopener"><strong><em>@saucedopen</em></strong></a><strong><em> because I couldn&#8217;t style it directly.”</em></strong></p><cite>This is what <a href="https://twitter.com/nickytonline" rel="noopener">Nick Taylor</a> from <a href="https://opensauced.pizza/" rel="noopener">OpenSauced</a> had to say about using <code>:has()</code>.</cite></blockquote></figure>
  722.  
  723.  
  724.  
  725. <pre rel="CSS" class="wp-block-csstricks-code-block language-css ticss-df139919" data-line=""><code markup="tt">svg:has(> #Mail) {
  726.  stroke-width: 1;
  727. }
  728. </code></pre>
  729.  
  730.  
  731.  
  732. <figure class="wp-block-pullquote" style="border-style:none;border-width:0px"><blockquote><p><strong><em>Lol the last time I used it I was building keyboard functionality into a tree view, so I needed to detect states and classes of sibling elements, but it wasn&#8217;t in Firefox yet so I had to find another solution. &#x1fae0;</em></strong></p><cite><a href="https://twitter.com/AbbeyPerini" rel="noopener">Abbey Perini</a> from <a href="https://www.kleanz.com/" rel="noopener">Nexcor Food Safety Technologies, Inc.</a></cite></blockquote></figure>
  733.  
  734.  
  735.  
  736. <p>It is great to see how community members are using modern CSS to solve real world problems, and also a shout out to Abbey using it for accessibility reasons!</p>
  737.  
  738.  
  739. <h2 class="wp-block-heading" id="things-to-keep-in-mind"><strong>Things to Keep in Mind</strong></h2>
  740.  
  741.  
  742. <p>There are a few key points to keep in mind when using <code>:has()</code> Bullet points referenced from <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:has" rel="noopener">MDN.</a></p>
  743.  
  744.  
  745.  
  746. <ul>
  747. <li>The pseudo-class takes on specificity of the most specific selector in its argument</li>
  748.  
  749.  
  750.  
  751. <li class="ticss-795cba5d">If the<code> :has()</code> pseudo-class itself is not supported in a browser, the entire selector block will fail unless <code>:has()</code> is in a forgiving selector list, such as in <code>:is()</code> and <code>:where()</code></li>
  752.  
  753.  
  754.  
  755. <li>The <code>:has()</code> pseudo-class cannot be nested within another <code>:has()</code>&nbsp;</li>
  756.  
  757.  
  758.  
  759. <li>Pseudo-elements are also not valid selectors within <code>:has()</code> and pseudo-elements are not valid anchors for <code>:has()</code></li>
  760. </ul>
  761.  
  762.  
  763. <h2 class="wp-block-heading" id="conclusion">Conclusion</h2>
  764.  
  765.  
  766. <p>Harnessing the power of CSS, including advanced features like the <code>:has()</code> pseudo-class, empowers us to craft exceptional web experiences. CSS&#8217;s strengths lie in its cascade and specificity…the best part, allowing us to leverage its full potential. By embracing the capabilities of CSS, we can drive web design and development forward, unlocking new possibilities and creating groundbreaking user interfaces.</p>
  767.  
  768.  
  769.  
  770. <p><strong>Links:</strong></p>
  771.  
  772.  
  773.  
  774. <ul>
  775. <li><a href="https://ishadeed.com/article/css-has-parent-selector/" rel="noopener">https://ishadeed.com/article/css-has-parent-selector/</a></li>
  776.  
  777.  
  778.  
  779. <li><a href="https://css-tricks.com/almanac/selectors/h/has/">https://css-tricks.com/almanac/selectors/h/has/</a></li>
  780.  
  781.  
  782.  
  783. <li><a href="https://developer.chrome.com/blog/has-m105/#forms" rel="noopener">https://developer.chrome.com/blog/has-m105/#forms</a></li>
  784.  
  785.  
  786.  
  787. <li><a href="https://css-tricks.com/the-css-has-selector/">https://css-tricks.com/the-css-has-selector/</a></li>
  788. </ul>
  789. <hr />
  790. <p><small><a rel="nofollow" href="https://css-tricks.com/the-power-of-has-in-css/">The Power of :has() in CSS</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
  791. ]]></content:encoded>
  792. <wfw:commentRss>https://css-tricks.com/the-power-of-has-in-css/feed/</wfw:commentRss>
  793. <slash:comments>5</slash:comments>
  794. <post-id xmlns="com-wordpress:feed-additions:1">377635</post-id> </item>
  795. <item>
  796. <title>Accessible Forms with Pseudo Classes</title>
  797. <link>https://css-tricks.com/accessible-forms-with-pseudo-classes/</link>
  798. <comments>https://css-tricks.com/accessible-forms-with-pseudo-classes/#comments</comments>
  799. <dc:creator><![CDATA[Chris DeMars]]></dc:creator>
  800. <pubDate>Fri, 22 Mar 2024 18:52:31 +0000</pubDate>
  801. <category><![CDATA[Article]]></category>
  802. <category><![CDATA[accessibility]]></category>
  803. <category><![CDATA[CSS]]></category>
  804. <category><![CDATA[focus]]></category>
  805. <category><![CDATA[forms]]></category>
  806. <category><![CDATA[HTML]]></category>
  807. <category><![CDATA[semantics]]></category>
  808. <guid isPermaLink="false">https://css-tricks.com/?p=377565</guid>
  809.  
  810. <description><![CDATA[<p>Hey all you wonderful developers out there! In this post, I am going to take you through creating a simple contact form using semantic HTML and an awesome CSS pseudo class known as <code>:focus-within</code>. The <code>:focus-within</code> class allows for &#8230;</p>
  811. <hr />
  812. <p><small><a rel="nofollow" href="https://css-tricks.com/accessible-forms-with-pseudo-classes/">Accessible Forms with Pseudo Classes</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
  813. ]]></description>
  814. <content:encoded><![CDATA[
  815. <p>Hey all you wonderful developers out there! In this post, I am going to take you through creating a simple contact form using semantic HTML and an awesome CSS pseudo class known as <code>:focus-within</code>. The <code>:focus-within</code> class allows for great control over focus and letting your user know this is exactly where they are in the experience. Before we jump in, let’s get to the core of what web accessibility is.</p>
  816.  
  817.  
  818. <h2 class="wp-block-heading" id="form-accessibility"><br>Form Accessibility?</h2>
  819.  
  820.  
  821. <p><br>You have most likely heard the term “accessibility” everywhere or the numeronym, a11y. What does it mean? That is a great question with so many answers. When we look at the physical world, accessibility means things like having sharps containers in your bathrooms at your business, making sure there are ramps for wheel assisted people, and having peripherals like large print keyboards on hand for anyone that needs it.</p>
  822.  
  823.  
  824.  
  825. <p>The gamut of accessibility doesn’t stop there, we have digital accessibility that we need to be cognizant of as well, not just for external users, but internal colleagues as well. <a href="https://www.w3.org/TR/WCAG22/#contrast-minimum" rel="noopener">Color contrast is a low hanging fruit</a> that we should be able to nip in the bud. At our workplaces, making sure that if any employee needs assistive tech like a screen reader, we have that installed and available. There are a lot of things that need to be kept into consideration. This article will focus on web accessibility by keeping the <a href="https://www.w3.org/TR/WCAG22/" rel="noopener">WCAG (web content accessibility guidelines)</a> in mind.</p>
  826.  
  827.  
  828.  
  829. <figure class="wp-block-pullquote ticss-471d08cf" style="border-style:none;border-width:0px"><blockquote><p><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:focus" rel="noopener">MDN (Mozilla Developer Network)</a></p><cite>The <code>:focus-within</code> CSS pseudo-class matches an element if the element or any of its descendants are focused. In other words, it represents an element that is itself matched by the :focus pseudo-class or has a descendant that is matched by :focus. (This includes descendants in shadow trees.)</cite></blockquote></figure>
  830.  
  831.  
  832.  
  833. <p>This pseudo class is really great when you want to emphasize that the user is in fact interacting with the element. You can change the background color of the whole form, for example. Or, if focus is moved into an input, you can make the label bold and larger of an input element when focus is moved into that input. What is happening below in the code snippets and examples is what is making the form accessible. <code>:focus-within</code> is just one way we can use CSS to our advantage.</p>
  834.  
  835.  
  836. <h3 class="wp-block-heading" id="how-to-focus">How To Focus</h3>
  837.  
  838.  
  839. <p><br>Focus, in regards to accessibility and the web experience, is the visual indicator that something is being interacted with on the page, in the UI, or within a component. CSS can tell when an interactive element is focused. </p>
  840.  
  841.  
  842.  
  843. <figure class="wp-block-pullquote ticss-805ba99d" style="border-style:none;border-width:0px"><blockquote><p>“The <code>:focus</code> CSS pseudo-class represents an element (such as a form input) that has received focus. It is generally triggered when the user clicks or taps on an element or selects it with the keyboard&#8217;s Tab key.”</p><cite><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:focus" rel="noopener">MDN (Mozilla Developer Network)</a></cite></blockquote></figure>
  844.  
  845.  
  846.  
  847. <p>Always make sure that the focus indicator or the ring around focusable elements maintains the proper color contrast through the experience.</p>
  848.  
  849.  
  850.  
  851. <p>Focus is written like this and can be styled to match your branding if you choose to style it.</p>
  852.  
  853.  
  854.  
  855. <pre rel="CSS" class="wp-block-csstricks-code-block language-css ticss-59119c2e" data-line=""><code markup="tt">:focus {
  856.  * / INSERT STYLES HERE /*
  857. }</code></pre>
  858.  
  859.  
  860.  
  861. <p>Whatever you do, never set your outline to <code><em><strong>0 </strong></em></code><em><strong>or</strong></em><code><em><strong> none</strong></em></code>. Doing so will remove a visible focus indicator for everyone across the whole experience. If you need to remove focus, you can, but make sure to add that back in later. When you remove focus from your CSS or set the outline to <code><em><strong>0 </strong></em></code><em><strong>or</strong></em><code><em><strong> none</strong></em></code>, it removes the focus ring for all your users. This is seen a lot when using a CSS reset. A CSS reset will reset the styles to a blank canvas. This way you are in charge of the empty canvas to style as you wish. If you wish to use a CSS reset, check out <a href="https://www.joshwcomeau.com/css/custom-css-reset/" rel="noopener">Josh Comeau’s reset</a>.</p>
  862.  
  863.  
  864.  
  865. <p class="ticss-4379d1ec"><strong><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">*DO NOT DO what is below!</mark></strong></p>
  866.  
  867.  
  868.  
  869. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">:focus {
  870.  outline: 0;
  871. }
  872.  
  873. :focus {
  874.  outline: none;
  875. }</code></pre>
  876.  
  877.  
  878. <h3 class="wp-block-heading ticss-4379d1ec" id="look-within"><br>Look Within!</h3>
  879.  
  880.  
  881. <p class="ticss-4379d1ec"><br>One of the coolest ways to style focus using CSS is what this article is all about. If you haven’t checked out the <code>:focus-within</code> pseudo class, definitely give that a look! There are a lot of hidden gems when it comes to using semantic markup and CSS, and this is one of them. A lot of things that are overlooked are accessible by default, for instance, semantic markup is by default accessible and should be used over div’s at all times.</p>
  882.  
  883.  
  884.  
  885. <div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
  886. <pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;header>
  887.  &lt;h1>Semantic Markup&lt;/h1>
  888.  &lt;nav>
  889.    &lt;ul>
  890.      &lt;li>&lt;a href="/">Home&lt;/a>&lt;/li>
  891.      &lt;li>&lt;a href="/about">About&lt;/a>&lt;/li>
  892.    &lt;/ul>
  893.  &lt;/nav>
  894. &lt;/header>
  895.  
  896. &lt;section>&lt;!-- Code goes here -->&lt;/section>
  897.  
  898. &lt;section>&lt;!-- Code goes here -->&lt;/section>
  899.  
  900. &lt;aside>&lt;!-- Code goes here -->&lt;/aside>
  901.  
  902. &lt;footer>&lt;!-- Code goes here -->&lt;/footer></code></pre>
  903. </div></div>
  904.  
  905.  
  906.  
  907. <p>The <code>header</code>, <code>nav</code>, <code>main</code>, <code>section</code>, <code>aside</code>, and <code>footer</code> are all semantic elements. The <code>h1</code> and <code>ul</code> are also semantic and accessible.</p>
  908.  
  909.  
  910.  
  911. <p>Unless there is a custom component that needs to be created, then a <code>div</code> is fine to use, paired with <a href="https://www.w3.org/WAI/standards-guidelines/aria/" rel="noopener">ARIA (Accessible Rich Internet Applications)</a>. We can do a deep dive into ARIA in a later post. For now let’s focus…see what I did there…on this CSS pseudo class.</p>
  912.  
  913.  
  914.  
  915. <p>The <code>:focus-within</code> pseudo class allows you to select an element when any descendent element it contains has focus.</p>
  916.  
  917.  
  918. <h2 class="wp-block-heading" id="focuswithin-in-action"><br><code>:focus-within</code> in Action!<br></h2>
  919.  
  920. <h3 class="wp-block-heading" id="html">HTML</h3>
  921.  
  922.  
  923. <pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;form>
  924.  &lt;div>
  925.    &lt;label for="firstName">First Name&lt;/label>&lt;input id="firstName" type="text">
  926.  &lt;/div>
  927.  &lt;div>
  928.    &lt;label for="lastName">Last Name&lt;/label>&lt;input id="lastName" type="text">
  929.  &lt;/div>
  930.  &lt;div>
  931.    &lt;label for="phone">Phone Number&lt;/label>&lt;input id="phone" type="text">
  932.  &lt;/div>
  933.  &lt;div>
  934.    &lt;label for="message">Message&lt;/label>&lt;textarea id="message">&lt;/textarea>
  935.  &lt;/div>
  936. &lt;/form></code></pre>
  937.  
  938.  
  939. <h3 class="wp-block-heading" id="css">CSS</h3>
  940.  
  941.  
  942. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">form:focus-within {
  943.  background: #ff7300;
  944.  color: black;
  945.  padding: 10px;
  946. }</code></pre>
  947.  
  948.  
  949.  
  950. <p class="ticss-af97e0c0">The example code above will add a background color of orange, add some padding, and change the color of the labels to black.</p>
  951.  
  952.  
  953.  
  954. <p>The final product looks something like below. Of course the possibilities are endless to change up the styling, but this should get you on a good track to make the web more accessible for everyone!</p>
  955.  
  956.  
  957. <div class="wp-block-image">
  958. <figure class="aligncenter size-full"><a href="https://codepen.io/chrisdemars/pen/PRXbpg" rel="noopener"><img loading="lazy" decoding="async" width="460" height="362" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/03/focus-within-codepen-example.gif?resize=460%2C362&#038;ssl=1" alt="First example of focus-within css class highlighting the form background and changing the label text color." class="wp-image-377570" data-recalc-dims="1"/></a></figure></div>
  959.  
  960.  
  961. <p>Another use case for using <code>:focus-within</code> would be turning the labels bold, a different color, or enlarging them for users with low vision. The example code for that would look something like below.</p>
  962.  
  963.  
  964. <h3 class="wp-block-heading" id="html">HTML</h3>
  965.  
  966.  
  967. <pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;form>
  968.  &lt;h1>:focus-within part 2!&lt;/h1>
  969.  &lt;label for="firstName">First Name: &lt;input name="firstName" type="text" />&lt;/label>
  970.  &lt;label for="lastName">Last Name: &lt;input name="lastName" type="text" />&lt;/label>
  971.  &lt;label for="phone">Phone number: &lt;input type="tel" id="phone" />&lt;/label>
  972.  &lt;label for="message">Message: &lt;textarea name="message" id="message"/>&lt;/textarea>&lt;/label>
  973. &lt;/form>
  974. </code></pre>
  975.  
  976.  
  977. <h3 class="wp-block-heading" id="css">CSS</h3>
  978.  
  979.  
  980. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">label {
  981.  display: block;
  982.  margin-right: 10px;
  983.  padding-bottom: 15px;
  984. }
  985.  
  986. label:focus-within {
  987.  font-weight: bold;
  988.  color: red;
  989.  font-size: 1.6em;
  990. }
  991. </code></pre>
  992.  
  993.  
  994. <div class="wp-block-image">
  995. <figure class="aligncenter size-full"><a href="https://codepen.io/chrisdemars/pen/KKYWLGr" rel="noopener"><img loading="lazy" decoding="async" width="480" height="404" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/03/focus-within-part-2-example.gif?resize=480%2C404&#038;ssl=1" alt="Showing how to bold, change color and font size of labels in a form using :focus-within." class="wp-image-377575" data-recalc-dims="1"/></a></figure></div>
  996.  
  997.  
  998. <p><code>:focus-within</code> also has great browser support across the board according to <a href="https://caniuse.com/?search=focus-within" target="_blank" rel="noreferrer noopener">Can I use</a>.</p>
  999.  
  1000.  
  1001.  
  1002. <figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="448" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/03/can-i-use-support.png?resize=1024%2C448&#038;ssl=1" alt="Focus within css pseudo class browser support according to the can i use website." class="wp-image-377577" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/03/can-i-use-support.png?resize=1024%2C448&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/03/can-i-use-support.png?resize=300%2C131&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/03/can-i-use-support.png?resize=768%2C336&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/03/can-i-use-support.png?resize=1536%2C672&amp;ssl=1 1536w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/03/can-i-use-support.png?resize=2048%2C897&amp;ssl=1 2048w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /></figure>
  1003.  
  1004.  
  1005. <h3 class="wp-block-heading" id="conclusion"><strong>Conclusion</strong></h3>
  1006.  
  1007.  
  1008. <p>Creating amazing, accessible user experience should always be a top priority when shipping software, not just externally but internally as well. We as developers, all the way up to senior leadership need to be cognizant of the challenges others face and how we can be ambassadors for the web platform to make it a better place.</p>
  1009.  
  1010.  
  1011.  
  1012. <p>Using technology like semantic markup and CSS to create inclusive spaces is a crucial part in making the web a better place, let’s continue moving forward and changing lives.</p>
  1013.  
  1014.  
  1015.  
  1016. <p>Check out another great resource here on CSS-Tricks on <a href="https://css-tricks.com/almanac/selectors/f/focus-within/">using :focus-within</a>.</p>
  1017. <hr />
  1018. <p><small><a rel="nofollow" href="https://css-tricks.com/accessible-forms-with-pseudo-classes/">Accessible Forms with Pseudo Classes</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
  1019. ]]></content:encoded>
  1020. <wfw:commentRss>https://css-tricks.com/accessible-forms-with-pseudo-classes/feed/</wfw:commentRss>
  1021. <slash:comments>13</slash:comments>
  1022. <post-id xmlns="com-wordpress:feed-additions:1">377565</post-id> </item>
  1023. <item>
  1024. <title>Passkeys: What the Heck and Why?</title>
  1025. <link>https://css-tricks.com/passkeys-what-the-heck-and-why/</link>
  1026. <comments>https://css-tricks.com/passkeys-what-the-heck-and-why/#respond</comments>
  1027. <dc:creator><![CDATA[Neal Fennimore]]></dc:creator>
  1028. <pubDate>Wed, 12 Apr 2023 17:41:53 +0000</pubDate>
  1029. <category><![CDATA[Article]]></category>
  1030. <category><![CDATA[passkeys]]></category>
  1031. <category><![CDATA[security]]></category>
  1032. <category><![CDATA[webauthn]]></category>
  1033. <guid isPermaLink="false">https://css-tricks.com/?p=377305</guid>
  1034.  
  1035. <description><![CDATA[<p>These things called&#160;<strong>passkeys</strong>&#160;sure are making the rounds these days. They were a main attraction at&#160;<a href="https://www.w3.org/2022/09/TPAC/demos/passkeys.html" rel="noopener">W3C TPAC 2022</a>, gained support in&#160;<a href="https://developer.apple.com/documentation/safari-release-notes/safari-16_1-release-notes/" rel="noopener">Safari 16</a>, are finding their way into&#160;<a href="https://developer.apple.com/passkeys/" rel="noopener">macOS and iOS</a>, and are slated to &#8230;</p>
  1036. <hr />
  1037. <p><small><a rel="nofollow" href="https://css-tricks.com/passkeys-what-the-heck-and-why/">Passkeys: What the Heck and Why?</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
  1038. ]]></description>
  1039. <content:encoded><![CDATA[
  1040. <p>These things called&nbsp;<strong>passkeys</strong>&nbsp;sure are making the rounds these days. They were a main attraction at&nbsp;<a href="https://www.w3.org/2022/09/TPAC/demos/passkeys.html" rel="noopener">W3C TPAC 2022</a>, gained support in&nbsp;<a href="https://developer.apple.com/documentation/safari-release-notes/safari-16_1-release-notes/" rel="noopener">Safari 16</a>, are finding their way into&nbsp;<a href="https://developer.apple.com/passkeys/" rel="noopener">macOS and iOS</a>, and are slated to be&nbsp;<a href="https://www.future.1password.com/passkeys/" rel="noopener">the future for password managers like 1Password</a>. They are&nbsp;<a href="https://passkeys.dev/device-support/" rel="noopener">already supported</a>&nbsp;in Android, and will soon find their way into Chrome OS and Windows in future releases.</p>
  1041.  
  1042.  
  1043.  
  1044. <p>Geeky OS security enhancements don’t exactly make big headlines in the front-end community, but it stands to reason that passkeys are going to be a “thing”. And considering how passwords and password apps affect the user experience of things like authentication and form processing, we might want to at least wrap our minds around them, so we know what’s coming.</p>
  1045.  
  1046.  
  1047.  
  1048. <p>That’s the point of this article. I’ve been studying and experimenting with passkeys — and the WebAuthn API they are built on top of — for some time now. Let me share what I’ve learned.</p>
  1049.  
  1050.  
  1051.  
  1052. <span id="more-377305"></span>
  1053.  
  1054.  
  1055. <h3 class="simpletoc-hidden wp-block-heading" id="table-of-contents">Table of contents</h3>
  1056.  
  1057. <ul class="simpletoc-list"   >
  1058. </li><li>
  1059. <a  href="#terminology">Terminology</a></li><li>
  1060. <a  href="#what-are-passkeys">What are passkeys?</a></li><li>
  1061. <a  href="#how-do-passkeys-replace-passwords">How do passkeys replace passwords?</a></li><li>
  1062. <a  href="#more-about-cryptography">More about cryptography</a></li><li>
  1063. <a  href="#how-do-we-access-passkeys">How do we access passkeys?</a></li><li>
  1064. <a  href="#the-difference-between-passkeys-and-webauthn">The difference between passkeys and WebAuthn</a></li><li>
  1065. <a  href="#the-process-in-a-nutshell">The process… in a nutshell</a></li><li>
  1066. <a  href="#the-meat-and-potatoes">The meat and potatoes</a></li><li>
  1067. <a  href="#some-downsides">Some downsides</a></li><li>
  1068. <a  href="#where-are-things-going">Where are things going?</a></li><li>
  1069. <a  href="#resources">Resources</a></li></ul>
  1070.  
  1071. <h3 class="wp-block-heading" id="terminology">Terminology</h3>
  1072.  
  1073.  
  1074. <p>Here’s the obligatory section of the terminology you’re going to want to know as we dig in. Like most tech, passkeys are wrought with esoteric verbiage and acronyms that are often roadblocks to understanding. I’ll try to de-mystify several for you here.</p>
  1075.  
  1076.  
  1077.  
  1078. <ul>
  1079. <li><strong>Relying Party:</strong>&nbsp;the server you will be authenticating against. We&#8217;ll use “server” to imply the Relying Party in this article.</li>
  1080.  
  1081.  
  1082.  
  1083. <li><strong>Client:</strong>&nbsp;in our case, the web browser or operating system.</li>
  1084.  
  1085.  
  1086.  
  1087. <li><strong>Authenticator:</strong>&nbsp;Software and/or hardware devices that allow generation and storage for public key pairs.</li>
  1088.  
  1089.  
  1090.  
  1091. <li><strong>FIDO</strong>: An open standards body that also creates specifications around FIDO credentials.</li>
  1092.  
  1093.  
  1094.  
  1095. <li><strong>WebAuthn</strong>: The underlying protocol for passkeys, Also known as a&nbsp;<a href="https://fidoalliance.org/fido2/" rel="noopener">FIDO2</a>&nbsp;credential or single-device FIDO credentials.</li>
  1096.  
  1097.  
  1098.  
  1099. <li><strong>Passkeys</strong>: WebAuthn, but with cloud syncing (also called multi-device FIDO credentials, discoverable credentials, or resident credentials).</li>
  1100.  
  1101.  
  1102.  
  1103. <li><strong>Public Key Cryptography:</strong>&nbsp;A generated key pair that includes a private and public key. Depending on the algorithm, it should either be used for signing and verification or encrypting and decrypting. This is also known as&nbsp;<em>asymmetric cryptography</em>.</li>
  1104.  
  1105.  
  1106.  
  1107. <li><strong>RSA:</strong>&nbsp;An acronym of the creators’ names, Rivest Shamir and Adel. RSA is an older, but still useful, family of public key cryptography based on factoring primes.</li>
  1108.  
  1109.  
  1110.  
  1111. <li><strong>Elliptic Curve Cryptography (ECC):</strong>&nbsp;A newer family of cryptography&nbsp;<a href="https://csrc.nist.gov/Projects/Elliptic-Curve-Cryptography" rel="noopener">based on elliptic curves</a>.</li>
  1112.  
  1113.  
  1114.  
  1115. <li><strong>ES256:</strong>&nbsp;An elliptic curve public key that uses an ECDSA signing algorithm (<a href="https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-5.pdf" rel="noopener">PDF</a>) with&nbsp;<a href="https://en.wikipedia.org/wiki/SHA-2" rel="noopener">SHA256</a>&nbsp;for hashing.</li>
  1116.  
  1117.  
  1118.  
  1119. <li><strong>RS256:</strong>&nbsp;Like ES256, but it uses RSA with&nbsp;<a href="https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/sign#rsassa-pkcs1-v1_5_2" rel="noopener">RSASSA-PKCS1-v1.5</a>&nbsp;and SHA256.</li>
  1120. </ul>
  1121.  
  1122.  
  1123. <h3 class="wp-block-heading" id="what-are-passkeys">What are passkeys?</h3>
  1124.  
  1125.  
  1126. <p>Before we can talk specifically about passkeys, we need to talk about another protocol called&nbsp;<a href="https://webauthn.guide/" rel="noopener">WebAuthn</a>&nbsp;(also known as FIDO2). Passkeys are a specification that is built on top of WebAuthn. WebAuthn allows for public key cryptography to replace passwords. We use some sort of security device, such as a hardware key or&nbsp;<a href="https://learn.microsoft.com/en-us/windows/security/information-protection/tpm/trusted-platform-module-top-node" rel="noopener">Trusted Platform Module</a>&nbsp;(TPM), to create private and public keys.</p>
  1127.  
  1128.  
  1129.  
  1130. <p>The public key is for anyone to use. The private key, however, cannot be removed from the device that generated it. This was one of the issues with WebAuthn; if you lose the device, you lose access.</p>
  1131.  
  1132.  
  1133.  
  1134. <p>Passkeys solves this by providing a cloud sync of your credentials. In other words, what you generate on your computer can now also be used on your phone (though confusingly, there are single-device credentials too).</p>
  1135.  
  1136.  
  1137.  
  1138. <p>Currently, at the time of writing, only iOS, macOS, and Android provide full support for cloud-synced passkeys, and even then, they are limited by the browser being used. Google and Apple provide an interface for syncing via their&nbsp;<a href="https://passwords.google.com/" rel="noopener">Google Password Manager</a> and&nbsp;<a href="https://support.apple.com/en-us/HT204085" rel="noopener">Apple iCloud Keychain</a>&nbsp;services, respectively.</p>
  1139.  
  1140.  
  1141. <h3 class="wp-block-heading" id="how-do-passkeys-replace-passwords">How do passkeys replace passwords?</h3>
  1142.  
  1143.  
  1144. <p>In public key cryptography, you can perform what is known as <em>signing</em>. Signing takes a piece of data and then runs it through a signing algorithm with the private key, where it can then be verified with the public key.</p>
  1145.  
  1146.  
  1147.  
  1148. <p>Anyone can generate a public key pair, and it&#8217;s not attributable to any person since any person could have generated it in the first place. What makes it useful is that only data signed with the private key can be verified with the public key. That&#8217;s the portion that replaces a password — a server stores the public key, and we sign in by verifying that we have the other half (e.g. private key), by signing a random challenge.</p>
  1149.  
  1150.  
  1151.  
  1152. <p>As an added benefit, since we&#8217;re storing the user&#8217;s public keys within a database, there is no longer concern with password breaches affecting millions of users. This reduces phishing, breaches, and a slew of other security issues that our password-dependent world currently faces. If a database is breached, all that&#8217;s stored in the user&#8217;s public keys, making it virtually useless to an attacker.</p>
  1153.  
  1154.  
  1155.  
  1156. <p>No more forgotten emails and their associated passwords, either! The browser will remember which credentials you used for which website — all you need to do is make a couple of clicks, and you&#8217;re logged in. You can provide a secondary means of verification to use the passkey, such as biometrics or a pin, but those are still much faster than the passwords of yesteryear.</p>
  1157.  
  1158.  
  1159. <h3 class="wp-block-heading" id="more-about-cryptography">More about cryptography</h3>
  1160.  
  1161.  
  1162. <p>Public key cryptography involves having a private and a public key (known as a key pair). The keys are generated together and have separate uses. For example, the private key is intended to be kept secret, and the public key is intended for whomever you want to exchange messages with.</p>
  1163.  
  1164.  
  1165.  
  1166. <p>When it comes to encrypting and decrypting a message, the recipient’s public key is used to encrypt a message so that only the recipient&#8217;s private key can decrypt the message. In security parlance, this is known as “providing confidentiality”. However, this doesn&#8217;t provide proof that the sender is who they say they are, as anyone can potentially use a public key to send someone an encrypted message.</p>
  1167.  
  1168.  
  1169.  
  1170. <p>There are cases where we need to verify that a message did indeed come from its sender. In these cases, we use signing and signature verification to ensure that the sender is who they say they are (also known as&nbsp;<em>authenticity</em>). In public key (also called&nbsp;<em>asymmetric</em>) cryptography, this is generally done by signing the hash of a message, so that only the public key can correctly verify it. The hash and the sender&#8217;s private key produce a signature after running it through an algorithm, and then anyone can verify the message came from the sender with the sender&#8217;s public key.</p>
  1171.  
  1172.  
  1173. <h3 class="wp-block-heading" id="how-do-we-access-passkeys">How do we access passkeys?</h3>
  1174.  
  1175.  
  1176. <p>To access passkeys, we first need to generate and store them somewhere. Some of this functionality can be provided with an authenticator. An&nbsp;<em>authenticator</em>&nbsp;is any hardware or software-backed device that provides the ability for cryptographic key generation. Think of those one-time passwords you get from&nbsp;<a href="https://support.google.com/accounts/answer/1066447?hl=en&amp;co=GENIE.Platform%3DAndroid" rel="noopener">Google Authenticator</a>,&nbsp;<a href="https://1password.com/" rel="noopener">1Password</a>, or&nbsp;<a href="https://www.lastpass.com/" rel="noopener">LastPass</a>, among others.</p>
  1177.  
  1178.  
  1179.  
  1180. <p>For example, a software authenticator can use the Trusted Platform Module (TPM) or secure enclave of a device to create credentials. The credentials can be then stored remotely and synced across devices e.g. passkeys. A hardware authenticator would be something like a&nbsp;<a href="https://www.yubico.com/" rel="noopener">YubiKey</a>, which can generate and store keys on the device itself.</p>
  1181.  
  1182.  
  1183.  
  1184. <p>To access the authenticator, the browser needs to have access to hardware, and for that, we need an interface. The interface we use here is the Client to Authenticator Protocol (CTAP). It allows access to different authenticators over different mechanisms. For example, we can access an authenticator over NFC, USB, and Bluetooth by utilizing CTAP.</p>
  1185.  
  1186.  
  1187.  
  1188. <p>One of the more interesting ways to use passkeys is by connecting your phone over Bluetooth to another device that might not support passkeys. When the devices are paired over Bluetooth, I can log into the browser on my computer using my phone as an intermediary!</p>
  1189.  
  1190.  
  1191. <h3 class="wp-block-heading" id="the-difference-between-passkeys-and-webauthn">The difference between passkeys and WebAuthn</h3>
  1192.  
  1193.  
  1194. <p>Passkeys and WebAuthn keys differ in several ways. First, passkeys are considered multi-device credentials and can be synced across devices. By contrast, WebAuthn keys are single-device credentials — a fancy way of saying you’re bound to one device for verification.</p>
  1195.  
  1196.  
  1197.  
  1198. <p>Second, to authenticate to a server, WebAuthn keys need to provide the user handle for login, after which an&nbsp;<code>allowCredentials</code>&nbsp;list is returned to the client from the server, which informs what credentials can be used to log in.&nbsp;<strong>Passkeys skip this step and use the server&#8217;s domain name to show which keys are already bound to that site.</strong>&nbsp;You’re able to select the passkey that is associated with that server, as it&#8217;s already known by your system.</p>
  1199.  
  1200.  
  1201.  
  1202. <p>Otherwise, the keys are cryptographically the same; they only differ in how they&#8217;re stored and what information they use to start the login process.</p>
  1203.  
  1204.  
  1205. <h3 class="wp-block-heading" id="the-process-in-a-nutshell">The process… in a nutshell</h3>
  1206.  
  1207.  
  1208. <p>The process for generating a WebAuthn or a passkey is very similar: get a challenge from the server and then use the&nbsp;<code>navigator.credentials.create</code>&nbsp;web API to generate a public key pair. Then, send the challenge and the public key back to the server to be stored.</p>
  1209.  
  1210.  
  1211.  
  1212. <p>Upon receiving the public key and challenge, the server validates the challenge and the session from which it was created. If that checks out, the public key is stored, as well as any other relevant information like the user identifier or attestation data, in the database.</p>
  1213.  
  1214.  
  1215.  
  1216. <p>The user has one more step — retrieve another challenge from the server and use the&nbsp;<code>navigator.credentials.get</code>&nbsp;API to sign the challenge. We send back the signed challenge to the server, and the server verifies the challenge, then logs us in if the signature passes.</p>
  1217.  
  1218.  
  1219.  
  1220. <p>There is, of course, quite a bit more to each step. But that is generally how we&#8217;d log into a website using WebAuthn or passkeys.</p>
  1221.  
  1222.  
  1223. <h3 class="wp-block-heading" id="the-meat-and-potatoes">The meat and potatoes</h3>
  1224.  
  1225.  
  1226. <p>Passkeys are used in two distinct phases: the&nbsp;<strong>attestation</strong>&nbsp;and&nbsp;<strong>assertion</strong>&nbsp;phases.</p>
  1227.  
  1228.  
  1229.  
  1230. <p>The attestation phase can also be thought of as the registration phase. You&#8217;d sign up with an email and password for a new website, however, in this case, we&#8217;d be using our passkey.</p>
  1231.  
  1232.  
  1233.  
  1234. <p>The assertion phase is similar to how you&#8217;d log in to a website after signing up.</p>
  1235.  
  1236.  
  1237. <h4 class="wp-block-heading" id="attestation">Attestation</h4>
  1238.  
  1239.  
  1240. <figure class="wp-block-image size-full"><a href="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/attestation.png?ssl=1"><img loading="lazy" decoding="async" width="736" height="569" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/attestation.png?resize=736%2C569&#038;ssl=1" alt="" class="wp-image-377328" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/attestation.png?w=736&amp;ssl=1 736w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/attestation.png?resize=300%2C232&amp;ssl=1 300w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /></a><figcaption class="wp-element-caption"><a href="https://css-tricks.com/wp-content/uploads/2023/02/attestation.png">View full size</a></figcaption></figure>
  1241.  
  1242.  
  1243.  
  1244. <p>The&nbsp;<code>navigator.credentials.create</code>&nbsp;API is the focus of our attestation phase. We&#8217;re registered as a new user in the system and need to generate a new public key pair. However, we need to specify what kind of key pair we want to generate. That means we need to provide options to&nbsp;<code>navigator.credentials.create</code>.</p>
  1245.  
  1246.  
  1247.  
  1248. <pre class="wp-block-csstricks-code-block language-javascript" data-line=""><code>// The `challenge` is random and has to come from the server
  1249. const publicKey: PublicKeyCredentialCreationOptions = {
  1250.  challenge: safeEncode(challenge),
  1251.  rp: {
  1252.    id: window.location.host,
  1253.    name: document.title,
  1254.  },
  1255.  user: {
  1256.    id: new TextEncoder().encode(crypto.randomUUID()), // Why not make it random?
  1257.    name: 'Your username',
  1258.    displayName: 'Display name in browser',
  1259.  },
  1260.  pubKeyCredParams: [
  1261.    {
  1262.      type: 'public-key',
  1263.      alg: -7, // ES256
  1264.    },
  1265.    {
  1266.      type: 'public-key',
  1267.      alg: -256, // RS256
  1268.    },
  1269.  ],
  1270.  authenticatorSelection: {
  1271.    userVerification: 'preferred', // Do you want to use biometrics or a pin?
  1272.    residentKey: 'required', // Create a resident key e.g. passkey
  1273.  },
  1274.  attestation: 'indirect', // indirect, direct, or none
  1275.  timeout: 60_000,
  1276. };</code></pre>
  1277.  
  1278.  
  1279.  
  1280. <pre class="wp-block-csstricks-code-block language-javascript" data-line=""><code>const pubKeyCredential: PublicKeyCredential = await navigator.credentials.create({
  1281.  publicKey
  1282. });
  1283. const {
  1284.  id // the key id a.k.a. kid
  1285. } = pubKeyCredential;
  1286. const pubKey = pubKeyCredential.response.getPublicKey();
  1287. const { clientDataJSON, attestationObject } = pubKeyCredential.response;
  1288. const { type, challenge, origin } = JSON.parse(new TextDecoder().decode(clientDataJSON));
  1289. // Send data off to the server for registration</code></pre>
  1290.  
  1291.  
  1292.  
  1293. <p>We&#8217;ll get&nbsp;<a href="https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential" rel="noopener"><code>PublicKeyCredential</code></a>&nbsp;which contains an&nbsp;<a href="https://developer.mozilla.org/en-US/docs/Web/API/AuthenticatorAttestationResponse" rel="noopener"><code>AuthenticatorAttestationResponse</code></a>&nbsp;that comes back after creation. The credential has the generated key pair’s ID.</p>
  1294.  
  1295.  
  1296.  
  1297. <p>The response provides a couple of bits of useful information. First, we have our public key in this response, and we need to send that to the server to be stored. Second, we also get back the&nbsp;<code>clientDataJSON</code>&nbsp;property which we can decode, and from there, get back the&nbsp;<code>type</code>,&nbsp;<code>challenge</code>, and&nbsp;<code>origin</code>&nbsp;of the passkey.</p>
  1298.  
  1299.  
  1300.  
  1301. <p>For attestation, we want to validate the&nbsp;<code>type</code>,&nbsp;<code>challenge</code>, and&nbsp;<code>origin</code>&nbsp;on the server, as well as store the public key with its identifier, e.g. kid. We can also optionally store the&nbsp;<code>attestationObject</code>&nbsp;if we wish. Another useful property to store is the&nbsp;<a href="https://www.iana.org/assignments/cose/cose.xhtml#algorithms" rel="noopener">COSE</a>&nbsp;algorithm, which is defined above in our &nbsp;<code>PublicKeyCredentialCreationOptions</code>&nbsp;with&nbsp;<code>alg: -7</code>&nbsp;or&nbsp;<code>alg: -256</code>, in order to easily verify any signed challenges in the assertion phase.</p>
  1302.  
  1303.  
  1304. <h4 class="wp-block-heading" id="assertion">Assertion</h4>
  1305.  
  1306.  
  1307. <figure class="wp-block-image size-full"><a href="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/assertion.png?ssl=1"><img loading="lazy" decoding="async" width="717" height="516" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/assertion.png?resize=717%2C516&#038;ssl=1" alt="" class="wp-image-377330" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/assertion.png?w=717&amp;ssl=1 717w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/assertion.png?resize=300%2C216&amp;ssl=1 300w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /></a><figcaption class="wp-element-caption"><a href="https://css-tricks.com/wp-content/uploads/2023/02/assertion.png">View full size</a></figcaption></figure>
  1308.  
  1309.  
  1310.  
  1311. <p>The&nbsp;<code>navigator.credentials.get</code>&nbsp;API will be the focus of the assertion phase. Conceptually, this would be where the user logs in to the web application after signing up.</p>
  1312.  
  1313.  
  1314.  
  1315. <pre class="wp-block-csstricks-code-block language-javascript" data-line=""><code>// The `challenge` is random and has to come from the server
  1316. const publicKey: PublicKeyCredentialRequestOptions = {
  1317.  challenge: new TextEncoder().encode(challenge),
  1318.  rpId: window.location.host,
  1319.  timeout: 60_000,
  1320. };</code></pre>
  1321.  
  1322.  
  1323.  
  1324. <pre class="wp-block-csstricks-code-block language-javascript" data-line=""><code>const publicKeyCredential: PublicKeyCredential = await navigator.credentials.get({
  1325.  publicKey,
  1326.  mediation: 'optional',
  1327. });
  1328. const {
  1329.  id // the key id, aka kid
  1330. } = pubKeyCredential;
  1331. const { clientDataJSON, attestationObject, signature, userHandle } = pubKeyCredential.response;
  1332. const { type, challenge, origin } = JSON.parse(new TextDecoder().decode(clientDataJSON));
  1333. // Send data off to the server for verification</code></pre>
  1334.  
  1335.  
  1336.  
  1337. <p>We&#8217;ll again get a&nbsp;<a href="https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential" rel="noopener"><code>PublicKeyCredential</code></a>&nbsp;with an <a href="https://developer.mozilla.org/en-US/docs/Web/API/AuthenticatorAssertionResponse" rel="noopener"><code>AuthenticatorAssertionResponse</code></a>&nbsp;this time. The credential again includes the key identifier.</p>
  1338.  
  1339.  
  1340.  
  1341. <p>We also get the&nbsp;<code>type</code>,&nbsp;<code>challenge</code>, and&nbsp;<code>origin</code>&nbsp;from the&nbsp;<code>clientDataJSON</code>&nbsp;again. The&nbsp;<code>signature</code>&nbsp;is now included in the response, as well as the <code>authenticatorData</code>. We&#8217;ll need those and the&nbsp;<code>clientDataJSON</code>&nbsp;to verify that this was signed with the private key.</p>
  1342.  
  1343.  
  1344.  
  1345. <p>The&nbsp;<a href="https://developer.mozilla.org/en-US/docs/Web/API/AuthenticatorAssertionResponse/authenticatorData" rel="noopener"><code>authenticatorData</code></a>&nbsp;includes some properties that are worth tracking First is the SHA256 hash of the origin you&#8217;re using, located within the first 32 bytes, which is useful for verifying that request comes from the same origin server. Second is the&nbsp;<code>signCount</code>, which is from byte 33 to 37. This is generated from the authenticator and should be compared to its previous value to ensure that nothing fishy is going on with the key. The value should always 0 when it’s a multi-device passkey and should be randomly larger than the previous signCount when it’s a single-device passkey.</p>
  1346.  
  1347.  
  1348.  
  1349. <p>Once you&#8217;ve asserted your login, you should be logged in —&nbsp;<em>congratulations</em>! Passkeys is a pretty great protocol, but it does come with some caveats.</p>
  1350.  
  1351.  
  1352. <h3 class="wp-block-heading" id="some-downsides">Some downsides</h3>
  1353.  
  1354.  
  1355. <p>There&#8217;s a lot of upside to Passkeys, however, there are some issues with it at the time of this writing. For one thing, passkeys is somewhat still early support-wise, with only single-device credentials allowed on Windows and very little support for Linux systems.&nbsp;<a href="http://passkeys.dev/" rel="noopener">Passkeys.dev</a>&nbsp;provides a&nbsp;<a href="https://passkeys.dev/device-support/" rel="noopener">nice table that’s sort of like the Caniuse of this protocol</a>.</p>
  1356.  
  1357.  
  1358.  
  1359. <p>Also, Google&#8217;s and Apple&#8217;s passkeys platforms do not communicate with each other. If you want to get your credentials from your Android phone over to your iPhone&#8230; well, you&#8217;re out of luck for now. That&#8217;s not to say there is no interoperability! You can log in to your computer by using your phone as an authenticator. But it would be much cleaner just to have it built into the operating system and synced without it being locked at the vendor level.</p>
  1360.  
  1361.  
  1362. <h3 class="wp-block-heading" id="where-are-things-going">Where are things going?</h3>
  1363.  
  1364.  
  1365. <p>What does the passkeys protocol of the future look like? It looks pretty good! Once it gains support from more operating systems, there should be an uptake in usage, and you&#8217;ll start seeing it used more and more in the wild. Some&nbsp;<a href="https://www.future.1password.com/passkeys" rel="noopener">password managers</a>&nbsp;are even going to support them first-hand.</p>
  1366.  
  1367.  
  1368.  
  1369. <p>Passkeys are by no means only supported on the web.&nbsp;<a href="https://developer.android.com/training/sign-in/passkeys" rel="noopener">Android</a>&nbsp;and&nbsp;<a href="https://developer.apple.com/documentation/authenticationservices/public-private_key_authentication/supporting_passkeys" rel="noopener">iOS</a>&nbsp;will both support native passkeys as first-class citizens. We&#8217;re still in the early days of all this, but expect to see it mentioned more and more.</p>
  1370.  
  1371.  
  1372.  
  1373. <p>After all, we eliminate the need for passwords, and by doing so, make the world safer for it!</p>
  1374.  
  1375.  
  1376. <h3 class="wp-block-heading" id="resources">Resources</h3>
  1377.  
  1378.  
  1379. <p>Here are some more resources if you want to learn more about Passkeys. There’s also a repository and demo I put together for this article.</p>
  1380.  
  1381.  
  1382.  
  1383. <ul>
  1384. <li><a href="https://passkeys.neal.codes/" rel="noopener">Live Demo</a> (no actual information is collected by the form)</li>
  1385.  
  1386.  
  1387.  
  1388. <li><a href="https://github.com/nealfennimore/passkeys" rel="noopener">Demo GitHub Repository</a></li>
  1389.  
  1390.  
  1391.  
  1392. <li><a href="https://developers.yubico.com/Passkeys/" rel="noopener">YubiKey Documentation</a></li>
  1393.  
  1394.  
  1395.  
  1396. <li><a href="https://passkeys.dev/" rel="noopener">Passkeys.dev</a></li>
  1397.  
  1398.  
  1399.  
  1400. <li><a href="https://www.passkeys.io/" rel="noopener">Passkeys.io</a></li>
  1401.  
  1402.  
  1403.  
  1404. <li><a href="https://webauthn.io/" rel="noopener">Webauthn.io</a></li>
  1405. </ul>
  1406. <hr />
  1407. <p><small><a rel="nofollow" href="https://css-tricks.com/passkeys-what-the-heck-and-why/">Passkeys: What the Heck and Why?</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
  1408. ]]></content:encoded>
  1409. <wfw:commentRss>https://css-tricks.com/passkeys-what-the-heck-and-why/feed/</wfw:commentRss>
  1410. <slash:comments>0</slash:comments>
  1411. <post-id xmlns="com-wordpress:feed-additions:1">377305</post-id> </item>
  1412. <item>
  1413. <title>Some Cross-Browser DevTools Features You Might Not Know</title>
  1414. <link>https://css-tricks.com/some-cross-browser-devtools-features-you-might-not-know/</link>
  1415. <comments>https://css-tricks.com/some-cross-browser-devtools-features-you-might-not-know/#comments</comments>
  1416. <dc:creator><![CDATA[Pankaj Parashar]]></dc:creator>
  1417. <pubDate>Wed, 22 Mar 2023 20:22:42 +0000</pubDate>
  1418. <category><![CDATA[Article]]></category>
  1419. <category><![CDATA[cross-browser]]></category>
  1420. <category><![CDATA[DevTools]]></category>
  1421. <guid isPermaLink="false">https://css-tricks.com/?p=377264</guid>
  1422.  
  1423. <description><![CDATA[<p>I spend a lot of time in DevTools, and I’m sure you do too. Sometimes I even bounce between them, especially when I’m debugging cross-browser issues. DevTools is a lot like browsers themselves — not all of the features in &#8230;</p>
  1424. <hr />
  1425. <p><small><a rel="nofollow" href="https://css-tricks.com/some-cross-browser-devtools-features-you-might-not-know/">Some Cross-Browser DevTools Features You Might Not Know</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
  1426. ]]></description>
  1427. <content:encoded><![CDATA[
  1428. <p>I spend a lot of time in DevTools, and I’m sure you do too. Sometimes I even bounce between them, especially when I’m debugging cross-browser issues. DevTools is a lot like browsers themselves — not all of the features in one browser’s DevTools will be the same or supported in another browser’s DevTools.</p>
  1429.  
  1430.  
  1431.  
  1432. <p>But there are quite a few DevTools features that are interoperable, even some lesser-known ones that I’m about to share with you.</p>
  1433.  
  1434.  
  1435.  
  1436. <p>For the sake of brevity, I use “Chromium” to refer to all Chromium-based browsers, like Chrome, Edge, and Opera, in the article. Many of the DevTools in them offer the exact same features and capabilities as one another, so this is just my shorthand for referring to all of them at once.</p>
  1437.  
  1438.  
  1439.  
  1440. <span id="more-377264"></span>
  1441.  
  1442.  
  1443. <h3 class="wp-block-heading" id="search-nodes-in-the-dom-tree">Search nodes in the DOM tree</h3>
  1444.  
  1445.  
  1446. <p>Sometimes the DOM tree is full of nodes nested in nodes that are nested in other nodes, and so on. That makes it pretty tough to find the exact one you’re looking for, but you can quickly search the DOM tree using <code>Cmd</code> + <code>F</code> (macOS) or <code>Ctrl</code> + <code>F</code> (Windows).</p>
  1447.  
  1448.  
  1449.  
  1450. <p>Additionally, you can also search using a valid CSS selector, like <code>.red</code>, or using an XPath, like <code>//div/h1</code>.</p>
  1451.  
  1452.  
  1453.  
  1454. <figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="2378" height="1048" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674209097213_image-1.png?resize=2378%2C1048&#038;ssl=1" alt="DevTools screenshots of all three browsers." class="wp-image-377265" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674209097213_image-1.png?w=2378&amp;ssl=1 2378w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674209097213_image-1.png?resize=300%2C132&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674209097213_image-1.png?resize=1024%2C451&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674209097213_image-1.png?resize=768%2C338&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674209097213_image-1.png?resize=1536%2C677&amp;ssl=1 1536w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674209097213_image-1.png?resize=2048%2C903&amp;ssl=1 2048w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /><figcaption class="wp-element-caption">Searching text in Chrome DevTools (left), selectors in Firefox DevTools (center), and XPath in Safari DevTools (right)</figcaption></figure>
  1455.  
  1456.  
  1457.  
  1458. <p>In Chromium browsers, the focus automatically jumps to the node that matches the search criteria as you type, which could be annoying if you are working with longer search queries or a large DOM tree. Fortunately, you can disable this behavior by heading to <strong>Settings</strong> (<code>F1</code>) → <strong>Preferences</strong> → <strong>Global</strong> → <strong>Search as you type</strong> → <strong>Disable</strong>.</p>
  1459.  
  1460.  
  1461.  
  1462. <p>After you have located the node in the DOM tree, you can scroll the page to bring the node within the viewport by right-clicking on the nod, and selecting “Scroll into view”.</p>
  1463.  
  1464.  
  1465.  
  1466. <figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1786" height="1118" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1675742347357_image.png?resize=1786%2C1118&#038;ssl=1" alt="Showing a highlighted node on a webpage with a contextual menu open to scroll into view" class="wp-image-377270" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1675742347357_image.png?w=1786&amp;ssl=1 1786w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1675742347357_image.png?resize=300%2C188&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1675742347357_image.png?resize=1024%2C641&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1675742347357_image.png?resize=768%2C481&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1675742347357_image.png?resize=1536%2C962&amp;ssl=1 1536w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /></figure>
  1467.  
  1468.  
  1469. <h3 class="wp-block-heading" id="access-nodes-from-the-console">Access nodes from the console</h3>
  1470.  
  1471.  
  1472. <p>DevTools provides many different ways to access a DOM node directly from the console.</p>
  1473.  
  1474.  
  1475.  
  1476. <p>For example, you can use <code>$0</code> to access the currently selected node in the DOM tree. Chromium browsers take this one step further by allowing you to access nodes selected in the reverse chronological order of historic selection using, <code>$1</code>, <code>$2</code>, <code>$3</code>, etc.</p>
  1477.  
  1478.  
  1479.  
  1480. <figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1682" height="1028" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674396487537_image-3.png?resize=1682%2C1028&#038;ssl=1" alt="Currently selected node accessed from the Console in Edge DevTools" class="wp-image-377271" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674396487537_image-3.png?w=1682&amp;ssl=1 1682w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674396487537_image-3.png?resize=300%2C183&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674396487537_image-3.png?resize=1024%2C626&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674396487537_image-3.png?resize=768%2C469&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674396487537_image-3.png?resize=1536%2C939&amp;ssl=1 1536w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /></figure>
  1481.  
  1482.  
  1483.  
  1484. <p>Another thing that Chromium browsers allow you to do is copy the node path as a JavaScript expression in the form of <code>document.querySelector</code> by right-clicking on the node, and selecting <strong>Copy</strong> → <strong>Copy JS path</strong>, which can then be used to access the node in the console.</p>
  1485.  
  1486.  
  1487.  
  1488. <figure class="wp-block-video wp-block-embed is-type-video is-provider-videopress"><div class="wp-block-embed__wrapper">
  1489. <iframe title="VideoPress Video Player" aria-label='VideoPress Video Player' width='500' height='305' src='https://videopress.com/embed/jwsHG7QN?cover=1&amp;playsinline=1&amp;preloadContent=metadata&amp;useAverageColor=1&amp;hd=0' frameborder='0' allowfullscreen data-resize-to-parent="true" allow='clipboard-write'></iframe><script src='https://v0.wordpress.com/js/next/videopress-iframe.js?m=1674852142'></script>
  1490. </div></figure>
  1491.  
  1492.  
  1493.  
  1494. <p>Here’s another way to access a DOM node directly from the console: as a temporary variable. This option is available by right-clicking on the node and selecting an option. That option is labeled differently in each browser&#8217;s DevTools:</p>
  1495.  
  1496.  
  1497.  
  1498. <ul>
  1499. <li><strong>Chromium</strong>: Right click → “Store as global variable”</li>
  1500.  
  1501.  
  1502.  
  1503. <li><strong>Firefox</strong>: Right click → “Use in Console”</li>
  1504.  
  1505.  
  1506.  
  1507. <li><strong>Safari</strong>: Right click → “Log Element”</li>
  1508. </ul>
  1509.  
  1510.  
  1511.  
  1512. <figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="2560" height="1440" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674633378298_Myproject-1.png?resize=2560%2C1440&#038;ssl=1" alt="Screenshot of DevTools contextual menus in all three browsers." class="wp-image-377274" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674633378298_Myproject-1.png?w=2560&amp;ssl=1 2560w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674633378298_Myproject-1.png?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674633378298_Myproject-1.png?resize=1024%2C576&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674633378298_Myproject-1.png?resize=768%2C432&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674633378298_Myproject-1.png?resize=1536%2C864&amp;ssl=1 1536w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674633378298_Myproject-1.png?resize=2048%2C1152&amp;ssl=1 2048w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /><figcaption class="wp-element-caption">Access a node as a temporary variable in the console, as shown in Chrome (left), Firefox (center), and Safari (right)</figcaption></figure>
  1513.  
  1514.  
  1515. <h3 class="wp-block-heading" id="visualize-elements-with-badges">Visualize elements with badges</h3>
  1516.  
  1517.  
  1518. <p>DevTools can help visualize elements that match certain properties by displaying a badge next to the node. Badges are clickable, and different browsers offer a variety of different badges.</p>
  1519.  
  1520.  
  1521.  
  1522. <p>In <strong>Safari</strong>, there is a badge button in the Elements panel toolbar which can be used to toggle the visibility of specific badges. For example, if a node has a <code>display: grid</code> or <code>display: inline-grid</code> CSS declaration applied to it, a <code>grid</code> badge is displayed next to it. Clicking on the badge will highlight grid areas, track sizes, line numbers, and more, on the page.</p>
  1523.  
  1524.  
  1525.  
  1526. <figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1738" height="1082" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674708049378_Screenshot2023-01-26at10.05.40.png?resize=1738%2C1082&#038;ssl=1" alt="A grid overlay visualized on top of a three-by-three grid." class="wp-image-377277" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674708049378_Screenshot2023-01-26at10.05.40.png?w=1738&amp;ssl=1 1738w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674708049378_Screenshot2023-01-26at10.05.40.png?resize=300%2C187&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674708049378_Screenshot2023-01-26at10.05.40.png?resize=1024%2C637&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674708049378_Screenshot2023-01-26at10.05.40.png?resize=768%2C478&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674708049378_Screenshot2023-01-26at10.05.40.png?resize=1536%2C956&amp;ssl=1 1536w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /><figcaption class="wp-element-caption">Grid overlay with badges in Safari DevTools</figcaption></figure>
  1527.  
  1528.  
  1529.  
  1530. <p>The badges that are currently supported in <strong>Firefox</strong>’s DevTools are listed in the Firefox <a href="https://firefox-source-docs.mozilla.org/devtools-user/page_inspector/how_to/examine_and_edit_html/index.html#html-tree" rel="noopener">source docs</a>. For example, a <code>scroll</code> badge indicates a scrollable element. Clicking on the badge highlights the element causing the overflow with an <code>overflow</code> badge next to it.</p>
  1531.  
  1532.  
  1533.  
  1534. <figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="852" height="379" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/overflow_badge.png?resize=852%2C379&#038;ssl=1" alt="Overflow badge in Firefox DevTools located in the HTML panel" class="wp-image-377279" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/overflow_badge.png?w=852&amp;ssl=1 852w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/overflow_badge.png?resize=300%2C133&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/overflow_badge.png?resize=768%2C342&amp;ssl=1 768w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /></figure>
  1535.  
  1536.  
  1537.  
  1538. <p>In <strong>Chromium</strong> browsers, you can right-click on any node and select <strong>“Badge settings…”</strong> to open a container that lists all of the available badges. For example, elements with <code>scroll-snap-type</code> will have a <code>scroll-snap</code> badge next to it, which on click, will toggle the <code>scroll-snap</code> overlay on that element.</p>
  1539.  
  1540.  
  1541.  
  1542. <figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1738" height="1082" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/tbuxzdUk.png?resize=1738%2C1082&#038;ssl=1" alt="" class="wp-image-377296" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/tbuxzdUk.png?w=1738&amp;ssl=1 1738w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/tbuxzdUk.png?resize=300%2C187&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/tbuxzdUk.png?resize=1024%2C637&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/tbuxzdUk.png?resize=768%2C478&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/tbuxzdUk.png?resize=1536%2C956&amp;ssl=1 1536w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /></figure>
  1543.  
  1544.  
  1545. <h3 class="wp-block-heading" id="taking-screenshots">Taking screenshots</h3>
  1546.  
  1547.  
  1548. <p>We’ve been able to take screenshots from some DevTools for a while now, but it’s now available in all of them and includes new ways to take full-page shots.</p>
  1549.  
  1550.  
  1551.  
  1552. <p>The process starts by right-clicking on the DOM node you want to capture. Then select the option to capture the node, which is labeled differently depending on which DevTools you’re using.</p>
  1553.  
  1554.  
  1555.  
  1556. <figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="2560" height="1700" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674721274966_Myproject-1.png?resize=2560%2C1700&#038;ssl=1" alt="Screenshot of DevTools in all three browsers." class="wp-image-377282" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674721274966_Myproject-1.png?w=2560&amp;ssl=1 2560w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674721274966_Myproject-1.png?resize=300%2C199&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674721274966_Myproject-1.png?resize=1024%2C680&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674721274966_Myproject-1.png?resize=768%2C510&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674721274966_Myproject-1.png?resize=1536%2C1020&amp;ssl=1 1536w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674721274966_Myproject-1.png?resize=2048%2C1360&amp;ssl=1 2048w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /><figcaption class="wp-element-caption">Chrome (left), Safari (middle), and Firefox (right)</figcaption></figure>
  1557.  
  1558.  
  1559.  
  1560. <p>Repeat the same steps on the <code>html</code> node to take a full-page screenshot. When you do, though, it’s worth noting that Safari retains the transparency of the element’s background color — Chromium and Firefox will capture it as a white background.</p>
  1561.  
  1562.  
  1563.  
  1564. <figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1506" height="1082" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674721838803_Screenshot2023-01-26at14.00.01.png?resize=1506%2C1082&#038;ssl=1" alt="Two screenshots of the same element, one with a background and one without." class="wp-image-377283" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674721838803_Screenshot2023-01-26at14.00.01.png?w=1506&amp;ssl=1 1506w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674721838803_Screenshot2023-01-26at14.00.01.png?resize=300%2C216&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674721838803_Screenshot2023-01-26at14.00.01.png?resize=1024%2C736&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674721838803_Screenshot2023-01-26at14.00.01.png?resize=768%2C552&amp;ssl=1 768w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /><figcaption class="wp-element-caption">Comparing screenshots in Safari (left) and Chromium (right)</figcaption></figure>
  1565.  
  1566.  
  1567.  
  1568. <p>There’s another option! You can take a “responsive” screenshot of the page, which allows you to capture the page at a specific viewport width. As you might expect, each browser has different ways to get there.</p>
  1569.  
  1570.  
  1571.  
  1572. <ul>
  1573. <li><strong>Chromium</strong>: <code>Cmd</code> + <code>Shift</code> + <code>M</code> (macOS) or <code>Ctrl</code> + <code>Shift</code> + <code>M</code> (Windows). Or click the “Devices” icon next to the “Inspect” icon.</li>
  1574.  
  1575.  
  1576.  
  1577. <li><strong>Firefox</strong>: Tools → Browser Tools → “Responsive Design Mode”</li>
  1578.  
  1579.  
  1580.  
  1581. <li><strong>Safari</strong>: Develop → “Enter Responsive Design Mode”</li>
  1582. </ul>
  1583.  
  1584.  
  1585.  
  1586. <figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="2550" height="1700" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674755907874_screenshot.png?resize=2550%2C1700&#038;ssl=1" alt="Enter responsive mode options in DevTools for all three browsers." class="wp-image-377285" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674755907874_screenshot.png?w=2550&amp;ssl=1 2550w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674755907874_screenshot.png?resize=300%2C200&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674755907874_screenshot.png?resize=1024%2C683&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674755907874_screenshot.png?resize=768%2C512&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674755907874_screenshot.png?resize=1536%2C1024&amp;ssl=1 1536w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_7E25E76B5C1A2A0120D43B477F8F8B0FA75578B215D36F3A918DC29D997AA0F4_1674755907874_screenshot.png?resize=2048%2C1365&amp;ssl=1 2048w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /><figcaption class="wp-element-caption">Launching responsive design mode in Safari (left), Firefox (right), and Chromium (bottom)</figcaption></figure>
  1587.  
  1588.  
  1589. <h3 class="wp-block-heading" id="chrome-tip-inspect-the-top-layer">Chrome tip: Inspect the top layer</h3>
  1590.  
  1591.  
  1592. <p>Chrome lets you visualize and inspect top-layer elements, like a dialog, alert, or modal. When an element is added to the <code>#top-layer</code>, it gets a <code>top-layer</code> badge next to it, which on click, jumps you to the top-layer container located just after the <code>&lt;/html&gt;</code> tag.</p>
  1593.  
  1594.  
  1595.  
  1596. <p>The order of the elements in the <code>top-layer</code> container follows the stacking order, which means the last one is on the top. Click the <code>reveal</code> badge to jump back to the node.</p>
  1597.  
  1598.  
  1599.  
  1600. <figure class="wp-block-video wp-block-embed is-type-video is-provider-videopress"><div class="wp-block-embed__wrapper">
  1601. <iframe title="VideoPress Video Player" aria-label='VideoPress Video Player' width='500' height='315' src='https://videopress.com/embed/jljQbj3P?cover=1&amp;playsinline=1&amp;preloadContent=metadata&amp;useAverageColor=1&amp;hd=0' frameborder='0' allowfullscreen data-resize-to-parent="true" allow='clipboard-write'></iframe><script src='https://v0.wordpress.com/js/next/videopress-iframe.js?m=1674852142'></script>
  1602. </div></figure>
  1603.  
  1604.  
  1605. <h3 class="wp-block-heading" id="firefox-tip-jump-to-id">Firefox tip: Jump to ID</h3>
  1606.  
  1607.  
  1608. <p>Firefox links the element referencing the ID attribute to its target element in the same DOM and highlights it with an underline. Use <code>CMD</code> + <code>Click</code> (macOS) or <code>CTRL</code> + <code>Click</code> (Windows) )to jump to the target element with the identifier.</p>
  1609.  
  1610.  
  1611.  
  1612. <figure class="wp-block-video wp-block-embed is-type-video is-provider-videopress"><div class="wp-block-embed__wrapper">
  1613. <iframe title="VideoPress Video Player" aria-label='VideoPress Video Player' width='500' height='315' src='https://videopress.com/embed/qWuqfQTh?cover=1&amp;playsinline=1&amp;preloadContent=metadata&amp;useAverageColor=1&amp;hd=0' frameborder='0' allowfullscreen data-resize-to-parent="true" allow='clipboard-write'></iframe><script src='https://v0.wordpress.com/js/next/videopress-iframe.js?m=1674852142'></script>
  1614. </div></figure>
  1615.  
  1616.  
  1617. <h3 class="wp-block-heading" id="wrapping-up">Wrapping up</h3>
  1618.  
  1619.  
  1620. <p>Quite a few things, right? It’s awesome that there are some incredibly useful DevTools features that are supported in Chromium, Firefox, and Safari alike. Are there any other lesser-known features supported by all three that you like?</p>
  1621.  
  1622.  
  1623.  
  1624. <p>There are a few resources I keep close by to stay on top of what’s new. I thought I’d share them with here:</p>
  1625.  
  1626.  
  1627.  
  1628. <ul>
  1629. <li><a href="https://devtoolstips.org/" rel="noopener">DevTools Tips</a> (Patrick Brosset): A curated collection of helpful cross-browser DevTools tips and tricks.</li>
  1630.  
  1631.  
  1632.  
  1633. <li><a href="https://umaar.com/dev-tips/" rel="noopener">Dev Tips</a> (Umar Hansa): DevTools tips sent to your inbox!</li>
  1634.  
  1635.  
  1636.  
  1637. <li><a href="https://www.canidev.tools/" rel="noopener">Can I DevTools?</a>: The <a href="https://caniuse.com" rel="noopener">Can</a><a href="https://caniuse.com" rel="noopener">iuse</a> for DevTools.</li>
  1638. </ul>
  1639. <hr />
  1640. <p><small><a rel="nofollow" href="https://css-tricks.com/some-cross-browser-devtools-features-you-might-not-know/">Some Cross-Browser DevTools Features You Might Not Know</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
  1641. ]]></content:encoded>
  1642. <wfw:commentRss>https://css-tricks.com/some-cross-browser-devtools-features-you-might-not-know/feed/</wfw:commentRss>
  1643. <slash:comments>4</slash:comments>
  1644. <post-id xmlns="com-wordpress:feed-additions:1">377264</post-id> </item>
  1645. <item>
  1646. <title>Making Calendars With Accessibility and Internationalization in Mind</title>
  1647. <link>https://css-tricks.com/making-calendars-with-accessibility-and-internationalization-in-mind/</link>
  1648. <comments>https://css-tricks.com/making-calendars-with-accessibility-and-internationalization-in-mind/#comments</comments>
  1649. <dc:creator><![CDATA[Mads Stoumann]]></dc:creator>
  1650. <pubDate>Mon, 13 Mar 2023 13:23:52 +0000</pubDate>
  1651. <category><![CDATA[Article]]></category>
  1652. <category><![CDATA[custom elements]]></category>
  1653. <category><![CDATA[i18n]]></category>
  1654. <category><![CDATA[locales]]></category>
  1655. <guid isPermaLink="false">https://css-tricks.com/?p=376950</guid>
  1656.  
  1657. <description><![CDATA[<p>Doing a quick search here on CSS-Tricks shows just how many different ways there are to approach calendars. Some show how <a href="https://css-tricks.com/a-calendar-in-three-lines-of-css/">CSS Grid can create the layout efficiently</a>. Some attempt to <a href="https://css-tricks.com/how-to-make-a-monthly-calendar-with-real-data/">bring actual data into the mix</a>. Some &#8230;</p>
  1658. <hr />
  1659. <p><small><a rel="nofollow" href="https://css-tricks.com/making-calendars-with-accessibility-and-internationalization-in-mind/">Making Calendars With Accessibility and Internationalization in Mind</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
  1660. ]]></description>
  1661. <content:encoded><![CDATA[
  1662. <p>Doing a quick search here on CSS-Tricks shows just how many different ways there are to approach calendars. Some show how <a href="https://css-tricks.com/a-calendar-in-three-lines-of-css/">CSS Grid can create the layout efficiently</a>. Some attempt to <a href="https://css-tricks.com/how-to-make-a-monthly-calendar-with-real-data/">bring actual data into the mix</a>. Some <a href="https://css-tricks.com/lets-make-a-vue-powered-monthly-calendar/">rely on a framework</a> to help with state management.</p>
  1663.  
  1664.  
  1665.  
  1666. <p>There are many considerations when building a calendar component — far more than what is covered in the articles I linked up. If you think about it, calendars are fraught with nuance, from handling timezones and date formats to localization and even making sure dates flow from one month to the next… and that’s before we even get into accessibility and additional layout considerations depending on where the calendar is displayed and whatnot.</p>
  1667.  
  1668.  
  1669.  
  1670. <p>Many developers fear the <a href="https://css-tricks.com/everything-you-need-to-know-about-date-in-javascript/"><code>Date()</code> object</a> and stick with older libraries like <a href="https://momentjs.com" rel="noopener"><code>moment.js</code></a>. But while there are many “gotchas” when it comes to dates and formatting, JavaScript has a lot of cool APIs and stuff to help out!</p>
  1671.  
  1672.  
  1673.  
  1674. <span id="more-376950"></span>
  1675.  
  1676.  
  1677.  
  1678. <figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="464" height="251" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/s_5F326355ADAED13048ECB603E300F82A04D113F56D11D632592576A458AB8C94_1672583219797_image.png?resize=464%2C251&#038;ssl=1" alt="January 2023 calendar grid." class="wp-image-376951" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/s_5F326355ADAED13048ECB603E300F82A04D113F56D11D632592576A458AB8C94_1672583219797_image.png?w=464&amp;ssl=1 464w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/s_5F326355ADAED13048ECB603E300F82A04D113F56D11D632592576A458AB8C94_1672583219797_image.png?resize=300%2C162&amp;ssl=1 300w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /></figure>
  1679.  
  1680.  
  1681.  
  1682. <p>I don’t want to re-create the wheel here, but I will show you how we can get a dang good calendar with vanilla JavaScript. We’ll look into <strong>accessibility</strong>, using semantic markup and screenreader-friendly <code>&lt;time&gt;</code> -tags — as well as <strong>internationalization</strong> and <strong>formatting</strong>, using the <code>Intl.Locale</code>, <code>Intl.DateTimeFormat</code> and <code>Intl.NumberFormat</code>-APIs.</p>
  1683.  
  1684.  
  1685.  
  1686. <p>In other words, we’re making a calendar… only without the extra dependencies you might typically see used in a tutorial like this, and with some of the nuances you might not typically see. And, in the process, I hope you’ll gain a new appreciation for newer things that JavaScript can do while getting an idea of the sorts of things that cross my mind when I’m putting something like this together.</p>
  1687.  
  1688.  
  1689. <h3 class="wp-block-heading" id="first-off-naming">First off, naming</h3>
  1690.  
  1691.  
  1692. <p>What should we call our calendar component? In my native language, it would be called “kalender element”, so let’s use that and shorten that to “Kal-El” — also known as <a href="https://the-superman.fandom.com/wiki/Kal-El" rel="noopener">Superman’s name on the planet Krypton</a>.</p>
  1693.  
  1694.  
  1695.  
  1696. <p>Let’s create a function to get things going:</p>
  1697.  
  1698.  
  1699.  
  1700. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">function kalEl(settings = {}) { ... }</code></pre>
  1701.  
  1702.  
  1703.  
  1704. <p>This method will render <strong>a single month</strong>. Later we’ll call this method from <code>[...Array(12).keys()]</code> to render an entire year.</p>
  1705.  
  1706.  
  1707. <h3 class="wp-block-heading" id="initial-data-and-internationalization">Initial data and internationalization</h3>
  1708.  
  1709.  
  1710. <p>One of the common things a typical online calendar does is highlight the current date. So let’s create a reference for that:</p>
  1711.  
  1712.  
  1713.  
  1714. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">const today = new Date();</code></pre>
  1715.  
  1716.  
  1717.  
  1718. <p>Next, we’ll create a “configuration object” that we’ll merge with the optional <code>settings</code> object of the primary method:</p>
  1719.  
  1720.  
  1721.  
  1722. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">const config = Object.assign(
  1723.  {
  1724.    locale: (document.documentElement.getAttribute('lang') || 'en-US'),
  1725.    today: {
  1726.      day: today.getDate(),
  1727.      month: today.getMonth(),
  1728.      year: today.getFullYear()
  1729.    }
  1730.  }, settings
  1731. );</code></pre>
  1732.  
  1733.  
  1734.  
  1735. <p>We check, if the root element (<code>&lt;html&gt;</code>) contains a <code>lang</code>-attribute with <strong>locale</strong> info; otherwise, we’ll fallback to using <code>en-US</code>. This is the first step toward <a href="https://www.w3.org/International/questions/qa-html-language-declarations" rel="noopener">internationalizing the calendar</a>.</p>
  1736.  
  1737.  
  1738.  
  1739. <p>We also need to determine which month to initially display when the calendar is rendered. That&#8217;s why we extended the <code>config</code> object with the primary <code>date</code>. This way, if no date is provided in the <code>settings</code> object, we’ll use the <code>today</code> reference instead:</p>
  1740.  
  1741.  
  1742.  
  1743. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">const date = config.date ? new Date(config.date) : today;</code></pre>
  1744.  
  1745.  
  1746.  
  1747. <p>We need a little more info to properly format the calendar based on locale. For example, we might not know whether the first day of the week is Sunday or Monday, depending on the locale. If we have the info, great! But if not, we’ll update it using the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale" rel="noopener"><code>Intl.Locale</code> API</a>. The API has a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/weekInfo" rel="noopener"><code>weekInfo</code> object</a> that returns a <code>firstDay</code> property that gives us exactly what we’re looking for without any hassle. We can also get which days of the week are assigned to the <code>weekend</code>:</p>
  1748.  
  1749.  
  1750.  
  1751. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">if (!config.info) config.info = new Intl.Locale(config.locale).weekInfo || {
  1752.  firstDay: 7,
  1753.  weekend: [6, 7]
  1754. };</code></pre>
  1755.  
  1756.  
  1757.  
  1758. <p>Again, we create fallbacks. The “first day” of the week for <code>en-US</code> is Sunday, so it defaults to a value of <code>7</code>. This is a little confusing, as the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getDay" rel="noopener"><code>getDay</code> method</a> in JavaScript returns the days as <code>[0-6]</code>, where <code>0</code> is Sunday… don’t ask me why. The weekends are Saturday and Sunday, hence <code>[6, 7]</code>.</p>
  1759.  
  1760.  
  1761.  
  1762. <p>Before we had the <code>Intl.Locale</code> API and its <code>weekInfo</code> method, it was pretty hard to create an international calendar without many **objects and arrays with information about each locale or region. Nowadays, it’s easy-peasy. If we pass in <code>en-GB</code>, the method returns:</p>
  1763.  
  1764.  
  1765.  
  1766. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">// en-GB
  1767. {
  1768.  firstDay: 1,
  1769.  weekend: [6, 7],
  1770.  minimalDays: 4
  1771. }</code></pre>
  1772.  
  1773.  
  1774.  
  1775. <p>In a country like Brunei (<code>ms-BN</code>), the weekend is Friday and Sunday:</p>
  1776.  
  1777.  
  1778.  
  1779. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">// ms-BN
  1780. {
  1781.  firstDay: 7,
  1782.  weekend: [5, 7],
  1783.  minimalDays: 1
  1784. }</code></pre>
  1785.  
  1786.  
  1787.  
  1788. <p class="is-style-explanation">You might wonder what that <code>minimalDays</code> property is. That’s the <a href="https://www.unicode.org/reports/tr35/tr35-dates.html#Date_Patterns_Week_Elements" rel="noopener">fewest days required in the first week of a month to be counted as a full week</a>. In some regions, it might be just one day. For others, it might be a full seven days.</p>
  1789.  
  1790.  
  1791.  
  1792. <p>Next, we’ll create a <code>render</code> method within our <code>kalEl</code>-method:</p>
  1793.  
  1794.  
  1795.  
  1796. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">const render = (date, locale) => { ... }</code></pre>
  1797.  
  1798.  
  1799.  
  1800. <p>We still need some more data to work with before we render anything:</p>
  1801.  
  1802.  
  1803.  
  1804. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">const month = date.getMonth();
  1805. const year = date.getFullYear();
  1806. const numOfDays = new Date(year, month + 1, 0).getDate();
  1807. const renderToday = (year === config.today.year) &amp;&amp; (month === config.today.month);</code></pre>
  1808.  
  1809.  
  1810.  
  1811. <p>The last one is a <code>Boolean</code> that checks whether <code>today</code> exists in the month we’re about to render.</p>
  1812.  
  1813.  
  1814. <h3 class="wp-block-heading" id="semantic-markup">Semantic markup</h3>
  1815.  
  1816.  
  1817. <p>We’re going to get deeper in rendering in just a moment. But first, I want to make sure that the details we set up have semantic HTML tags associated with them. Setting that up right out of the box gives us accessibility benefits from the start.</p>
  1818.  
  1819.  
  1820. <h4 class="wp-block-heading" id="calendar-wrapper">Calendar wrapper</h4>
  1821.  
  1822.  
  1823. <p>First, we have the non-semantic wrapper: <code>&lt;kal-el&gt;</code>. That’s fine because there isn’t a semantic <code>&lt;calendar&gt;</code> tag or anything like that. If we weren’t making a custom element, <code>&lt;article&gt;</code> might be the most appropriate element since the calendar could stand on its own page.</p>
  1824.  
  1825.  
  1826. <h4 class="wp-block-heading" id="month-names">Month names</h4>
  1827.  
  1828.  
  1829. <p>The <code>&lt;time&gt;</code> element is going to be a big one for us because it helps translate dates into a format that screenreaders and search engines can parse more accurately and consistently. For example, here’s how we can convey “January 2023” in our markup:</p>
  1830.  
  1831.  
  1832.  
  1833. <pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;time datetime="2023-01">January &lt;i>2023&lt;/i>&lt;/time></code></pre>
  1834.  
  1835.  
  1836. <h4 class="wp-block-heading" id="day-names">Day names</h4>
  1837.  
  1838.  
  1839. <p>The row above the calendar’s dates containing the names of the days of the week can be tricky. It’s ideal if we can write out the full names for each day — e.g. Sunday, Monday, Tuesday, etc. — but that can take up a lot of space. So, let’s abbreviate the names for now inside of an <code>&lt;ol&gt;</code> where each day is a <code>&lt;li&gt;</code>:</p>
  1840.  
  1841.  
  1842.  
  1843. <pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;ol>
  1844.  &lt;li>&lt;abbr title="Sunday">Sun&lt;/abbr>&lt;/li>
  1845.  &lt;li>&lt;abbr title="Monday">Mon&lt;/abbr>&lt;/li>
  1846.  &lt;!-- etc. -->
  1847. &lt;/ol></code></pre>
  1848.  
  1849.  
  1850.  
  1851. <p>We could get tricky with CSS to get the best of both worlds. For example, if we modified the markup a bit like this:</p>
  1852.  
  1853.  
  1854.  
  1855. <pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;ol>
  1856.  &lt;li>
  1857.    &lt;abbr title="S">Sunday&lt;/abbr>
  1858.  &lt;/li>
  1859. &lt;/ol></code></pre>
  1860.  
  1861.  
  1862.  
  1863. <p>…we get the full names by default. We can then “hide” the full name when space runs out and display the <code>title</code> attribute instead:</p>
  1864.  
  1865.  
  1866.  
  1867. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">@media all and (max-width: 800px) {
  1868.  li abbr::after {
  1869.    content: attr(title);
  1870.  }
  1871. }</code></pre>
  1872.  
  1873.  
  1874.  
  1875. <p>But, we’re not going that way because the <code>Intl.DateTimeFormat</code> API can help here as well. We’ll get to that in the next section when we cover rendering.</p>
  1876.  
  1877.  
  1878. <h4 class="wp-block-heading" id="day-numbers">Day numbers</h4>
  1879.  
  1880.  
  1881. <p>Each date in the calendar grid gets a number. Each number is a list item (<code>&lt;li&gt;</code>) in an ordered list (<code>&lt;ol&gt;</code>), and the inline <code>&lt;time&gt;</code> tag wraps the actual number.</p>
  1882.  
  1883.  
  1884.  
  1885. <pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;li>
  1886.  &lt;time datetime="2023-01-01">1&lt;/time>
  1887. &lt;/li></code></pre>
  1888.  
  1889.  
  1890.  
  1891. <p>And while I’m not planning to do any styling just yet, I know I will want some way to style the date numbers. That’s possible as-is, but I also want to be able to style weekday numbers differently than weekend numbers if I need to. So, I’m going to include <a href="https://css-tricks.com/a-complete-guide-to-data-attributes/"><code>data-*</code> attributes</a> specifically for that: <code>data-weekend</code> and <code>data-today</code>.</p>
  1892.  
  1893.  
  1894. <h4 class="wp-block-heading" id="week-numbers">Week numbers</h4>
  1895.  
  1896.  
  1897. <p>There are 52 weeks in a year, sometimes 53. While it’s not super common, it can be nice to display the number for a given week in the calendar for additional context. I like having it now, even if I don&#8217;t wind up not using it. But we’ll totally use it in this tutorial.</p>
  1898.  
  1899.  
  1900.  
  1901. <p>We’ll use a <code>data-weeknumber</code> attribute as a styling hook and include it in the markup for each date that is the week&#8217;s first date.</p>
  1902.  
  1903.  
  1904.  
  1905. <pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;li data-day="7" data-weeknumber="1" data-weekend="">
  1906.  &lt;time datetime="2023-01-08">8&lt;/time>
  1907. &lt;/li></code></pre>
  1908.  
  1909.  
  1910. <h3 class="wp-block-heading" id="rendering">Rendering</h3>
  1911.  
  1912.  
  1913. <p>Let’s get the calendar on a page! We already know that <code>&lt;kal-el&gt;</code> is the name of our custom element. First thing we need to configure it is to set the <code>firstDay</code> property on it, so the calendar knows whether Sunday or some other day is the first day of the week.</p>
  1914.  
  1915.  
  1916.  
  1917. <pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;kal-el data-firstday="${ config.info.firstDay }"></code></pre>
  1918.  
  1919.  
  1920.  
  1921. <p>We’ll be using <a href="https://css-tricks.com/template-literals/">template literals</a> to render the markup. To format the dates for an international audience, we’ll use the <code>Intl.DateTimeFormat</code> API, again using the <code>locale</code> we specified earlier.</p>
  1922.  
  1923.  
  1924. <h4 class="wp-block-heading" id="the-month-and-year">The month and year</h4>
  1925.  
  1926.  
  1927. <p>When we call the <code>month</code>, we can set whether we want to use the <code>long</code> name (e.g. February) or the <code>short</code> name (e.g. Feb.). Let’s use the <code>long</code> name since it’s the title above the calendar:</p>
  1928.  
  1929.  
  1930.  
  1931. <pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;time datetime="${year}-${(pad(month))}">
  1932.  ${new Intl.DateTimeFormat(
  1933.    locale,
  1934.    { month:'long'}).format(date)} &lt;i>${year}&lt;/i>
  1935. &lt;/time></code></pre>
  1936.  
  1937.  
  1938. <h4 class="wp-block-heading" id="weekday-names">Weekday names</h4>
  1939.  
  1940.  
  1941. <p>For weekdays displayed above the grid of dates, we need both the <code>long</code> (e.g. “Sunday”) and <code>short</code> (abbreviated, ie. “Sun”) names. This way, we can use the “short” name when the calendar is short on space:</p>
  1942.  
  1943.  
  1944.  
  1945. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">Intl.DateTimeFormat([locale], { weekday: 'long' })
  1946. Intl.DateTimeFormat([locale], { weekday: 'short' })</code></pre>
  1947.  
  1948.  
  1949.  
  1950. <p>Let’s make a small helper method that makes it a little easier to call each one:</p>
  1951.  
  1952.  
  1953.  
  1954. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">const weekdays = (firstDay, locale) => {
  1955.  const date = new Date(0);
  1956.  const arr = [...Array(7).keys()].map(i => {
  1957.    date.setDate(5 + i)
  1958.    return {
  1959.      long: new Intl.DateTimeFormat([locale], { weekday: 'long'}).format(date),
  1960.      short: new Intl.DateTimeFormat([locale], { weekday: 'short'}).format(date)
  1961.    }
  1962.  })
  1963.  for (let i = 0; i &lt; 8 - firstDay; i++) arr.splice(0, 0, arr.pop());
  1964.  return arr;
  1965. }</code></pre>
  1966.  
  1967.  
  1968.  
  1969. <p>Here’s how we invoke that in the template:</p>
  1970.  
  1971.  
  1972.  
  1973. <pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;ol>
  1974.  ${weekdays(config.info.firstDay,locale).map(name => `
  1975.    &lt;li>
  1976.      &lt;abbr title="${name.long}">${name.short}&lt;/abbr>
  1977.    &lt;/li>`).join('')
  1978.  }
  1979. &lt;/ol></code></pre>
  1980.  
  1981.  
  1982. <h4 class="wp-block-heading" id="day-numbers">Day numbers</h4>
  1983.  
  1984.  
  1985. <p>And finally, the days, wrapped in an <code>&lt;ol&gt;</code> element:</p>
  1986.  
  1987.  
  1988.  
  1989. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">${[...Array(numOfDays).keys()].map(i => {
  1990.  const cur = new Date(year, month, i + 1);
  1991.  let day = cur.getDay(); if (day === 0) day = 7;
  1992.  const today = renderToday &amp;&amp; (config.today.day === i + 1) ? ' data-today':'';
  1993.  return `
  1994.    &lt;li data-day="${day}"${today}${i === 0 || day === config.info.firstDay ? ` data-weeknumber="${new Intl.NumberFormat(locale).format(getWeek(cur))}"`:''}${config.info.weekend.includes(day) ? ` data-weekend`:''}>
  1995.      &lt;time datetime="${year}-${(pad(month))}-${pad(i)}" tabindex="0">
  1996.        ${new Intl.NumberFormat(locale).format(i + 1)}
  1997.      &lt;/time>
  1998.    &lt;/li>`
  1999. }).join('')}</code></pre>
  2000.  
  2001.  
  2002.  
  2003. <p>Let’s break that down:</p>
  2004.  
  2005.  
  2006.  
  2007. <ol>
  2008. <li>We create a “dummy” array, based on the “number of days” variable, which we’ll use to iterate.</li>
  2009.  
  2010.  
  2011.  
  2012. <li>We create a <code>day</code> variable for the current day in the iteration.</li>
  2013.  
  2014.  
  2015.  
  2016. <li>We fix the discrepancy between the <code>Intl.Locale</code> API and <code>getDay()</code>.</li>
  2017.  
  2018.  
  2019.  
  2020. <li>If the <code>day</code> is equal to <code>today</code>, we add a <code>data-*</code> attribute.</li>
  2021.  
  2022.  
  2023.  
  2024. <li>Finally, we return the <code>&lt;li&gt;</code> element as a string with merged data.</li>
  2025.  
  2026.  
  2027.  
  2028. <li><code>tabindex="0"</code> makes the element focusable, when using keyboard navigation, after any positive tabindex values (Note: you should <strong>never</strong> add <strong>positive</strong> tabindex-values)</li>
  2029. </ol>
  2030.  
  2031.  
  2032.  
  2033. <p>To <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart" rel="noopener">“pad” the numbers</a> in the <code>datetime</code> attribute, we use a little helper method:</p>
  2034.  
  2035.  
  2036.  
  2037. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">const pad = (val) => (val + 1).toString().padStart(2, '0');</code></pre>
  2038.  
  2039.  
  2040. <h4 class="wp-block-heading" id="week-number">Week number</h4>
  2041.  
  2042.  
  2043. <p>Again, the “week number” is where a week falls in a 52-week calendar. We use a little helper method for that as well:</p>
  2044.  
  2045.  
  2046.  
  2047. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">function getWeek(cur) {
  2048.  const date = new Date(cur.getTime());
  2049.  date.setHours(0, 0, 0, 0);
  2050.  date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7);
  2051.  const week = new Date(date.getFullYear(), 0, 4);
  2052.  return 1 + Math.round(((date.getTime() - week.getTime()) / 86400000 - 3 + (week.getDay() + 6) % 7) / 7);
  2053. }</code></pre>
  2054.  
  2055.  
  2056.  
  2057. <p class="is-style-explanation">I didn’t write this <code>getWeek</code>-method. It’s a cleaned up version of <a href="https://weeknumber.com/how-to/javascript" rel="noopener">this script</a>.</p>
  2058.  
  2059.  
  2060.  
  2061. <p>And that’s it! Thanks to the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale" rel="noopener"><code>Intl.Locale</code></a>, <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat" rel="noopener"><code>Intl.DateTimeFormat</code></a> and <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat" rel="noopener"><code>Intl.NumberFormat</code></a> APIs, we can now simply change the <code>lang</code>-attribute of the <code>&lt;html&gt;</code> element to change the context of the calendar based on the current region:</p>
  2062.  
  2063.  
  2064.  
  2065. <div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-2 wp-block-columns-is-layout-flex">
  2066. <div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
  2067. <figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="456" height="284" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/s_5F326355ADAED13048ECB603E300F82A04D113F56D11D632592576A458AB8C94_1672589519354_image.png?resize=456%2C284&#038;ssl=1" alt="January 2023 calendar grid." class="wp-image-376959" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/s_5F326355ADAED13048ECB603E300F82A04D113F56D11D632592576A458AB8C94_1672589519354_image.png?w=456&amp;ssl=1 456w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/s_5F326355ADAED13048ECB603E300F82A04D113F56D11D632592576A458AB8C94_1672589519354_image.png?resize=300%2C187&amp;ssl=1 300w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /><figcaption class="wp-element-caption"><code>de-DE</code></figcaption></figure>
  2068. </div>
  2069.  
  2070.  
  2071.  
  2072. <div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
  2073. <figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="444" height="253" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/s_5F326355ADAED13048ECB603E300F82A04D113F56D11D632592576A458AB8C94_1672589309595_image.png?resize=444%2C253&#038;ssl=1" alt="January 2023 calendar grid." class="wp-image-376960" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/s_5F326355ADAED13048ECB603E300F82A04D113F56D11D632592576A458AB8C94_1672589309595_image.png?w=444&amp;ssl=1 444w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/s_5F326355ADAED13048ECB603E300F82A04D113F56D11D632592576A458AB8C94_1672589309595_image.png?resize=300%2C171&amp;ssl=1 300w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /><figcaption class="wp-element-caption"><code>fa-IR</code></figcaption></figure>
  2074. </div>
  2075.  
  2076.  
  2077.  
  2078. <div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
  2079. <figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="451" height="257" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/s_5F326355ADAED13048ECB603E300F82A04D113F56D11D632592576A458AB8C94_1672589340340_image.png?resize=451%2C257&#038;ssl=1" alt="January 2023 calendar grid." class="wp-image-376961" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/s_5F326355ADAED13048ECB603E300F82A04D113F56D11D632592576A458AB8C94_1672589340340_image.png?w=451&amp;ssl=1 451w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/s_5F326355ADAED13048ECB603E300F82A04D113F56D11D632592576A458AB8C94_1672589340340_image.png?resize=300%2C171&amp;ssl=1 300w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /><figcaption class="wp-element-caption"><code>zh-Hans-CN-u-nu-hanidec</code></figcaption></figure>
  2080. </div>
  2081. </div>
  2082.  
  2083.  
  2084. <h3 class="wp-block-heading" id="styling-the-calendar">Styling the calendar</h3>
  2085.  
  2086.  
  2087. <p>You might recall how all the days are just one <code>&lt;ol&gt;</code> with list items. To style these into a readable calendar, we dive into the wonderful world of CSS Grid. In fact, we can repurpose the same grid from <a href="https://css-tricks.com/snippets/css/css-grid-starter-layouts/">a starter calendar template right here on CSS-Tricks</a>, but updated a smidge with the <code>:is()</code> relational pseudo to optimize the code.</p>
  2088.  
  2089.  
  2090.  
  2091. <p>Notice that I’m defining configurable CSS variables along the way (and prefixing them with <code>---kalel-</code> to avoid conflicts).</p>
  2092.  
  2093.  
  2094.  
  2095. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">kal-el :is(ol, ul) {
  2096.  display: grid;
  2097.  font-size: var(--kalel-fz, small);
  2098.  grid-row-gap: var(--kalel-row-gap, .33em);
  2099.  grid-template-columns: var(--kalel-gtc, repeat(7, 1fr));
  2100.  list-style: none;
  2101.  margin: unset;
  2102.  padding: unset;
  2103.  position: relative;
  2104. }</code></pre>
  2105.  
  2106.  
  2107.  
  2108. <figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="397" height="266" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/s_5F326355ADAED13048ECB603E300F82A04D113F56D11D632592576A458AB8C94_1672590153826_image.png?resize=397%2C266&#038;ssl=1" alt="Seven-column calendar grid with grid lines shown." class="wp-image-376958" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/s_5F326355ADAED13048ECB603E300F82A04D113F56D11D632592576A458AB8C94_1672590153826_image.png?w=397&amp;ssl=1 397w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/s_5F326355ADAED13048ECB603E300F82A04D113F56D11D632592576A458AB8C94_1672590153826_image.png?resize=300%2C201&amp;ssl=1 300w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /></figure>
  2109.  
  2110.  
  2111.  
  2112. <p>Let’s draw borders around the date numbers to help separate them visually:</p>
  2113.  
  2114.  
  2115.  
  2116. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">kal-el :is(ol, ul) li {
  2117.  border-color: var(--kalel-li-bdc, hsl(0, 0%, 80%));
  2118.  border-style: var(--kalel-li-bds, solid);
  2119.  border-width: var(--kalel-li-bdw, 0 0 1px 0);
  2120.  grid-column: var(--kalel-li-gc, initial);
  2121.  text-align: var(--kalel-li-tal, end);
  2122. }</code></pre>
  2123.  
  2124.  
  2125.  
  2126. <p>The seven-column grid works fine when the first day of the month is <em>also</em> the first day of the week for the selected locale). But that’s the exception rather than the rule. Most times, we’ll need to shift the first day of the month to a different weekday.</p>
  2127.  
  2128.  
  2129.  
  2130. <figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="390" height="266" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/s_5F326355ADAED13048ECB603E300F82A04D113F56D11D632592576A458AB8C94_1672590297938_image.png?resize=390%2C266&#038;ssl=1" alt="Showing the first day of the month falling on a Thursday." class="wp-image-376964" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/s_5F326355ADAED13048ECB603E300F82A04D113F56D11D632592576A458AB8C94_1672590297938_image.png?w=390&amp;ssl=1 390w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/s_5F326355ADAED13048ECB603E300F82A04D113F56D11D632592576A458AB8C94_1672590297938_image.png?resize=300%2C205&amp;ssl=1 300w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /></figure>
  2131.  
  2132.  
  2133.  
  2134. <p>Remember all the extra <code>data-*</code> attributes we defined when writing our markup? We can hook into those to update which grid column (<code>--kalel-li-gc</code>) the first date number of the month is placed on:</p>
  2135.  
  2136.  
  2137.  
  2138. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">[data-firstday="1"] [data-day="3"]:first-child {
  2139.  --kalel-li-gc: 1 / 4;
  2140. }</code></pre>
  2141.  
  2142.  
  2143.  
  2144. <p>In this case, we’re spanning from the first grid column to the fourth grid column — which will automatically “push” the next item (Day 2) to the fifth grid column, and so forth.</p>
  2145.  
  2146.  
  2147.  
  2148. <p>Let’s add a little style to the “current” date, so it stands out. These are just my styles. You can totally do what you’d like here.</p>
  2149.  
  2150.  
  2151.  
  2152. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">[data-today] {
  2153.  --kalel-day-bdrs: 50%;
  2154.  --kalel-day-bg: hsl(0, 86%, 40%);
  2155.  --kalel-day-hover-bgc: hsl(0, 86%, 70%);
  2156.  --kalel-day-c: #fff;
  2157. }</code></pre>
  2158.  
  2159.  
  2160.  
  2161. <p>I like the idea of styling the date numbers for weekends differently than weekdays. I’m going to use a reddish color to style those. Note that we can reach for the <code>:not()</code> pseudo-class to select them while leaving the current date alone:</p>
  2162.  
  2163.  
  2164.  
  2165. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">[data-weekend]:not([data-today]) {
  2166.  --kalel-day-c: var(--kalel-weekend-c, hsl(0, 86%, 46%));
  2167. }</code></pre>
  2168.  
  2169.  
  2170.  
  2171. <p>Oh, and let’s not forget the week numbers that go before the first date number of each week. We used a <code>data-weeknumber</code> attribute in the markup for that, but the numbers won’t actually display unless we reveal them with CSS, which we can do on the <code>::before</code> pseudo-element:</p>
  2172.  
  2173.  
  2174.  
  2175. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">[data-weeknumber]::before {
  2176.  display: var(--kalel-weeknumber-d, inline-block);
  2177.  content: attr(data-weeknumber);
  2178.  position: absolute;
  2179.  inset-inline-start: 0;
  2180.  /* additional styles */
  2181. }</code></pre>
  2182.  
  2183.  
  2184.  
  2185. <p>We’re technically done at this point! We can render a calendar grid that shows the dates for the current month, complete with considerations for localizing the data by locale, and ensuring that the calendar uses proper semantics. And all we used was vanilla JavaScript and CSS!</p>
  2186.  
  2187.  
  2188.  
  2189. <p>But let’s take this <em>one more step</em>…</p>
  2190.  
  2191.  
  2192. <h3 class="wp-block-heading" id="rendering-an-entire-year">Rendering an entire year</h3>
  2193.  
  2194.  
  2195. <p>Maybe you need to display a full year of dates! So, rather than render the current month, you might want to display all of the month grids for the current year.</p>
  2196.  
  2197.  
  2198.  
  2199. <p>Well, the nice thing about the approach we’re using is that we can call the <code>render</code> method as many times as we want and merely change the integer that identifies the month on each instance. Let’s call it 12 times based on the current year.</p>
  2200.  
  2201.  
  2202.  
  2203. <p>as simple as calling the <code>render</code>-method 12 times, and just change the integer for <code>month</code> — <code>i</code>:</p>
  2204.  
  2205.  
  2206.  
  2207. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">[...Array(12).keys()].map(i =>
  2208.  render(
  2209.    new Date(date.getFullYear(),
  2210.    i,
  2211.    date.getDate()),
  2212.    config.locale,
  2213.    date.getMonth()
  2214.  )
  2215. ).join('')</code></pre>
  2216.  
  2217.  
  2218.  
  2219. <p>It’s probably a good idea to create a new parent wrapper for the rendered year. Each calendar grid is a <code>&lt;kal-el&gt;</code> element. Let’s call the new parent wrapper <code>&lt;jor-el&gt;</code>, where <a href="https://superman.fandom.com/wiki/Jor-El" rel="noopener">Jor-El is the name of Kal-El’s father</a>.</p>
  2220.  
  2221.  
  2222.  
  2223. <pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;jor-el id="app" data-year="true">
  2224.  &lt;kal-el data-firstday="7">
  2225.    &lt;!-- etc. -->
  2226.  &lt;/kal-el>
  2227.  
  2228.  &lt;!-- other months -->
  2229. &lt;/jor-el></code></pre>
  2230.  
  2231.  
  2232.  
  2233. <p>We can use <code>&lt;jor-el&gt;</code> to create a grid for our grids. So meta!</p>
  2234.  
  2235.  
  2236.  
  2237. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">jor-el {
  2238.  background: var(--jorel-bg, none);
  2239.  display: var(--jorel-d, grid);
  2240.  gap: var(--jorel-gap, 2.5rem);
  2241.  grid-template-columns: var(--jorel-gtc, repeat(auto-fill, minmax(320px, 1fr)));
  2242.  padding: var(--jorel-p, 0);
  2243. }</code></pre>
  2244.  
  2245.  
  2246. <h3 class="wp-block-heading" id="final-demo">Final demo</h3>
  2247.  
  2248.  
  2249. <div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_vYaKKKJ" src="//codepen.io/anon/embed/vYaKKKJ?height=500&amp;theme-id=1&amp;slug-hash=vYaKKKJ&amp;default-tab=result" height="500" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed vYaKKKJ" title="CodePen Embed vYaKKKJ" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>
  2250.  
  2251.  
  2252. <h3 class="wp-block-heading" id="bonus-confetti-calendar">Bonus: Confetti Calendar</h3>
  2253.  
  2254.  
  2255. <p>I read an excellent book called <em><a href="https://www.goodreads.com/book/show/35863579-making-and-breaking-the-grid" rel="noopener">Making and Breaking the Grid</a></em> the other day and stumbled on this beautiful “New Year’s poster”:</p>
  2256.  
  2257.  
  2258.  
  2259. <figure class="wp-block-image"><img decoding="async" src="https://i0.wp.com/paper-attachments.dropboxusercontent.com/s_5F326355ADAED13048ECB603E300F82A04D113F56D11D632592576A458AB8C94_1673333960043_IMG_1264.jpeg?ssl=1" alt="" data-recalc-dims="1"/><figcaption class="wp-element-caption">Source: <em><a href="https://www.goodreads.com/book/show/35863579-making-and-breaking-the-grid" rel="noopener">Making and Breaking the Grid (2nd Edition)</a></em> by Timothy Samara</figcaption></figure>
  2260.  
  2261.  
  2262.  
  2263. <p>I figured we could do something similar without changing anything in the HTML or JavaScript. I’ve taken the liberty to include full names for months, and numbers instead of day names, to make it more readable. Enjoy!</p>
  2264.  
  2265.  
  2266.  
  2267. <div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_bGjqqoj" src="//codepen.io/anon/embed/bGjqqoj?height=750&amp;theme-id=1&amp;slug-hash=bGjqqoj&amp;default-tab=result" height="750" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed bGjqqoj" title="CodePen Embed bGjqqoj" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>
  2268. <hr />
  2269. <p><small><a rel="nofollow" href="https://css-tricks.com/making-calendars-with-accessibility-and-internationalization-in-mind/">Making Calendars With Accessibility and Internationalization in Mind</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
  2270. ]]></content:encoded>
  2271. <wfw:commentRss>https://css-tricks.com/making-calendars-with-accessibility-and-internationalization-in-mind/feed/</wfw:commentRss>
  2272. <slash:comments>3</slash:comments>
  2273. <post-id xmlns="com-wordpress:feed-additions:1">376950</post-id> </item>
  2274. <item>
  2275. <title>5 Mistakes I Made When Starting My First React Project</title>
  2276. <link>https://css-tricks.com/5-mistakes-starting-react/</link>
  2277. <comments>https://css-tricks.com/5-mistakes-starting-react/#respond</comments>
  2278. <dc:creator><![CDATA[Richard Oliver Bray]]></dc:creator>
  2279. <pubDate>Fri, 10 Mar 2023 16:41:52 +0000</pubDate>
  2280. <category><![CDATA[Article]]></category>
  2281. <category><![CDATA[learning]]></category>
  2282. <category><![CDATA[react]]></category>
  2283. <guid isPermaLink="false">https://css-tricks.com/?p=377098</guid>
  2284.  
  2285. <description><![CDATA[<p>You know what it&#8217;s like to pick up a new language or framework. Sometimes there’s great documentation to help you find your way through it. But even the best documentation doesn’t cover absolutely everything. And when you work with something &#8230;</p>
  2286. <hr />
  2287. <p><small><a rel="nofollow" href="https://css-tricks.com/5-mistakes-starting-react/">5 Mistakes I Made When Starting My First React Project</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
  2288. ]]></description>
  2289. <content:encoded><![CDATA[
  2290. <p>You know what it&#8217;s like to pick up a new language or framework. Sometimes there’s great documentation to help you find your way through it. But even the best documentation doesn’t cover absolutely everything. And when you work with something that&#8217;s new, you&#8217;re bound to find a problem that doesn&#8217;t have a written solution.</p>
  2291.  
  2292.  
  2293.  
  2294. <p>That’s how it was for me the first time I created a React project — and React is one of those frameworks with remarkable documentation, especially now with the beta docs. But I still struggled my way through. It’s been quite a while since that project, but the lessons I gained from it are still fresh in my mind. And even though there are a lot of React “how-to” tutorials in out there, I thought I’d share what I wish I knew when I first used it.</p>
  2295.  
  2296.  
  2297.  
  2298. <p>So, that’s what this article is — a list of the early mistakes I made. I hope they help make learning React a lot smoother for you.</p>
  2299.  
  2300.  
  2301.  
  2302. <span id="more-377098"></span>
  2303.  
  2304.  
  2305. <h3 class="wp-block-heading" id="using-createreactapp-to-start-a-project">Using create-react-app to start a project</h3>
  2306.  
  2307.  
  2308. <p class="is-style-explanation">TL;DR Use Vite or Parcel.</p>
  2309.  
  2310.  
  2311.  
  2312. <p><a href="https://create-react-app.dev/" rel="noopener">Create React App</a> (CRA) is a tool that helps you set up a new React project. It creates a development environment with the best configuration options for most React projects. This means you don&#8217;t have to spend time configuring anything yourself.</p>
  2313.  
  2314.  
  2315.  
  2316. <p>As a beginner, this seemed like a great way to start my work! No configuration! Just start coding!</p>
  2317.  
  2318.  
  2319.  
  2320. <p>CRA uses two popular packages to achieve this, webpack and Babel. webpack is a web bundler that optimizes all of the assets in your project, such as JavaScript, CSS, and images. Babel is a tool that allows you to use newer JavaScript features, even if some browsers don&#8217;t support them.</p>
  2321.  
  2322.  
  2323.  
  2324. <p>Both are good, but there are newer tools that can do the job better, specifically <a href="https://vitejs.dev" rel="noopener">Vite</a> and <a href="https://swc.rs" rel="noopener">Speedy Web Compiler</a> (SWC).</p>
  2325.  
  2326.  
  2327.  
  2328. <p>These new and improved alternatives are faster and easier to configure than webpack and Babel. This makes it easier to adjust the configuration which is difficult to do in create-react-app without ejecting.</p>
  2329.  
  2330.  
  2331.  
  2332. <p>To use them both when setting up a new React project you have to make sure you have <a href="https://nodejs.org/en/" rel="noopener">Node</a> version 12 or higher installed, then run the following command.</p>
  2333.  
  2334.  
  2335.  
  2336. <pre rel="Terminal" class="wp-block-csstricks-code-block language-none" data-line=""><code markup="tt">npm create vite</code></pre>
  2337.  
  2338.  
  2339.  
  2340. <p>You’ll be asked to pick a name for your project. Once you do that, select React from the list of frameworks. After that, you can select either <code>Javascript + SWC</code> or <code>Typescript + SWC</code></p>
  2341.  
  2342.  
  2343.  
  2344. <p>Then you’ll have to change directory <code>cd</code> into your project and run the following command;</p>
  2345.  
  2346.  
  2347.  
  2348. <pre rel="Terminal" class="wp-block-csstricks-code-block language-none" data-line=""><code markup="tt">npm i &amp;&amp; npm run dev</code></pre>
  2349.  
  2350.  
  2351.  
  2352. <p>This should run a development server for your site with the URL <code>localhost:5173</code></p>
  2353.  
  2354.  
  2355.  
  2356. <p>And it’s as simple as that.</p>
  2357.  
  2358.  
  2359.  
  2360.    
  2361.    <div class="in-article-cards">
  2362.      <article class="in-article-card article" id="mini-post-377098">
  2363.  
  2364.  <time datetime="2022-01-11" title="Originally published Mar 10, 2023">
  2365.    <strong>
  2366.                
  2367.        Article
  2368.      </strong>
  2369.  
  2370.    on
  2371.  
  2372.    Jan 11, 2022  </time>
  2373.  
  2374.  <h3>
  2375.    <a href="https://css-tricks.com/adding-vite-to-your-existing-web-app/">
  2376.      Adding Vite to Your Existing Web App    </a>
  2377.  </h3>
  2378.  
  2379.  
  2380.      <div class="tags">
  2381.      <a href="https://css-tricks.com/tag/learning/" rel="tag">learning</a> <a href="https://css-tricks.com/tag/react/" rel="tag">react</a>    </div>
  2382.  
  2383.  <div class="author-row">
  2384.    <a href="https://css-tricks.com/author/adam-rackis/" aria-label="Author page of Adam Rackis">
  2385.      <img alt='' src='https://secure.gravatar.com/avatar/d4b0907c0d98a2394568d9bf9e8814c3?s=80&#038;d=retro&#038;r=pg' srcset='https://secure.gravatar.com/avatar/d4b0907c0d98a2394568d9bf9e8814c3?s=160&#038;d=retro&#038;r=pg 2x' class='avatar avatar-80 photo' height='80' width='80' />    </a>
  2386.  
  2387.    <a class="author-name" href="https://css-tricks.com/author/adam-rackis/">
  2388.      Adam Rackis    </a>
  2389.  </div>
  2390.  
  2391. </article>
  2392. <article class="in-article-card article" id="mini-post-377098">
  2393.  
  2394.  <time datetime="2022-01-18" title="Originally published Mar 10, 2023">
  2395.    <strong>
  2396.                
  2397.        Article
  2398.      </strong>
  2399.  
  2400.    on
  2401.  
  2402.    Jan 18, 2022  </time>
  2403.  
  2404.  <h3>
  2405.    <a href="https://css-tricks.com/vitepwa-plugin-offline-service-worker/">
  2406.      Making a Site Work Offline Using the VitePWA Plugin    </a>
  2407.  </h3>
  2408.  
  2409.  
  2410.      <div class="tags">
  2411.      <a href="https://css-tricks.com/tag/learning/" rel="tag">learning</a> <a href="https://css-tricks.com/tag/react/" rel="tag">react</a>    </div>
  2412.  
  2413.  <div class="author-row">
  2414.    <a href="https://css-tricks.com/author/adam-rackis/" aria-label="Author page of Adam Rackis">
  2415.      <img alt='' src='https://secure.gravatar.com/avatar/d4b0907c0d98a2394568d9bf9e8814c3?s=80&#038;d=retro&#038;r=pg' srcset='https://secure.gravatar.com/avatar/d4b0907c0d98a2394568d9bf9e8814c3?s=160&#038;d=retro&#038;r=pg 2x' class='avatar avatar-80 photo' height='80' width='80' />    </a>
  2416.  
  2417.    <a class="author-name" href="https://css-tricks.com/author/adam-rackis/">
  2418.      Adam Rackis    </a>
  2419.  </div>
  2420.  
  2421. </article>
  2422. <article class="in-article-card article" id="mini-post-377098">
  2423.  
  2424.  <time datetime="2022-01-12" title="Originally published Mar 10, 2023">
  2425.    <strong>
  2426.                
  2427.        Article
  2428.      </strong>
  2429.  
  2430.    on
  2431.  
  2432.    Jan 12, 2022  </time>
  2433.  
  2434.  <h3>
  2435.    <a href="https://css-tricks.com/parcel-css/">
  2436.      Parcel CSS: A New CSS Parser, Transformer, and Minifier    </a>
  2437.  </h3>
  2438.  
  2439.  
  2440.      <div class="tags">
  2441.      <a href="https://css-tricks.com/tag/learning/" rel="tag">learning</a> <a href="https://css-tricks.com/tag/react/" rel="tag">react</a>    </div>
  2442.  
  2443.  <div class="author-row">
  2444.    <a href="https://css-tricks.com/author/chriscoyier/" aria-label="Author page of Chris Coyier">
  2445.      <img alt='' src='https://secure.gravatar.com/avatar/8081b26e05bb4354f7d65ffc34cbbd67?s=80&#038;d=retro&#038;r=pg' srcset='https://secure.gravatar.com/avatar/8081b26e05bb4354f7d65ffc34cbbd67?s=160&#038;d=retro&#038;r=pg 2x' class='avatar avatar-80 photo' height='80' width='80' />    </a>
  2446.  
  2447.    <a class="author-name" href="https://css-tricks.com/author/chriscoyier/">
  2448.      Chris Coyier    </a>
  2449.  </div>
  2450.  
  2451. </article>
  2452. <article class="in-article-card article" id="mini-post-377098">
  2453.  
  2454.  <time datetime="2019-04-25" title="Originally published Mar 10, 2023">
  2455.    <strong>
  2456.                
  2457.        Article
  2458.      </strong>
  2459.  
  2460.    on
  2461.  
  2462.    Apr 25, 2019  </time>
  2463.  
  2464.  <h3>
  2465.    <a href="https://css-tricks.com/using-parcel-as-a-bundler-for-react-applications/">
  2466.      Using Parcel as a Bundler for React Applications    </a>
  2467.  </h3>
  2468.  
  2469.  
  2470.      <div class="tags">
  2471.      <a href="https://css-tricks.com/tag/learning/" rel="tag">learning</a> <a href="https://css-tricks.com/tag/react/" rel="tag">react</a>    </div>
  2472.  
  2473.  <div class="author-row">
  2474.    <a href="https://css-tricks.com/author/kinglseysilas/" aria-label="Author page of Kingsley Silas">
  2475.      <img alt='' src='https://secure.gravatar.com/avatar/b0d0ce2a0e59387acbf0848f62aa52bf?s=80&#038;d=retro&#038;r=pg' srcset='https://secure.gravatar.com/avatar/b0d0ce2a0e59387acbf0848f62aa52bf?s=160&#038;d=retro&#038;r=pg 2x' class='avatar avatar-80 photo' height='80' width='80' />    </a>
  2476.  
  2477.    <a class="author-name" href="https://css-tricks.com/author/kinglseysilas/">
  2478.      Kingsley Silas    </a>
  2479.  </div>
  2480.  
  2481. </article>
  2482.    </div>
  2483.  
  2484.  
  2485.  
  2486. <h3 class="wp-block-heading" id="using-defaultprops-for-default-values">Using <code>defaultProps</code> for default values</h3>
  2487.  
  2488.  
  2489. <p class="is-style-explanation">TL;DR Use default function parameters instead.</p>
  2490.  
  2491.  
  2492.  
  2493. <p>Data can be passed to React components through something called <code>props</code>. These are added to a component just like attributes in an HTML element and can be used in a component&#8217;s definition by taking the relevant values from the prop object passed in as an argument.</p>
  2494.  
  2495.  
  2496.  
  2497. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">// App.jsx
  2498. export default function App() {
  2499.  return &lt;Card title="Hello" description="world" />
  2500. }
  2501.  
  2502. // Card.jsx
  2503. function Card(props) {
  2504.  return (
  2505.    &lt;div>
  2506.      &lt;h1>{props.title}&lt;/h1>
  2507.      &lt;p>{props.description}&lt;/p>
  2508.    &lt;/div>
  2509.  );
  2510. }
  2511.  
  2512. export default Card;</code></pre>
  2513.  
  2514.  
  2515.  
  2516. <p>If a default value is ever required for a <code>prop</code>, the <code>defaultProp</code> property can be used:</p>
  2517.  
  2518.  
  2519.  
  2520. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">// Card.jsx
  2521. function Card(props) {
  2522.  // ...
  2523. }
  2524.  
  2525. Card.defaultProps = {
  2526.  title: 'Default title',
  2527.  description: 'Desc',
  2528. };
  2529.  
  2530. export default Card;</code></pre>
  2531.  
  2532.  
  2533.  
  2534. <p>With modern JavaScript, it is possible to destructure the <code>props</code> object and assign a default value to it all in the function argument.</p>
  2535.  
  2536.  
  2537.  
  2538. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">// Card.jsx
  2539. function Card({title = "Default title", description= "Desc"}) {
  2540.  return (
  2541.    &lt;div>
  2542.      &lt;h1>{title}&lt;/h1>
  2543.      &lt;p>{description}&lt;/p>
  2544.    &lt;/div>
  2545.  )
  2546. }
  2547.  
  2548. export default Card;</code></pre>
  2549.  
  2550.  
  2551.  
  2552. <p>This is more favorable as the code that can be read by modern browsers without the need for extra transformation.</p>
  2553.  
  2554.  
  2555.  
  2556. <p>Unfortunately, <code>defaultProps</code> do require some transformation to be read by the browser since JSX (JavaScript XML) isn&#8217;t supported out of the box. This could potentially affect the performance of an application that is using a lot of <code>defaultProps</code>.</p>
  2557.  
  2558.  
  2559.  
  2560.    
  2561.    <div class="in-article-cards">
  2562.      <article class="in-article-card article" id="mini-post-377098">
  2563.  
  2564.  <time datetime="2019-10-02" title="Originally published Mar 10, 2023">
  2565.    <strong>
  2566.                
  2567.        Article
  2568.      </strong>
  2569.  
  2570.    on
  2571.  
  2572.    Oct 23, 2019  </time>
  2573.  
  2574.  <h3>
  2575.    <a href="https://css-tricks.com/demonstrating-reusable-react-components-in-a-form/">
  2576.      Demonstrating Reusable React Components in a Form    </a>
  2577.  </h3>
  2578.  
  2579.  
  2580.      <div class="tags">
  2581.      <a href="https://css-tricks.com/tag/learning/" rel="tag">learning</a> <a href="https://css-tricks.com/tag/react/" rel="tag">react</a>    </div>
  2582.  
  2583.  <div class="author-row">
  2584.    <a href="https://css-tricks.com/author/kinglseysilas/" aria-label="Author page of Kingsley Silas">
  2585.      <img alt='' src='https://secure.gravatar.com/avatar/b0d0ce2a0e59387acbf0848f62aa52bf?s=80&#038;d=retro&#038;r=pg' srcset='https://secure.gravatar.com/avatar/b0d0ce2a0e59387acbf0848f62aa52bf?s=160&#038;d=retro&#038;r=pg 2x' class='avatar avatar-80 photo' height='80' width='80' />    </a>
  2586.  
  2587.    <a class="author-name" href="https://css-tricks.com/author/kinglseysilas/">
  2588.      Kingsley Silas    </a>
  2589.  </div>
  2590.  
  2591. </article>
  2592. <article class="in-article-card intermediate" id="mini-post-377098">
  2593.  
  2594.  <time datetime="2016-02-08" title="Originally published Mar 10, 2023">
  2595.    <strong>
  2596.                
  2597.        Article
  2598.      </strong>
  2599.  
  2600.    on
  2601.  
  2602.    Jun 7, 2017  </time>
  2603.  
  2604.  <h3>
  2605.    <a href="https://css-tricks.com/productive-in-react/">
  2606.      I Learned How to be Productive in React in a Week and You Can, Too    </a>
  2607.  </h3>
  2608.  
  2609.  
  2610.      <div class="tags">
  2611.      <a href="https://css-tricks.com/tag/learning/" rel="tag">learning</a> <a href="https://css-tricks.com/tag/react/" rel="tag">react</a>    </div>
  2612.  
  2613.  <div class="author-row">
  2614.    <a href="https://css-tricks.com/author/sdrasner/" aria-label="Author page of Sarah Drasner">
  2615.      <img alt='' src='https://secure.gravatar.com/avatar/48cfd8342f9b9e5b7970f63afb0a8ee3?s=80&#038;d=retro&#038;r=pg' srcset='https://secure.gravatar.com/avatar/48cfd8342f9b9e5b7970f63afb0a8ee3?s=160&#038;d=retro&#038;r=pg 2x' class='avatar avatar-80 photo' height='80' width='80' />    </a>
  2616.  
  2617.    <a class="author-name" href="https://css-tricks.com/author/sdrasner/">
  2618.      Sarah Drasner    </a>
  2619.  </div>
  2620.  
  2621. </article>
  2622. <article class="in-article-card article" id="mini-post-377098">
  2623.  
  2624.  <time datetime="2018-08-31" title="Originally published Mar 10, 2023">
  2625.    <strong>
  2626.                
  2627.        Article
  2628.      </strong>
  2629.  
  2630.    on
  2631.  
  2632.    Aug 31, 2018  </time>
  2633.  
  2634.  <h3>
  2635.    <a href="https://css-tricks.com/props-and-proptypes-in-react/">
  2636.      Props and PropTypes in React    </a>
  2637.  </h3>
  2638.  
  2639.  
  2640.      <div class="tags">
  2641.      <a href="https://css-tricks.com/tag/learning/" rel="tag">learning</a> <a href="https://css-tricks.com/tag/react/" rel="tag">react</a>    </div>
  2642.  
  2643.  <div class="author-row">
  2644.    <a href="https://css-tricks.com/author/kinglseysilas/" aria-label="Author page of Kingsley Silas">
  2645.      <img alt='' src='https://secure.gravatar.com/avatar/b0d0ce2a0e59387acbf0848f62aa52bf?s=80&#038;d=retro&#038;r=pg' srcset='https://secure.gravatar.com/avatar/b0d0ce2a0e59387acbf0848f62aa52bf?s=160&#038;d=retro&#038;r=pg 2x' class='avatar avatar-80 photo' height='80' width='80' />    </a>
  2646.  
  2647.    <a class="author-name" href="https://css-tricks.com/author/kinglseysilas/">
  2648.      Kingsley Silas    </a>
  2649.  </div>
  2650.  
  2651. </article>
  2652.    </div>
  2653.  
  2654.  
  2655.  
  2656. <h3 class="wp-block-heading" id="dont-use-proptypes">Don&#8217;t use <code>propTypes</code></h3>
  2657.  
  2658.  
  2659. <p class="is-style-explanation">TL;DR Use TypeScript.</p>
  2660.  
  2661.  
  2662.  
  2663. <p>In React, the <code>propTypes</code> property can be used to check if a component is being passed the correct data type for its props. They allow you to specify the type of data that should be used for each prop such as a string, number, object, etc. They also allow you to specify if a prop is required or not.</p>
  2664.  
  2665.  
  2666.  
  2667. <p>This way, if a component is passed the wrong data type or if a required prop is not being provided, then React will throw an error.</p>
  2668.  
  2669.  
  2670.  
  2671. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">// Card.jsx
  2672. import { PropTypes } from "prop-types";
  2673.  
  2674. function Card(props) {
  2675.  // ...
  2676. }
  2677.  
  2678. Card.propTypes = {
  2679.  title: PropTypes.string.isRequired,
  2680.  description: PropTypes.string,
  2681. };
  2682.  
  2683. export default Card;</code></pre>
  2684.  
  2685.  
  2686.  
  2687. <p><a href="https://github.com/microsoft/TypeScript" rel="noopener">TypeScript</a> provides a level of type safety in data that’s being passed to components. So, sure, <code>propTypes</code> were a good idea back when I was starting. However, now that TypeScript has become the go-to solution for type safety, I would highly recommend using it over anything else.</p>
  2688.  
  2689.  
  2690.  
  2691. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">// Card.tsx
  2692. interface CardProps {
  2693.  title: string,
  2694.  description?: string,
  2695. }
  2696.  
  2697. export default function Card(props: CardProps) {
  2698.  // ...
  2699. }</code></pre>
  2700.  
  2701.  
  2702.  
  2703. <p>TypeScript is a programming language that builds on top of JavaScript by adding static type-checking. TypeScript provides a more powerful type system, that can catch more potential bugs and improves the development experience.</p>
  2704.  
  2705.  
  2706.  
  2707.    
  2708.    <div class="in-article-cards">
  2709.      <article class="in-article-card article" id="mini-post-377098">
  2710.  
  2711.  <time datetime="2018-08-31" title="Originally published Mar 10, 2023">
  2712.    <strong>
  2713.                
  2714.        Article
  2715.      </strong>
  2716.  
  2717.    on
  2718.  
  2719.    Aug 31, 2018  </time>
  2720.  
  2721.  <h3>
  2722.    <a href="https://css-tricks.com/props-and-proptypes-in-react/">
  2723.      Props and PropTypes in React    </a>
  2724.  </h3>
  2725.  
  2726.  
  2727.      <div class="tags">
  2728.      <a href="https://css-tricks.com/tag/learning/" rel="tag">learning</a> <a href="https://css-tricks.com/tag/react/" rel="tag">react</a>    </div>
  2729.  
  2730.  <div class="author-row">
  2731.    <a href="https://css-tricks.com/author/kinglseysilas/" aria-label="Author page of Kingsley Silas">
  2732.      <img alt='' src='https://secure.gravatar.com/avatar/b0d0ce2a0e59387acbf0848f62aa52bf?s=80&#038;d=retro&#038;r=pg' srcset='https://secure.gravatar.com/avatar/b0d0ce2a0e59387acbf0848f62aa52bf?s=160&#038;d=retro&#038;r=pg 2x' class='avatar avatar-80 photo' height='80' width='80' />    </a>
  2733.  
  2734.    <a class="author-name" href="https://css-tricks.com/author/kinglseysilas/">
  2735.      Kingsley Silas    </a>
  2736.  </div>
  2737.  
  2738. </article>
  2739. <article class="in-article-card article" id="mini-post-377098">
  2740.  
  2741.  <time datetime="2018-03-21" title="Originally published Mar 10, 2023">
  2742.    <strong>
  2743.                
  2744.        Article
  2745.      </strong>
  2746.  
  2747.    on
  2748.  
  2749.    Mar 27, 2018  </time>
  2750.  
  2751.  <h3>
  2752.    <a href="https://css-tricks.com/putting-things-in-context-with-react/">
  2753.      Putting Things in Context With React    </a>
  2754.  </h3>
  2755.  
  2756.  
  2757.      <div class="tags">
  2758.      <a href="https://css-tricks.com/tag/learning/" rel="tag">learning</a> <a href="https://css-tricks.com/tag/react/" rel="tag">react</a>    </div>
  2759.  
  2760.  <div class="author-row">
  2761.    <a href="https://css-tricks.com/author/nealfennimore/" aria-label="Author page of Neal Fennimore">
  2762.      <img alt='' src='https://secure.gravatar.com/avatar/fc8aa639f5c8b5362773475c3734a4db?s=80&#038;d=retro&#038;r=pg' srcset='https://secure.gravatar.com/avatar/fc8aa639f5c8b5362773475c3734a4db?s=160&#038;d=retro&#038;r=pg 2x' class='avatar avatar-80 photo' height='80' width='80' />    </a>
  2763.  
  2764.    <a class="author-name" href="https://css-tricks.com/author/nealfennimore/">
  2765.      Neal Fennimore    </a>
  2766.  </div>
  2767.  
  2768. </article>
  2769. <article class="in-article-card article" id="mini-post-377098">
  2770.  
  2771.  <time datetime="2018-11-16" title="Originally published Mar 10, 2023">
  2772.    <strong>
  2773.                
  2774.        Article
  2775.      </strong>
  2776.  
  2777.    on
  2778.  
  2779.    Nov 16, 2018  </time>
  2780.  
  2781.  <h3>
  2782.    <a href="https://css-tricks.com/an-overview-of-render-props-in-react/">
  2783.      An Overview of Render Props in React    </a>
  2784.  </h3>
  2785.  
  2786.  
  2787.      <div class="tags">
  2788.      <a href="https://css-tricks.com/tag/learning/" rel="tag">learning</a> <a href="https://css-tricks.com/tag/react/" rel="tag">react</a>    </div>
  2789.  
  2790.  <div class="author-row">
  2791.    <a href="https://css-tricks.com/author/kinglseysilas/" aria-label="Author page of Kingsley Silas">
  2792.      <img alt='' src='https://secure.gravatar.com/avatar/b0d0ce2a0e59387acbf0848f62aa52bf?s=80&#038;d=retro&#038;r=pg' srcset='https://secure.gravatar.com/avatar/b0d0ce2a0e59387acbf0848f62aa52bf?s=160&#038;d=retro&#038;r=pg 2x' class='avatar avatar-80 photo' height='80' width='80' />    </a>
  2793.  
  2794.    <a class="author-name" href="https://css-tricks.com/author/kinglseysilas/">
  2795.      Kingsley Silas    </a>
  2796.  </div>
  2797.  
  2798. </article>
  2799.    </div>
  2800.  
  2801.  
  2802.  
  2803. <h3 class="wp-block-heading" id="using-class-components">Using class components</h3>
  2804.  
  2805.  
  2806. <p class="is-style-explanation">TL;DR: Write components as functions</p>
  2807.  
  2808.  
  2809.  
  2810. <p>Class components in React are created using JavaScript classes. They have a more object-oriented structure and as well as a few additional features, like the ability to use the <code>this</code> keyword and lifecycle methods.</p>
  2811.  
  2812.  
  2813.  
  2814. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">// Card.jsx
  2815. class Card extends React.Component {
  2816.  render() {
  2817.    return (
  2818.      &lt;div>
  2819.        &lt;h1>{this.props.title}&lt;/h1>
  2820.        &lt;p>{this.props.description}&lt;/p>
  2821.      &lt;/div>
  2822.    )
  2823.  }
  2824. }
  2825.  
  2826. export default Card;</code></pre>
  2827.  
  2828.  
  2829.  
  2830. <p>I prefer writing components with classes over functions, but JavaScript classes are more difficult for beginners to understand and <code>this</code> can get very confusing. Instead, I’d recommend writing components as functions:</p>
  2831.  
  2832.  
  2833.  
  2834. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">// Card.jsx
  2835. function Card(props) {
  2836.  return (
  2837.    &lt;div>
  2838.      &lt;h1>{props.title}&lt;/h1>
  2839.      &lt;p>{props.description}&lt;/p>
  2840.    &lt;/div>
  2841.  )
  2842. }
  2843.  
  2844. export default Card;</code></pre>
  2845.  
  2846.  
  2847.  
  2848. <p>Function components are simply JavaScript functions that return JSX. They are much easier to read, and do not have additional features like the <code>this</code> keyword and <a href="https://css-tricks.com/the-circle-of-a-react-lifecycle/">lifecycle methods</a> which make them more performant than class components.</p>
  2849.  
  2850.  
  2851.  
  2852. <p>Function components also have the advantage of using hooks. <a href="https://css-tricks.com/intro-to-react-hooks/">React Hooks</a> allow you to use state and other React features without writing a class component, making your code more readable, maintainable and reusable.</p>
  2853.  
  2854.  
  2855.  
  2856.    
  2857.    <div class="in-article-cards">
  2858.      <article class="in-article-card article" id="mini-post-377098">
  2859.  
  2860.  <time datetime="2019-06-26" title="Originally published Mar 10, 2023">
  2861.    <strong>
  2862.                
  2863.        Article
  2864.      </strong>
  2865.  
  2866.    on
  2867.  
  2868.    Jul 6, 2019  </time>
  2869.  
  2870.  <h3>
  2871.    <a href="https://css-tricks.com/getting-to-know-the-usereducer-react-hook/">
  2872.      Getting to Know the useReducer React Hook    </a>
  2873.  </h3>
  2874.  
  2875.  
  2876.      <div class="tags">
  2877.      <a href="https://css-tricks.com/tag/learning/" rel="tag">learning</a> <a href="https://css-tricks.com/tag/react/" rel="tag">react</a>    </div>
  2878.  
  2879.  <div class="author-row">
  2880.    <a href="https://css-tricks.com/author/kinglseysilas/" aria-label="Author page of Kingsley Silas">
  2881.      <img alt='' src='https://secure.gravatar.com/avatar/b0d0ce2a0e59387acbf0848f62aa52bf?s=80&#038;d=retro&#038;r=pg' srcset='https://secure.gravatar.com/avatar/b0d0ce2a0e59387acbf0848f62aa52bf?s=160&#038;d=retro&#038;r=pg 2x' class='avatar avatar-80 photo' height='80' width='80' />    </a>
  2882.  
  2883.    <a class="author-name" href="https://css-tricks.com/author/kinglseysilas/">
  2884.      Kingsley Silas    </a>
  2885.  </div>
  2886.  
  2887. </article>
  2888. <article class="in-article-card article" id="mini-post-377098">
  2889.  
  2890.  <time datetime="2019-01-18" title="Originally published Mar 10, 2023">
  2891.    <strong>
  2892.                
  2893.        Article
  2894.      </strong>
  2895.  
  2896.    on
  2897.  
  2898.    May 1, 2020  </time>
  2899.  
  2900.  <h3>
  2901.    <a href="https://css-tricks.com/intro-to-react-hooks/">
  2902.      Intro to React Hooks    </a>
  2903.  </h3>
  2904.  
  2905.  
  2906.      <div class="tags">
  2907.      <a href="https://css-tricks.com/tag/learning/" rel="tag">learning</a> <a href="https://css-tricks.com/tag/react/" rel="tag">react</a>    </div>
  2908.  
  2909.  <div class="author-row">
  2910.    <a href="https://css-tricks.com/author/kinglseysilas/" aria-label="Author page of Kingsley Silas">
  2911.      <img alt='' src='https://secure.gravatar.com/avatar/b0d0ce2a0e59387acbf0848f62aa52bf?s=80&#038;d=retro&#038;r=pg' srcset='https://secure.gravatar.com/avatar/b0d0ce2a0e59387acbf0848f62aa52bf?s=160&#038;d=retro&#038;r=pg 2x' class='avatar avatar-80 photo' height='80' width='80' />    </a>
  2912.  
  2913.    <a class="author-name" href="https://css-tricks.com/author/kinglseysilas/">
  2914.      Kingsley Silas    </a>
  2915.  </div>
  2916.  
  2917. </article>
  2918. <article class="in-article-card article" id="mini-post-377098">
  2919.  
  2920.  <time datetime="2022-07-13" title="Originally published Mar 10, 2023">
  2921.    <strong>
  2922.                
  2923.        Article
  2924.      </strong>
  2925.  
  2926.    on
  2927.  
  2928.    Jul 15, 2022  </time>
  2929.  
  2930.  <h3>
  2931.    <a href="https://css-tricks.com/react-hooks-the-deep-cuts/">
  2932.      React Hooks: The Deep Cuts    </a>
  2933.  </h3>
  2934.  
  2935.  
  2936.      <div class="tags">
  2937.      <a href="https://css-tricks.com/tag/learning/" rel="tag">learning</a> <a href="https://css-tricks.com/tag/react/" rel="tag">react</a>    </div>
  2938.  
  2939.  <div class="author-row">
  2940.    <a href="https://css-tricks.com/author/blessingeneanyebe/" aria-label="Author page of Blessing Ene Anyebe">
  2941.      <img loading="lazy" decoding="async" alt="" class="avatar avatar-80 photo avatar-default" height="80" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2022/07/blessing.webp?resize=80%2C80&#038;ssl=1" width="80"  data-recalc-dims="1">    </a>
  2942.  
  2943.    <a class="author-name" href="https://css-tricks.com/author/blessingeneanyebe/">
  2944.      Blessing Ene Anyebe    </a>
  2945.  </div>
  2946.  
  2947. </article>
  2948.    </div>
  2949.  
  2950.  
  2951.  
  2952. <h3 class="wp-block-heading" id="importing-react-unnecessarily">Importing React unnecessarily</h3>
  2953.  
  2954.  
  2955. <p class="is-style-explanation">TL;DR: There’s no need to do it, unless you need hooks.</p>
  2956.  
  2957.  
  2958.  
  2959. <p>Since React 17 was released in 2020, it’s now unnecessary to import React at the top of your file whenever you create a component.</p>
  2960.  
  2961.  
  2962.  
  2963. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">import React from 'react'; // Not needed!
  2964. export default function Card() {}</code></pre>
  2965.  
  2966.  
  2967.  
  2968. <p>But we had to do that before React 17 because the JSX transformer (the thing that converts JSX into regular JavaScript) used a method called <a href="https://reactjs.org/docs/react-api.html#createelement" rel="noopener"><code>React.createElement</code></a> that would only work when importing React. Since then, a new transformer has been release which can transform JSX without the <code>createElement</code> method.</p>
  2969.  
  2970.  
  2971.  
  2972. <p>You will still need to import React to use hooks, <a href="https://css-tricks.com/render-children-in-react-using-fragment-or-array-components/">fragments</a>, and any other functions or components you might need from the library:</p>
  2973.  
  2974.  
  2975.  
  2976. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">import { useState } from 'react';
  2977.  
  2978. export default function Card() {
  2979.  const [count, setCount] = useState(0);
  2980.  // ...
  2981. }</code></pre>
  2982.  
  2983.  
  2984. <h3 class="wp-block-heading" id="those-were-my-early-mistakes">Those were my early mistakes!</h3>
  2985.  
  2986.  
  2987. <p>Maybe “mistake” is too harsh a word since some of the better practices came about later. Still, I see plenty of instances where the “old” way of doing something is still being actively used in projects and other tutorials.</p>
  2988.  
  2989.  
  2990.  
  2991. <p>To be honest, I probably made way more than five mistakes when getting started. Anytime you reach for a new tool it is going to be more like a learning journey to use it effectively, rather than flipping a switch. But these are the things I still carry with me years later!</p>
  2992.  
  2993.  
  2994.  
  2995. <p>If you’ve been using React for a while, what are some of the things you wish you knew before you started? It would be great to get a collection going to help others avoid the same struggles.</p>
  2996. <hr />
  2997. <p><small><a rel="nofollow" href="https://css-tricks.com/5-mistakes-starting-react/">5 Mistakes I Made When Starting My First React Project</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
  2998. ]]></content:encoded>
  2999. <wfw:commentRss>https://css-tricks.com/5-mistakes-starting-react/feed/</wfw:commentRss>
  3000. <slash:comments>0</slash:comments>
  3001. <post-id xmlns="com-wordpress:feed-additions:1">377098</post-id> </item>
  3002. <item>
  3003. <title>Creating a Clock with the New CSS sin() and cos() Trigonometry Functions</title>
  3004. <link>https://css-tricks.com/creating-a-clock-with-the-new-css-sin-and-cos-trigonometry-functions/</link>
  3005. <comments>https://css-tricks.com/creating-a-clock-with-the-new-css-sin-and-cos-trigonometry-functions/#comments</comments>
  3006. <dc:creator><![CDATA[Mads Stoumann]]></dc:creator>
  3007. <pubDate>Wed, 08 Mar 2023 14:05:52 +0000</pubDate>
  3008. <category><![CDATA[Article]]></category>
  3009. <category><![CDATA[cos()]]></category>
  3010. <category><![CDATA[math]]></category>
  3011. <category><![CDATA[sin()]]></category>
  3012. <guid isPermaLink="false">https://css-tricks.com/?p=377074</guid>
  3013.  
  3014. <description><![CDATA[<p>CSS trigonometry functions are here! Well, they are if you’re using the latest versions of Firefox and Safari, that is. Having this sort of mathematical power in CSS opens up a whole bunch of possibilities. In this tutorial, I thought &#8230;</p>
  3015. <hr />
  3016. <p><small><a rel="nofollow" href="https://css-tricks.com/creating-a-clock-with-the-new-css-sin-and-cos-trigonometry-functions/">Creating a Clock with the New CSS sin() and cos() Trigonometry Functions</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
  3017. ]]></description>
  3018. <content:encoded><![CDATA[
  3019. <p>CSS trigonometry functions are here! Well, they are if you’re using the latest versions of Firefox and Safari, that is. Having this sort of mathematical power in CSS opens up a whole bunch of possibilities. In this tutorial, I thought we’d dip our toes in the water to get a feel for a couple of the newer functions: <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/sin" rel="noopener"><code>sin()</code></a> and <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/cos" rel="noopener"><code>cos()</code></a>.</p>
  3020.  
  3021.  
  3022.  
  3023. <p>There are other trigonometry functions in the pipeline — including <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/tan" rel="noopener"><code>tan()</code></a> — so why focus just on <code>sin()</code> and <code>cos()</code>? They happen to be perfect for the idea I have in mind, which is to place text along the edge of a circle. That’s been covered here on CSS-Tricks when <a href="https://css-tricks.com/set-text-on-a-circle/">Chris shared an approach that uses a Sass mixin</a>. That was six years ago, so let’s give it the bleeding edge treatment.</p>
  3024.  
  3025.  
  3026.  
  3027. <span id="more-377074"></span>
  3028.  
  3029.  
  3030.  
  3031. <p>Here’s what I have in mind. Again, it’s only supported in Firefox and Safari at the moment:</p>
  3032.  
  3033.  
  3034.  
  3035. <div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_wvxOQKo" src="//codepen.io/anon/embed/wvxOQKo?height=650&amp;theme-id=1&amp;slug-hash=wvxOQKo&amp;default-tab=result" height="650" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed wvxOQKo" title="CodePen Embed wvxOQKo" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>
  3036.  
  3037.  
  3038.  
  3039. <p>So, it’s not exactly like words forming a circular shape, but we are placing text characters along the circle to form a clock face. Here’s some markup we can use to kick things off:</p>
  3040.  
  3041.  
  3042.  
  3043. <pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;div class="clock">
  3044.  &lt;div class="clock-face">
  3045.    &lt;time datetime="12:00">12&lt;/time>
  3046.    &lt;time datetime="1:00">1&lt;/time>
  3047.    &lt;time datetime="2:00">2&lt;/time>
  3048.    &lt;time datetime="3:00">3&lt;/time>
  3049.    &lt;time datetime="4:00">4&lt;/time>
  3050.    &lt;time datetime="5:00">5&lt;/time>
  3051.    &lt;time datetime="6:00">6&lt;/time>
  3052.    &lt;time datetime="7:00">7&lt;/time>
  3053.    &lt;time datetime="8:00">8&lt;/time>
  3054.    &lt;time datetime="9:00">9&lt;/time>
  3055.    &lt;time datetime="10:00">10&lt;/time>
  3056.    &lt;time datetime="11:00">11&lt;/time>
  3057.  &lt;/div>
  3058. &lt;/div></code></pre>
  3059.  
  3060.  
  3061.  
  3062. <p>Next,&nbsp;here are&nbsp;some&nbsp;super&nbsp;basic styles for the&nbsp;<code>.clock-face</code>&nbsp;container. I decided to use&nbsp;the&nbsp;<code>&lt;time&gt;</code> tag with a&nbsp;<code>datetime</code> attribute.&nbsp;</p>
  3063.  
  3064.  
  3065.  
  3066. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.clock {
  3067.  --_ow: clamp(5rem, 60vw, 40rem);
  3068.  --_w: 88cqi;
  3069.  aspect-ratio: 1;
  3070.  background-color: tomato;
  3071.  border-radius: 50%;
  3072.  container-type: inline;
  3073.  display: grid;
  3074.  height: var(--_ow);
  3075.  place-content: center;
  3076.  position: relative;
  3077.  width var(--_ow);
  3078. }</code></pre>
  3079.  
  3080.  
  3081.  
  3082. <p>I decorated things a bit in there, but only to get the basic shape and background color to help us see what we’re doing. Notice how we save the <code>width</code> value in a <a href="https://css-tricks.com/a-complete-guide-to-custom-properties/">CSS variable</a>. We’ll use that later. Not much to look at so far:</p>
  3083.  
  3084.  
  3085.  
  3086. <figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="315" height="373" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/1_art_experiment.png?resize=315%2C373&#038;ssl=1" alt="Large tomato colored circle with a vertical list of numbers 1-12 on the left." class="wp-image-377159" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/1_art_experiment.png?w=315&amp;ssl=1 315w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/1_art_experiment.png?resize=253%2C300&amp;ssl=1 253w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /></figure>
  3087.  
  3088.  
  3089.  
  3090. <p>It looks like some sort of modern art experiment, right? Let’s introduce a new variable, <code>--_r</code>, to store the circle’s <strong>radius</strong>, which is equal to half of the circle’s width. This way, if the width (<code>--_w</code>) changes, the radius value (<code>--_r</code>) will also update — thanks to another CSS math function, <code>calc()</code>:</p>
  3091.  
  3092.  
  3093.  
  3094. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.clock {
  3095.  --_w: 300px;
  3096.  --_r: calc(var(--_w) / 2);
  3097.  /* rest of styles */
  3098. }</code></pre>
  3099.  
  3100.  
  3101.  
  3102. <p>Now, a bit of math. A circle is 360 degrees. We have 12 labels on our clock, so want to place the numbers every 30 degrees (<code>360 / 12</code>). In math-land, a circle begins at 3 o’clock, so noon is actually <strong>minus 90 degrees</strong> from that, which is 270 degrees (<code>360 - 90</code>).</p>
  3103.  
  3104.  
  3105.  
  3106. <p>Let’s add another variable, <code>--_d</code>, that we can use to set a <strong>degree</strong> value for each number on the clock face. We’re going to increment the values by 30 degrees to complete our circle:</p>
  3107.  
  3108.  
  3109.  
  3110. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.clock time:nth-child(1) { --_d: 270deg; }
  3111. .clock time:nth-child(2) { --_d: 300deg; }
  3112. .clock time:nth-child(3) { --_d: 330deg; }
  3113. .clock time:nth-child(4) { --_d: 0deg; }
  3114. .clock time:nth-child(5) { --_d: 30deg; }
  3115. .clock time:nth-child(6) { --_d: 60deg; }
  3116. .clock time:nth-child(7) { --_d: 90deg; }
  3117. .clock time:nth-child(8) { --_d: 120deg; }
  3118. .clock time:nth-child(9) { --_d: 150deg; }
  3119. .clock time:nth-child(10) { --_d: 180deg; }
  3120. .clock time:nth-child(11) { --_d: 210deg; }
  3121. .clock time:nth-child(12) { --_d: 240deg; }</code></pre>
  3122.  
  3123.  
  3124.  
  3125. <p>OK, now’s the time to get our hands dirty with the <code>sin()</code> and <code>cos()</code> functions! What we want to do is use them to get the X and Y coordinates for each number so we can place them properly around the clock face.</p>
  3126.  
  3127.  
  3128.  
  3129. <p>The formula for the X coordinate is <code>radius + (radius * cos(degree))</code>. Let’s plug that into our new <code>--_x</code> variable:</p>
  3130.  
  3131.  
  3132.  
  3133. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">--_x: calc(var(--_r) + (var(--_r) * cos(var(--_d))));</code></pre>
  3134.  
  3135.  
  3136.  
  3137. <p>The formula for the Y coordinate is <code>radius + (radius * sin(degree))</code>. We have what we need to calculate that:</p>
  3138.  
  3139.  
  3140.  
  3141. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">--_y: calc(var(--_r) + (var(--_r) * sin(var(--_d))));</code></pre>
  3142.  
  3143.  
  3144.  
  3145. <p>There are a few housekeeping things we need to do to set up the numbers, so let’s put some basic styling on them to make sure they are absolutely positioned and placed with our coordinates:</p>
  3146.  
  3147.  
  3148.  
  3149. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.clock-face time {
  3150.  --_x: calc(var(--_r) + (var(--_r) * cos(var(--_d))));
  3151.  --_y: calc(var(--_r) + (var(--_r) * sin(var(--_d))));
  3152.  --_sz: 12cqi;
  3153.  display: grid;
  3154.  height: var(--_sz);
  3155.  left: var(--_x);
  3156.  place-content: center;
  3157.  position: absolute;
  3158.  top: var(--_y);
  3159.  width: var(--_sz);
  3160. }</code></pre>
  3161.  
  3162.  
  3163.  
  3164. <p>Notice <code>--_sz</code>, which we’ll use for the <code>width</code> and <code>height</code> of the numbers in a moment. Let’s see what we have so far.</p>
  3165.  
  3166.  
  3167.  
  3168. <figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="348" height="364" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/2_off_pos.png?resize=348%2C364&#038;ssl=1" alt="Large tomato colored circle with off-centered hour number labels along its edge." class="wp-image-377161" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/2_off_pos.png?w=348&amp;ssl=1 348w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/2_off_pos.png?resize=287%2C300&amp;ssl=1 287w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /></figure>
  3169.  
  3170.  
  3171.  
  3172. <p>This definitely looks more like a clock! See how the top-left corner of each number is positioned at the correct place around the circle? We need to “shrink” the radius when calculating the positions for each number. We can <em>deduct</em> the size of a number (<code>--_sz</code>) from the size of the circle (<code>--_w</code>), before we calculate the radius:</p>
  3173.  
  3174.  
  3175.  
  3176. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">--_r: calc((var(--_w) - var(--_sz)) / 2);</code></pre>
  3177.  
  3178.  
  3179.  
  3180. <figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="314" height="314" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/3_correct_pos.png?resize=314%2C314&#038;ssl=1" alt="Large tomato colored circle with hour number labels along its rounded edge." class="wp-image-377164" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/3_correct_pos.png?w=314&amp;ssl=1 314w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/3_correct_pos.png?resize=300%2C300&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/3_correct_pos.png?resize=150%2C150&amp;ssl=1 150w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /></figure>
  3181.  
  3182.  
  3183.  
  3184. <p>Much better! Let’s change the colors, so it looks more elegant:</p>
  3185.  
  3186.  
  3187.  
  3188. <figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="717" height="689" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/nY6Vbx2b.png?resize=717%2C689&#038;ssl=1" alt="A white clock face with numbers against a dark gray background. The clock has no arms." class="wp-image-377119" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/nY6Vbx2b.png?w=717&amp;ssl=1 717w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/nY6Vbx2b.png?resize=300%2C288&amp;ssl=1 300w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /></figure>
  3189.  
  3190.  
  3191.  
  3192. <p>We could stop right here! We accomplished the goal of placing text around a circle, right? But what’s a clock without arms to show hours, minutes, and seconds?</p>
  3193.  
  3194.  
  3195.  
  3196. <p>Let’s use a single CSS animation for that. First, let’s add three more elements to our markup,</p>
  3197.  
  3198.  
  3199.  
  3200. <pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;div class="clock">
  3201.  &lt;!-- after &lt;time>-tags -->
  3202.  &lt;span class="arm seconds">&lt;/span>
  3203.  &lt;span class="arm minutes">&lt;/span>
  3204.  &lt;span class="arm hours">&lt;/span>
  3205.  &lt;span class="arm center">&lt;/span>
  3206. &lt;/div></code></pre>
  3207.  
  3208.  
  3209.  
  3210. <p>Then some common markup for all three arms. Again, most of this is just make sure the arms are absolutely positioned and placed accordingly:</p>
  3211.  
  3212.  
  3213.  
  3214. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.arm {
  3215.  background-color: var(--_abg);
  3216.  border-radius: calc(var(--_aw) * 2);
  3217.  display: block;
  3218.  height: var(--_ah);
  3219.  left: calc((var(--_w) - var(--_aw)) / 2);
  3220.  position: absolute;
  3221.  top: calc((var(--_w) / 2) - var(--_ah));
  3222.  transform: rotate(0deg);
  3223.  transform-origin: bottom;
  3224.  width: var(--_aw);
  3225. }</code></pre>
  3226.  
  3227.  
  3228.  
  3229. <p>We’ll use the <strong>same animation</strong> for all three arms:</p>
  3230.  
  3231.  
  3232.  
  3233. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">@keyframes turn {
  3234.  to {
  3235.    transform: rotate(1turn);
  3236.  }
  3237. }</code></pre>
  3238.  
  3239.  
  3240.  
  3241. <p>The only difference is the time the individual arms take to make a full turn. For the <strong>hours arm</strong>, it takes <strong>12 hours</strong> to make a full turn. The <a href="https://css-tricks.com/almanac/properties/a/animation/"><code>animation-duration</code></a> property only accepts values in milliseconds and seconds. Let’s stick with seconds, which is 43,200 seconds (<code>60 seconds * 60 minutes * 12 hours</code>).</p>
  3242.  
  3243.  
  3244.  
  3245. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">animation: turn 43200s infinite;</code></pre>
  3246.  
  3247.  
  3248.  
  3249. <p>It takes <strong>1 hour</strong> for the <strong>minutes arm</strong> to make a full turn. But we want this to be a <a href="https://css-tricks.com/using-multi-step-animations-transitions/">multi-step animation</a> so the movement between the arms is staggered rather than linear. We’ll need 60 steps, one for each minute:</p>
  3250.  
  3251.  
  3252.  
  3253. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">animation: turn 3600s steps(60, end) infinite;</code></pre>
  3254.  
  3255.  
  3256.  
  3257. <p>The <strong>seconds arm</strong> is <em>almost the same</em> as the minutes arm, but the duration is 60 seconds instead of 60 minutes:</p>
  3258.  
  3259.  
  3260.  
  3261. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">animation: turn 60s steps(60, end) infinite;</code></pre>
  3262.  
  3263.  
  3264.  
  3265. <p>Let’s update the properties we created in the common styles:</p>
  3266.  
  3267.  
  3268.  
  3269. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.seconds {
  3270.  --_abg: hsl(0, 5%, 40%);
  3271.  --_ah: 145px;
  3272.  --_aw: 2px;
  3273.  animation: turn 60s steps(60, end) infinite;
  3274. }
  3275. .minutes {
  3276.  --_abg: #333;
  3277.  --_ah: 145px;
  3278.  --_aw: 6px;
  3279.  animation: turn 3600s steps(60, end) infinite;
  3280. }
  3281. .hours {
  3282.  --_abg: #333;
  3283.  --_ah: 110px;
  3284.  --_aw: 6px;
  3285.  animation: turn 43200s linear infinite;
  3286. }</code></pre>
  3287.  
  3288.  
  3289.  
  3290. <p>What if we want to start at the current time? We need a little bit of JavaScript:</p>
  3291.  
  3292.  
  3293.  
  3294. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">const time = new Date();
  3295. const hour = -3600 * (time.getHours() % 12);
  3296. const mins = -60 * time.getMinutes();
  3297. app.style.setProperty('--_dm', `${mins}s`);
  3298. app.style.setProperty('--_dh', `${(hour+mins)}s`);</code></pre>
  3299.  
  3300.  
  3301.  
  3302. <p>I’ve added&nbsp;<code>id="app"</code>&nbsp;to the clockface and set two new custom properties on it that set a negative&nbsp;<code>animation-delay</code>, as Mate Marschalko did <a href="https://css-tricks.com/of-course-we-can-make-a-css-only-clock-that-tells-the-current-time/">when he shared a CSS-only clock</a>. The&nbsp;<code>getHours()</code> method of JavaScipt&#8217;s&nbsp;<code>Date</code> object is using the 24-hour format, so we use the&nbsp;<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Remainder" target="_blank" rel="noreferrer noopener"><code>remainder</code> operator</a>&nbsp;to convert it into 12-hour format.</p>
  3303.  
  3304.  
  3305.  
  3306. <p>In the CSS, we need to add the&nbsp;<code>animation-delay</code>&nbsp;as well:</p>
  3307.  
  3308.  
  3309.  
  3310. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.minutes {
  3311.  animation-delay: var(--_dm, 0s);
  3312.  /* other styles */
  3313. }
  3314.  
  3315. .hours {
  3316.  animation-delay: var(--_dh, 0s);
  3317.  /* other styles */
  3318. }</code></pre>
  3319.  
  3320.  
  3321.  
  3322. <p><strong>Just one more thing.</strong> Using CSS&nbsp;<code>@supports</code>&nbsp;and the properties we’ve already created, we can provide a fallback to browsers that do not supprt&nbsp;<code>sin()</code>&nbsp;and&nbsp;<code>cos()</code>. (Thank you, Temani Afif!):</p>
  3323.  
  3324.  
  3325.  
  3326. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">@supports not (left: calc(1px * cos(45deg))) {
  3327.   time {
  3328.     left: 50% !important;
  3329.     top: 50% !important;
  3330.     transform: translate(-50%,-50%) rotate(var(--_d)) translate(var(--_r)) rotate(calc(-1*var(--_d)))
  3331.   }
  3332. }</code></pre>
  3333.  
  3334.  
  3335.  
  3336. <p>And, voilà! Our clock is done! Here’s the final demo one more time. Again, it’s only supported in Firefox and Safari at the moment.</p>
  3337.  
  3338.  
  3339.  
  3340. <div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_wvxOQKo" src="//codepen.io/anon/embed/preview/wvxOQKo?height=650&amp;theme-id=1&amp;slug-hash=wvxOQKo&amp;default-tab=result" height="650" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed wvxOQKo" title="CodePen Embed wvxOQKo" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>
  3341.  
  3342.  
  3343. <h3 class="wp-block-heading" id="what-else-can-we-do">What else can we do?</h3>
  3344.  
  3345.  
  3346. <p>Just messing around here, but we can quickly turn our clock into a circular image gallery by replacing the&nbsp;<code>&lt;time&gt;</code>&nbsp;tags with&nbsp;<code>&lt;img&gt;</code> then updating the width (<code>--_w</code>) and radius (<code>--_r</code>) values:</p>
  3347.  
  3348.  
  3349.  
  3350. <div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_abjQeZy" src="//codepen.io/anon/embed/abjQeZy?height=700&amp;theme-id=1&amp;slug-hash=abjQeZy&amp;default-tab=result" height="700" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed abjQeZy" title="CodePen Embed abjQeZy" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>
  3351.  
  3352.  
  3353.  
  3354. <p>Let’s try one more. I mentioned earlier how the clock looked kind of like a modern art experiment. We can lean into that and re-create a pattern I saw on a poster (that I unfortunately didn’t buy) in an art gallery the other day. As I recall, it was called “Moon” and consisted of a bunch of dots forming a circle.</p>
  3355.  
  3356.  
  3357.  
  3358. <figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="897" height="874" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_2756772B6AAC4896CD9BFF036E07360F7C8D595A4B775A1B72DBF756AC4018A4_1675325303440_image.png?resize=897%2C874&#038;ssl=1" alt="A large circle formed out of a bunch of smaller filled circles of various earthtone colors." class="wp-image-377080" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_2756772B6AAC4896CD9BFF036E07360F7C8D595A4B775A1B72DBF756AC4018A4_1675325303440_image.png?w=897&amp;ssl=1 897w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_2756772B6AAC4896CD9BFF036E07360F7C8D595A4B775A1B72DBF756AC4018A4_1675325303440_image.png?resize=300%2C292&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_2756772B6AAC4896CD9BFF036E07360F7C8D595A4B775A1B72DBF756AC4018A4_1675325303440_image.png?resize=768%2C748&amp;ssl=1 768w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /></figure>
  3359.  
  3360.  
  3361.  
  3362. <p>We’ll use an unordered list this time since the circles don’t follow a particular order. We’re not even going to put all the list items in the markup. Instead, let’s inject them with JavaScript and add a few controls we can use to manipulate the final result.</p>
  3363.  
  3364.  
  3365.  
  3366. <p>The controls are range inputs (<code>&lt;input type="range"&gt;)</code> which we’ll wrap in a <code>&lt;form&gt;</code> and listen for the <code>input</code> event.</p>
  3367.  
  3368.  
  3369.  
  3370. <pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;form id="controls">
  3371.  &lt;fieldset>
  3372.    &lt;label>Number of rings
  3373.      &lt;input type="range" min="2" max="12" value="10" id="rings" />
  3374.    &lt;/label>
  3375.    &lt;label>Dots per ring
  3376.      &lt;input type="range" min="5" max="12" value="7" id="dots" />
  3377.    &lt;/label>
  3378.    &lt;label>Spread
  3379.      &lt;input type="range" min="10" max="40" value="40" id="spread" />
  3380.    &lt;/label>
  3381.  &lt;/fieldset>
  3382. &lt;/form></code></pre>
  3383.  
  3384.  
  3385.  
  3386. <p>We’ll run this method on “input”, which will create a bunch of <code>&lt;li&gt;</code> elements with the degree (<code>--_d</code>) variable we used earlier applied to each one. We can also repurpose our radius variable (<code>--_r</code>) .</p>
  3387.  
  3388.  
  3389.  
  3390. <p>I also want the dots to be different colors. So, let’s randomize (well, not <em>completely</em> randomized) the HSL color value for each list item and store it as a new CSS variable, <code>--_bgc</code>:</p>
  3391.  
  3392.  
  3393.  
  3394. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">const update = () => {
  3395.  let s = "";
  3396.  for (let i = 1; i &lt;= rings.valueAsNumber; i++) {
  3397.    const r = spread.valueAsNumber * i;
  3398.    const theta = coords(dots.valueAsNumber * i);
  3399.    for (let j = 0; j &lt; theta.length; j++) {
  3400.      s += `&lt;li style="--_d:${theta[j]};--_r:${r}px;--_bgc:hsl(${random(
  3401.        50,
  3402.        25
  3403.      )},${random(90, 50)}%,${random(90, 60)}%)">&lt;/li>`;
  3404.    }
  3405.  }
  3406.  app.innerHTML = s;
  3407. }</code></pre>
  3408.  
  3409.  
  3410.  
  3411. <p>The <code>random()</code> method picks a value within a defined range of numbers:</p>
  3412.  
  3413.  
  3414.  
  3415. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">const random = (max, min = 0, f = true) => f ? Math.floor(Math.random() * (max - min) + min) : Math.random() * max;</code></pre>
  3416.  
  3417.  
  3418.  
  3419. <p>And that’s it. We use JavaScript to render the markup, but as soon as it’s rendered, we don’t really need it. The <code>sin()</code> and <code>cos()</code> functions help us position all the dots in the right spots.</p>
  3420.  
  3421.  
  3422.  
  3423. <div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_oNMJvjO" src="//codepen.io/anon/embed/oNMJvjO?height=850&amp;theme-id=1&amp;slug-hash=oNMJvjO&amp;default-tab=result" height="850" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed oNMJvjO" title="CodePen Embed oNMJvjO" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>
  3424.  
  3425.  
  3426. <h3 class="wp-block-heading" id="final-thoughts">Final thoughts</h3>
  3427.  
  3428.  
  3429. <p>Placing things around a circle is a pretty basic example to demonstrate the powers of trigonometry functions like <code>sin()</code> and <code>cos()</code>. But it’s <em>really</em> cool that we are getting modern CSS features that provide new solutions for old workarounds I’m sure we’ll see way more interesting, complex, and creative use cases, especially as browser support comes to Chrome and Edge.</p>
  3430. <hr />
  3431. <p><small><a rel="nofollow" href="https://css-tricks.com/creating-a-clock-with-the-new-css-sin-and-cos-trigonometry-functions/">Creating a Clock with the New CSS sin() and cos() Trigonometry Functions</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
  3432. ]]></content:encoded>
  3433. <wfw:commentRss>https://css-tricks.com/creating-a-clock-with-the-new-css-sin-and-cos-trigonometry-functions/feed/</wfw:commentRss>
  3434. <slash:comments>1</slash:comments>
  3435. <post-id xmlns="com-wordpress:feed-additions:1">377074</post-id> </item>
  3436. <item>
  3437. <title>Managing Fonts in WordPress Block Themes</title>
  3438. <link>https://css-tricks.com/managing-fonts-in-wordpress-block-themes/</link>
  3439. <comments>https://css-tricks.com/managing-fonts-in-wordpress-block-themes/#comments</comments>
  3440. <dc:creator><![CDATA[Ganesh Dahal]]></dc:creator>
  3441. <pubDate>Mon, 06 Mar 2023 15:26:31 +0000</pubDate>
  3442. <category><![CDATA[Article]]></category>
  3443. <category><![CDATA[fonts]]></category>
  3444. <category><![CDATA[google fonts]]></category>
  3445. <category><![CDATA[WordPress]]></category>
  3446. <guid isPermaLink="false">https://css-tricks.com/?p=377123</guid>
  3447.  
  3448. <description><![CDATA[<p>Fonts are a defining characteristic of the design of any site. That includes WordPress themes, where it’s common for theme developers to integrate a service like <a href="https://fonts.google.com" rel="noopener">Google Fonts</a> into the WordPress Customizer settings for a “classic” PHP-based theme. That hasn’t &#8230;</p>
  3449. <hr />
  3450. <p><small><a rel="nofollow" href="https://css-tricks.com/managing-fonts-in-wordpress-block-themes/">Managing Fonts in WordPress Block Themes</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
  3451. ]]></description>
  3452. <content:encoded><![CDATA[
  3453. <p>Fonts are a defining characteristic of the design of any site. That includes WordPress themes, where it’s common for theme developers to integrate a service like <a href="https://fonts.google.com" rel="noopener">Google Fonts</a> into the WordPress Customizer settings for a “classic” PHP-based theme. That hasn’t quite been the case for WordPress block themes. While integrating Google Fonts into classic themes is well-documented, there’s nothing currently available for block themes in the <a href="https://developer.wordpress.org/themes/" rel="noopener">WordPress Theme Handbook</a>.</p>
  3454.  
  3455.  
  3456.  
  3457. <p>That’s what we’re going to look at in this article. Block themes can indeed use Google Fonts, but the process for registering them is way different than what you might have done before in classic themes.</p>
  3458.  
  3459.  
  3460.  
  3461. <span id="more-377123"></span>
  3462.  
  3463.  
  3464. <h3 class="wp-block-heading" id="what-we-already-know">What we already know</h3>
  3465.  
  3466.  
  3467. <p>As I said, there’s little for us to go on as far as getting started. The Twenty Twenty-Two theme is the first block-based default WordPress theme, and it demonstrates how we can use downloaded font files as assets in the theme. But it’s pretty unwieldy because it involves a couple of steps: (1) <a href="https://github.com/WordPress/twentytwentytwo/blob/trunk/functions.php#L90-L148" rel="noopener">register the files in the <code>functions.php</code> file</a> and (2) define the bundled fonts <a href="https://github.com/WordPress/twentytwentytwo/blob/trunk/theme.json#L172-L183" rel="noopener">in the <code>theme.json</code> file</a>.</p>
  3468.  
  3469.  
  3470.  
  3471. <p>Since Twenty Twenty-Two was released, though, the process has gotten simpler. Bundled fonts can now be defined without registering them, as shown in the <a href="https://github.com/WordPress/twentytwentythree/blob/trunk/theme.json#L111-L150" rel="noopener">Twenty Twenty-Three theme</a>. However, the process still requires us to manually download font files and bundle them into the themes. That’s a hindrance that sort of defeats the purpose of simple, drop-in, hosted fonts that are served on a speedy CDN.</p>
  3472.  
  3473.  
  3474. <h3 class="wp-block-heading" id="whats-new">What’s new</h3>
  3475.  
  3476.  
  3477. <p>If you didn’t already know, the <a href="https://wordpress.org/plugins/gutenberg/" rel="noopener">Gutenberg project</a> is an experimental plugin where features being developed for the WordPress Block and Site Editor are available for early use and testing. In a recent <a href="https://themeshaper.com/2022/11/28/how-to-add-typographic-fonts-to-wordpress-block-themes/" rel="noopener">Theme Shaper article</a>, Gutenberg project lead architect <a href="https://matiasventura.com/" rel="noopener">Matias Ventura</a> discusses how Google Fonts — or any other downloaded fonts, for that matter — can be added to block themes using the <a href="https://wordpress.org/plugins/create-block-theme/" rel="noopener">Create Block Theme</a> plugin.</p>
  3478.  
  3479.  
  3480.  
  3481. <p>This short <a href="https://learn.wordpress.org/tutorial/manage-your-block-theme-fonts-with-create-block-theme/" rel="noopener">video at Learn WordPress</a> provides a good overview of the Create Block Theme plugin and how it works. But the bottom line is that it does what it says on the tin: it creates block themes. But it does it by providing controls in the WordPress UI that allow you to create an entire theme, child theme, or a theme style variation without writing any code or ever having to touch template files.</p>
  3482.  
  3483.  
  3484.  
  3485. <p>I’ve given it a try! And since Create Block Theme is <a href="https://wordpress.org/plugins/create-block-theme/" rel="noopener">authored and maintained by the WordPress.org</a> team, I’d say it’s the best direction we have for integrating Google Fonts into a theme. That said, it’s definitely worth noting that the plugin is in active development. That means things could change pretty quickly.</p>
  3486.  
  3487.  
  3488.  
  3489. <p>Before I get to how it all works, let’s first briefly refresh ourselves with the “traditional” process for adding Google Fonts to classic WordPress themes.</p>
  3490.  
  3491.  
  3492. <h3 class="wp-block-heading" id="how-it-used-to-be-done">How it used to be done</h3>
  3493.  
  3494.  
  3495. <p><a href="https://themeshaper.com/2014/08/13/how-to-add-google-fonts-to-wordpress-themes/" rel="noopener">This ThemeShaper article from 2014</a> provides an excellent example of how we used to do this in classic PHP themes, as is <a href="https://www.cloudways.com/blog/google-fonts-wordpress/?utm_medium=content_acq&amp;utm_source=css-tricks&amp;utm_campaign=global_cloudways_post_en&amp;utm_content=awareness_wordpress-fonts" rel="noopener">this newer Cloudways article by Ibad Ur Rehman</a>.</p>
  3496.  
  3497.  
  3498.  
  3499. <p>To refresh our memory, here is an example from the default <a href="https://github.com/WordPress/twentyseventeen/blob/master/functions.php#L127-L174" rel="noopener">Twenty Seventeen theme</a> showing how Google fonts are enqueued in the <code>functions.php</code> file.</p>
  3500.  
  3501.  
  3502.  
  3503. <pre rel="PHP" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">function twentyseventeen_fonts_url() {
  3504.  $fonts_url = '';
  3505.  /**
  3506.   * Translators: If there are characters in your language that are not
  3507.   * supported by Libre Franklin, translate this to 'off'. Do not translate
  3508.   * into your own language.
  3509.   */
  3510.  $libre_franklin = _x( 'on', 'libre_franklin font: on or off', 'twentyseventeen' );
  3511.  if ( 'off' !== $libre_franklin ) {
  3512.    $font_families = array();
  3513.    $font_families[] = 'Libre Franklin:300,300i,400,400i,600,600i,800,800i';
  3514.    $query_args = array(
  3515.      'family' => urlencode( implode( '|', $font_families ) ),
  3516.      'subset' => urlencode( 'latin,latin-ext' ),
  3517.    );
  3518.    $fonts_url = add_query_arg( $query_args, 'https://fonts.googleapis.com/css' );
  3519.  }
  3520.  return esc_url_raw( $fonts_url );
  3521. }</code></pre>
  3522.  
  3523.  
  3524.  
  3525. <p>Then Google Fonts is pre-connected to the theme like this:</p>
  3526.  
  3527.  
  3528.  
  3529. <pre rel="PHP" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">function twentyseventeen_resource_hints( $urls, $relation_type ) {
  3530.  if ( wp_style_is( 'twentyseventeen-fonts', 'queue' ) &amp;&amp; 'preconnect' === $relation_type ) {
  3531.    $urls[] = array(
  3532.      'href' => 'https://fonts.gstatic.com',
  3533.      'crossorigin',
  3534.    );
  3535.  }
  3536.  return $urls;
  3537. }
  3538. add_filter( 'wp_resource_hints', 'twentyseventeen_resource_hints', 10, 2 );</code></pre>
  3539.  
  3540.  
  3541. <h3 class="wp-block-heading" id="whats-wrong-with-the-traditional-way">What’s wrong with the traditional way</h3>
  3542.  
  3543.  
  3544. <p>Great, right? There’s a hitch, however. In January 2022, a <a href="https://rewis.io/urteile/urteil/lhm-20-01-2022-3-o-1749320/" rel="noopener">German regional court imposed a fine</a> on a website owner for violating Europe’s <a href="https://www.gdpreu.org" rel="noopener">GDPR requirements</a>. The issue? Enqueuing Google Fonts on the site exposed a visitor&#8217;s IP address, jeopardizing user privacy. <a href="https://css-tricks.com/bunny-fonts/">CSS-Tricks covered this a while back.</a></p>
  3545.  
  3546.  
  3547.  
  3548. <p>The <a href="https://wordpress.org/plugins/create-block-theme/" rel="noopener">Create Block Theme</a> plugin satisfies GDPR privacy requirements, as it leverages the Google Fonts API to serve solely as a proxy for the local vendor. The fonts are served to the user on the same website rather than on Google&#8217;s servers, protecting privacy. <a href="https://wptavern.com/german-court-fines-website-owner-for-violating-the-gdpr-by-using-google-hosted-fonts" rel="noopener">WP Tavern</a> discusses the German court ruling and includes links to guides for self-hosting Google Fonts.</p>
  3549.  
  3550.  
  3551. <h3 class="wp-block-heading" id="how-to-use-google-fonts-with-block-themes">How to use Google Fonts with block themes</h3>
  3552.  
  3553.  
  3554. <p>This brings us to today’s “modern” way of using Google Fonts with WordPress block themes. First, let&#8217;s set up a local test site. I use Flywheel’s <a href="https://localwp.com/" rel="noopener">Local</a> app for local development. You can use that or whatever you prefer, then use the <a href="https://github.com/WPTT/theme-test-data" rel="noopener">Theme Test Data plugin</a> by the WordPress Themes Team to work with dummy content. And, of course, you’ll want the <a href="https://wordpress.org/plugins/create-block-theme/" rel="noopener">Create Block Theme</a> plugin in there as well.</p>
  3555.  
  3556.  
  3557.  
  3558. <p>Have you installed and activated those plugins? If so, navigate to <strong>Appearance</strong> → <strong>Manage theme fonts</strong> from the WordPress admin menu.</p>
  3559.  
  3560.  
  3561.  
  3562. <figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="2560" height="1400" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_CCF1BA23A68924924E32749C4E8AADBAA675591E1F3A6BB9A642F97CA9EA7227_1675701055783_screenshot-2.jpg?resize=2560%2C1400&#038;ssl=1" alt="Manage Theme Fonts screen with type samples for Space Mono." class="wp-image-377124" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_CCF1BA23A68924924E32749C4E8AADBAA675591E1F3A6BB9A642F97CA9EA7227_1675701055783_screenshot-2.jpg?w=2560&amp;ssl=1 2560w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_CCF1BA23A68924924E32749C4E8AADBAA675591E1F3A6BB9A642F97CA9EA7227_1675701055783_screenshot-2.jpg?resize=300%2C164&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_CCF1BA23A68924924E32749C4E8AADBAA675591E1F3A6BB9A642F97CA9EA7227_1675701055783_screenshot-2.jpg?resize=1024%2C560&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_CCF1BA23A68924924E32749C4E8AADBAA675591E1F3A6BB9A642F97CA9EA7227_1675701055783_screenshot-2.jpg?resize=768%2C420&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_CCF1BA23A68924924E32749C4E8AADBAA675591E1F3A6BB9A642F97CA9EA7227_1675701055783_screenshot-2.jpg?resize=1536%2C840&amp;ssl=1 1536w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/s_CCF1BA23A68924924E32749C4E8AADBAA675591E1F3A6BB9A642F97CA9EA7227_1675701055783_screenshot-2.jpg?resize=2048%2C1120&amp;ssl=1 2048w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /><figcaption class="wp-element-caption">Source:&nbsp;<a href="https://wordpress.org/plugins/create-block-theme/#developers" target="_blank" rel="noreferrer noopener">WordPress Theme Directory</a></figcaption></figure>
  3563.  
  3564.  
  3565.  
  3566. <p>The “Manage theme fonts” screen displays a list of any fonts already defined in the theme’s <code>theme.json</code> file. There are also two options at the top of the screen:</p>
  3567.  
  3568.  
  3569.  
  3570. <ul>
  3571. <li><strong>Add Google fonts.</strong> This option adds Google Fonts directly to the theme from the Google fonts API.</li>
  3572.  
  3573.  
  3574.  
  3575. <li><strong>Add local fonts.</strong> This option adds downloaded font files to the theme.</li>
  3576. </ul>
  3577.  
  3578.  
  3579.  
  3580. <p>I’m using a completely blank theme by WordPress called <a href="https://github.com/WordPress/theme-experiments/tree/master/emptytheme" rel="noopener">Emptytheme</a>. You’re welcome to roll along with your own theme, but I wanted to call out that I’ve renamed Emptytheme to “EMPTY-BLANK” and modified it, so there are no predefined fonts and styles at all.</p>
  3581.  
  3582.  
  3583.  
  3584. <figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="947" height="372" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/appearance-fonts.png?resize=947%2C372&#038;ssl=1" alt="Themes screen showing Empty Theme as the active selection with no screenshot preview." class="wp-image-377125" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/appearance-fonts.png?w=947&amp;ssl=1 947w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/appearance-fonts.png?resize=300%2C118&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/appearance-fonts.png?resize=768%2C302&amp;ssl=1 768w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /></figure>
  3585.  
  3586.  
  3587.  
  3588. <p>I thought I’d share a screenshot of my theme’s file structure and <code>theme.json</code> file to show that there are literally no styles or configurations going on.</p>
  3589.  
  3590.  
  3591.  
  3592. <figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1117" height="577" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/fonts-4-1.jpg?resize=1117%2C577&#038;ssl=1" alt="VS Code file explorer on the left and an open theme.json file on the right." class="wp-image-377127" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/fonts-4-1.jpg?w=1117&amp;ssl=1 1117w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/fonts-4-1.jpg?resize=300%2C155&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/fonts-4-1.jpg?resize=1024%2C529&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/fonts-4-1.jpg?resize=768%2C397&amp;ssl=1 768w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /><figcaption class="wp-element-caption">File structure of Emptytheme (left) and <code>theme.json</code> file (right)</figcaption></figure>
  3593.  
  3594.  
  3595.  
  3596. <p>Let&#8217;s click the &#8220;Add Google Fonts&#8221; button. It takes us to a new page with options to choose any available font from the current <a href="https://developers.google.com/fonts/docs/developer_api" rel="noopener">Google</a> <a href="https://developers.google.com/fonts/docs/developer_api" rel="noopener">F</a><a href="https://developers.google.com/fonts/docs/developer_api" rel="noopener">onts API</a>.</p>
  3597.  
  3598.  
  3599.  
  3600. <figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1000" height="563" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/fonts-2.jpg?resize=1000%2C563&#038;ssl=1" alt="Add Google Fonts to your theme screen with the select font menu open showing a list of available fonts." class="wp-image-377129" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/fonts-2.jpg?w=1000&amp;ssl=1 1000w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/fonts-2.jpg?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/fonts-2.jpg?resize=768%2C432&amp;ssl=1 768w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /></figure>
  3601.  
  3602.  
  3603.  
  3604. <p>For this demo, I selected <strong>Inter</strong> from the menu of options and selected the 300, Regular, and 900 weights from the preview screen:</p>
  3605.  
  3606.  
  3607.  
  3608. <figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1000" height="563" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/fonts-6.jpg?resize=1000%2C563&#038;ssl=1" alt="Add Google Fonts to your theme screen with Inter selected and type samples below it of the various weight variations." class="wp-image-377131" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/fonts-6.jpg?w=1000&amp;ssl=1 1000w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/fonts-6.jpg?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/fonts-6.jpg?resize=768%2C432&amp;ssl=1 768w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /></figure>
  3609.  
  3610.  
  3611.  
  3612. <p>Once I’ve saved my selections, the Inter font styles I selected are automatically downloaded and stored in the theme&#8217;s <code>assets/fonts</code> folder:</p>
  3613.  
  3614.  
  3615.  
  3616. <figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1206" height="591" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/fonts-5-1.jpg?resize=1206%2C591&#038;ssl=1" alt="VS Code file explorer on the left showing Inter font files; theme.json on the right showing Inter references." class="wp-image-377132" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/fonts-5-1.jpg?w=1206&amp;ssl=1 1206w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/fonts-5-1.jpg?resize=300%2C147&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/fonts-5-1.jpg?resize=1024%2C502&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/fonts-5-1.jpg?resize=768%2C376&amp;ssl=1 768w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /></figure>
  3617.  
  3618.  
  3619.  
  3620. <p>Notice, too, how those selections have been automatically written to the <code>theme.json</code> file in that screenshot. The Create Block Theme plugin even adds the path to the font files.</p>
  3621.  
  3622.  
  3623.  
  3624. <details >
  3625.  <summary>
  3626.          View the entire <code>theme.json</code> code      </summary>
  3627.  
  3628.  
  3629. <pre rel="JSON" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">{
  3630.  "version": 2,
  3631.  "settings": {
  3632.    "appearanceTools": true,
  3633.    "layout": {
  3634.      "contentSize": "840px",
  3635.      "wideSize": "1100px"
  3636.    },
  3637.    "typography": {
  3638.      "fontFamilies": [
  3639.        {
  3640.          "fontFamily": "Inter",
  3641.          "slug": "inter",
  3642.          "fontFace": [
  3643.            {
  3644.              "fontFamily": "Inter",
  3645.              "fontStyle": "normal",
  3646.              "fontWeight": "300",
  3647.              "src": [
  3648.                "file:./assets/fonts/inter_300.ttf"
  3649.              ]
  3650.            },
  3651.            {
  3652.              "fontFamily": "Inter",
  3653.              "fontStyle": "normal",
  3654.              "fontWeight": "900",
  3655.              "src": [
  3656.                "file:./assets/fonts/inter_900.ttf"
  3657.              ]
  3658.            },
  3659.            {
  3660.              "fontFamily": "Inter",
  3661.              "fontStyle": "normal",
  3662.              "fontWeight": "400",
  3663.              "src": [
  3664.                "file:./assets/fonts/inter_regular.ttf"
  3665.              ]
  3666.            }
  3667.          ]
  3668.        }
  3669.      ]
  3670.    }
  3671.  }
  3672. }</code></pre>
  3673.  
  3674.  
  3675.  
  3676. <p></p>
  3677.  
  3678.  
  3679. </details>
  3680.  
  3681.  
  3682. <p>If we go to the Create Block Theme’s main screen and click the <strong>Manage theme fonts</strong> button again, we will see Inter’s 300, 400 (Regular), and 900 weight variants displayed in the preview panel.</p>
  3683.  
  3684.  
  3685.  
  3686. <figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="886" height="588" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/fonts-3.png?resize=886%2C588&#038;ssl=1" alt="Manage Theme Fonts screen with a button to Add Google Font highlighted in red." class="wp-image-377134" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/fonts-3.png?w=886&amp;ssl=1 886w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/fonts-3.png?resize=300%2C199&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/fonts-3.png?resize=768%2C510&amp;ssl=1 768w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /></figure>
  3687.  
  3688.  
  3689.  
  3690. <p>A <a href="https://github.com/WordPress/create-block-theme/pull/202" rel="noopener">demo text preview box</a> at the top even allows you to preview the selected fonts within the sentence, header, and paragraph with the font size selection slider. You can check out this new feature in action in <a href="https://user-images.githubusercontent.com/1310626/216234715-68ce33f0-3e70-4a90-8277-a904b4126aae.mp4" rel="noopener">this GitHub video</a>.</p>
  3691.  
  3692.  
  3693.  
  3694. <p>The selected font(s) are also available in the Site Editor <strong>Global Styles</strong> (<strong>Appearance</strong> → <strong>Editor</strong>), specifically in the Design panel.</p>
  3695.  
  3696.  
  3697.  
  3698. <figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/design-preview-panel-1.jpg?resize=825%2C321&#038;ssl=1" alt="Wordpress Site Editor screen with navigation panel open and highlighting the Edit button." class="wp-image-377135" width="825" height="321" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/design-preview-panel-1.jpg?w=1230&amp;ssl=1 1230w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/design-preview-panel-1.jpg?resize=300%2C117&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/design-preview-panel-1.jpg?resize=1024%2C399&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/design-preview-panel-1.jpg?resize=768%2C299&amp;ssl=1 768w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /></figure>
  3699.  
  3700.  
  3701.  
  3702. <p>From here, navigate to <strong>Templates</strong> → <strong>Index</strong> and click the blue <strong>Edit</strong> button to edit the <code>index.html</code> template. We want to open the <strong>Global Styles</strong> settings, which are represented as a contrast icon located at the top-right of the screen. When we click the <strong>Text</strong> settings and open the <strong>Font</strong> menu in the <strong>Typography</strong> section… we see Inter!</p>
  3703.  
  3704.  
  3705.  
  3706. <figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1280" height="720" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/Slide4.jpg?resize=1280%2C720&#038;ssl=1" alt="Open template file in the Site Editor with an arrow pointing out the Global Styles settings button." class="wp-image-377138" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/Slide4.jpg?w=1280&amp;ssl=1 1280w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/Slide4.jpg?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/Slide4.jpg?resize=1024%2C576&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/Slide4.jpg?resize=768%2C432&amp;ssl=1 768w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /></figure>
  3707.  
  3708.  
  3709. <h3 class="wp-block-heading" id="same-thing-but-with-local-fonts">Same thing, but with local fonts</h3>
  3710.  
  3711.  
  3712. <p>We may as well look at adding local fonts to a theme since the Create Block Theme plugin provides that option. The benefit is that you can use any font file you want from whatever font service you prefer.</p>
  3713.  
  3714.  
  3715.  
  3716. <p>Without the plugin, we’d have to grab our font files, drop them somewhere in the theme folder, then resort to the traditional PHP route of enqueuing them in the <code>functions.php</code> file. But we can let WordPress carry that burden for us by uploading the font file on the <strong>Add local fonts</strong> screen using the Create Block Theme interface. Once a file is selected to upload, font face definitions boxes are filled automatically.</p>
  3717.  
  3718.  
  3719.  
  3720. <figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1000" height="501" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/fonts-1.jpg?resize=1000%2C501&#038;ssl=1" alt="Add local fonts to your theme screen with options to upload a font file and set its name, style, and weight." class="wp-image-377141" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/fonts-1.jpg?w=1000&amp;ssl=1 1000w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/fonts-1.jpg?resize=300%2C150&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/fonts-1.jpg?resize=768%2C385&amp;ssl=1 768w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /></figure>
  3721.  
  3722.  
  3723.  
  3724. <p>Even though we can use any <code>.ttf</code>, <code>.woff</code>, or <code>.woff2</code> file, I simply downloaded <a href="https://fonts.google.com/specimen/Open+Sans?query=open+sans" rel="noopener">Open Sans font files from Google Fonts</a> for this exercise. I snatched two weight variations, regular and 800.</p>
  3725.  
  3726.  
  3727.  
  3728. <p>The same auto-magical file management and <code>theme.json</code> update we saw with the Google Fonts option happens once again when we upload the font files (which are done one at a time). Check out where the fonts landed in my theme folder and how they are added to <code>theme.json</code>:</p>
  3729.  
  3730.  
  3731.  
  3732. <figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="843" height="700" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/json-add-local-fonts.png?resize=843%2C700&#038;ssl=1" alt="VS Code showing the font files and the theme.json file references to the font." class="wp-image-377142" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/json-add-local-fonts.png?w=843&amp;ssl=1 843w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/json-add-local-fonts.png?resize=300%2C249&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/json-add-local-fonts.png?resize=768%2C638&amp;ssl=1 768w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /></figure>
  3733.  
  3734.  
  3735. <h3 class="wp-block-heading" id="removing-fonts">Removing fonts</h3>
  3736.  
  3737.  
  3738. <p>The plugin also allows us to remove font files from a block theme from the WordPress admin. Let&#8217;s delete one of the Open Sans variants we installed in the last section to see how that works.</p>
  3739.  
  3740.  
  3741.  
  3742. <figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1000" height="563" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/remove-ui.jpg?resize=1000%2C563&#038;ssl=1" alt="The interface for removing a font from the theme." class="wp-image-377145" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/remove-ui.jpg?w=1000&amp;ssl=1 1000w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/remove-ui.jpg?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/remove-ui.jpg?resize=768%2C432&amp;ssl=1 768w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /></figure>
  3743.  
  3744.  
  3745.  
  3746. <p>Clicking the <strong>Remove</strong> links triggers a warning for you to confirm the deletion. We’ll click <strong>OK</strong> to continue.</p>
  3747.  
  3748.  
  3749.  
  3750. <figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1000" height="233" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/remove-conform.png?resize=1000%2C233&#038;ssl=1" alt="Modal confirming the font deletion." class="wp-image-377146" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/remove-conform.png?w=1000&amp;ssl=1 1000w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/remove-conform.png?resize=300%2C70&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/remove-conform.png?resize=768%2C179&amp;ssl=1 768w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /></figure>
  3751.  
  3752.  
  3753.  
  3754. <p>Let’s open our theme folder and check the <code>theme.json</code> file. Sure enough, the Open Sans 800 file we deleted on the plugin screen removed the font file from the theme folder, and the reference to it is long gone in <code>theme.json</code>.</p>
  3755.  
  3756.  
  3757.  
  3758. <figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1280" height="720" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/remove-json.jpg?resize=1280%2C720&#038;ssl=1" alt="Updated theme.json file showing the font references have been removed." class="wp-image-377147" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/remove-json.jpg?w=1280&amp;ssl=1 1280w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/remove-json.jpg?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/remove-json.jpg?resize=1024%2C576&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/02/remove-json.jpg?resize=768%2C432&amp;ssl=1 768w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /></figure>
  3759.  
  3760.  
  3761. <h3 class="wp-block-heading" id="theres-ongoing-work-happening">There’s ongoing work happening</h3>
  3762.  
  3763.  
  3764. <p><a href="https://github.com/WordPress/gutenberg/pull/46332#issue-1479275259" rel="noopener">There’s talk going on adding this “Font Manager” feature to WordPress Core</a> rather than needing a separate plugin.</p>
  3765.  
  3766.  
  3767.  
  3768. <p>An initial iteration of the feature <a href="https://github.com/WordPress/gutenberg/pull/46332#issuecomment-1366864436" rel="noopener">is available in the repo</a>, and it uses the exact same approach we used in this article. It should be <a href="https://github.com/WordPress/gutenberg/pull/46332#issuecomment-1340970863" rel="noopener">GDPR-compliant</a>, too. The feature is <a href="https://make.wordpress.org/core/2023/02/04/phase-2-finale/" rel="noopener">scheduled to land with WordPress 6.3</a> release later this year.</p>
  3769.  
  3770.  
  3771. <h3 class="wp-block-heading" id="wrapping-up">Wrapping up</h3>
  3772.  
  3773.  
  3774. <p>The Create Block Theme plugin significantly enhances the user experience when it comes to handling fonts in WordPress block themes. The plugin allows us to add or delete any fonts while respecting GDPR requirements.</p>
  3775.  
  3776.  
  3777.  
  3778. <p>We saw how selecting a Google Font or uploading a local font file automatically places the font in the theme folder and registers it in the <code>theme.json</code> file. We also saw how the font is an available option in the Global Styles settings in the Site Editor. And if we need to remove a font? The plugin totally takes care of that as well — without touching theme files or code.</p>
  3779.  
  3780.  
  3781.  
  3782. <p>Thanks for reading! If you have any comments or suggestions, share them in the comments. I’d love to know what you think of this possible direction for font management in WordPress.</p>
  3783.  
  3784.  
  3785. <h3 class="wp-block-heading" id="additional-resources">Additional resources</h3>
  3786.  
  3787.  
  3788. <p>I relied on a lot of research to write this article and thought I’d share the articles and resources I used to provide you with additional context.</p>
  3789.  
  3790.  
  3791. <h4 class="wp-block-heading" id="wordpress-font-management">WordPress font management</h4>
  3792.  
  3793.  
  3794. <ul>
  3795. <li><a href="https://themeshaper.com/2022/11/28/how-to-add-typographic-fonts-to-wordpress-block-themes/" rel="noopener">How to add typographic fonts to WordPress block themes</a> (Theme Shaper)</li>
  3796.  
  3797.  
  3798.  
  3799. <li><a href="https://themeshaper.com/2014/08/13/how-to-add-google-fonts-to-wordpress-themes/" rel="noopener">How to add Google fonts to WordPress themes</a> (Theme Shaper)</li>
  3800.  
  3801.  
  3802.  
  3803. <li><a href="https://www.cloudways.com/blog/google-fonts-wordpress/?utm_medium=content_acq&amp;utm_source=css-tricks&amp;utm_campaign=global_cloudways_post_en&amp;utm_content=awareness_wordpress-fonts" rel="noopener">How to Use Google Fonts With WordPress?</a> (Cloudways)</li>
  3804.  
  3805.  
  3806.  
  3807. <li><a href="https://learn.wordpress.org/tutorial/manage-your-block-theme-fonts-with-create-block-theme/" rel="noopener">Manage your block theme fonts with Create Block Theme</a> (Learn WordPress)</li>
  3808.  
  3809.  
  3810.  
  3811. <li><a href="https://wordpress.tv/2022/08/17/using-create-block-theme/" rel="noopener">Using Create Block Theme</a> (WordPress.tv)</li>
  3812. </ul>
  3813.  
  3814.  
  3815. <h4 class="wp-block-heading" id="github-issues">GitHub issues</h4>
  3816.  
  3817.  
  3818. <ul>
  3819. <li><a href="https://github.com/WordPress/gutenberg/issues/45271" rel="noopener">Global Styles/Typography: Managing font sets</a> (#45271)</li>
  3820.  
  3821.  
  3822.  
  3823. <li><a href="https://github.com/WordPress/gutenberg/pull/46332" rel="noopener">Font manager</a> (#46332)</li>
  3824. </ul>
  3825.  
  3826.  
  3827. <h4 class="wp-block-heading" id="european-gdpr-requirements">European GDPR requirements</h4>
  3828.  
  3829.  
  3830. <ul>
  3831. <li><a href="https://wptavern.com/german-court-fines-website-owner-for-violating-the-gdpr-by-using-google-hosted-fonts" rel="noopener">German Court Fines Website Owner for Violating the GDPR by Using Google-Hosted Fonts</a> (WPTavern)</li>
  3832.  
  3833.  
  3834.  
  3835. <li><a href="https://make.wordpress.org/themes/2022/06/18/complying-with-gdpr-when-using-google-fonts/" rel="noopener">Complying with GDPR when using Google Fonts</a> (Make WordPress Themes)</li>
  3836.  
  3837.  
  3838.  
  3839. <li><a href="https://blog.eprivacy.eu/?p=1398" rel="noopener">German court awards user €100 in damages against website operator for using Google Fonts</a> (ePrivacy Blog)</li>
  3840.  
  3841.  
  3842.  
  3843. <li><a href="https://css-tricks.com/bunny-fonts/">Bunny Fonts</a> (CSS-Tricks)</li>
  3844. </ul>
  3845. <hr />
  3846. <p><small><a rel="nofollow" href="https://css-tricks.com/managing-fonts-in-wordpress-block-themes/">Managing Fonts in WordPress Block Themes</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
  3847. ]]></content:encoded>
  3848. <wfw:commentRss>https://css-tricks.com/managing-fonts-in-wordpress-block-themes/feed/</wfw:commentRss>
  3849. <slash:comments>2</slash:comments>
  3850. <enclosure url="https://user-images.githubusercontent.com/1310626/216234715-68ce33f0-3e70-4a90-8277-a904b4126aae.mp4" length="10073975" type="video/mp4" />
  3851.  
  3852. <post-id xmlns="com-wordpress:feed-additions:1">377123</post-id> </item>
  3853. <item>
  3854. <title>Everything You Need to Know About the Gap After the List Marker</title>
  3855. <link>https://css-tricks.com/everything-you-need-to-know-about-the-gap-after-the-list-marker/</link>
  3856. <comments>https://css-tricks.com/everything-you-need-to-know-about-the-gap-after-the-list-marker/#comments</comments>
  3857. <dc:creator><![CDATA[Šime Vidas]]></dc:creator>
  3858. <pubDate>Thu, 02 Mar 2023 18:20:03 +0000</pubDate>
  3859. <category><![CDATA[Article]]></category>
  3860. <category><![CDATA[list-style]]></category>
  3861. <category><![CDATA[lists]]></category>
  3862. <category><![CDATA[marker]]></category>
  3863. <guid isPermaLink="false">https://css-tricks.com/?p=376748</guid>
  3864.  
  3865. <description><![CDATA[<p>I was reading <a href="https://web.dev/creative-list-styling/" rel="noopener">“Creative List Styling”</a> on Google’s web.dev blog and noticed something odd in one of the code examples in the <code>::marker</code> section of the article. The built-in list markers are bullets, ordinal numbers, and letters. The <code>::marker</code> pseudo-element &#8230;</p>
  3866. <hr />
  3867. <p><small><a rel="nofollow" href="https://css-tricks.com/everything-you-need-to-know-about-the-gap-after-the-list-marker/">Everything You Need to Know About the Gap After the List Marker</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
  3868. ]]></description>
  3869. <content:encoded><![CDATA[
  3870. <p>I was reading <a href="https://web.dev/creative-list-styling/" rel="noopener">“Creative List Styling”</a> on Google’s web.dev blog and noticed something odd in one of the code examples in the <code>::marker</code> section of the article. The built-in list markers are bullets, ordinal numbers, and letters. The <code>::marker</code> pseudo-element allows us to style these markers or replace them with a custom character or image.</p>
  3871.  
  3872.  
  3873.  
  3874. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">::marker {
  3875.  content: url('/marker.svg') ' ';
  3876. }</code></pre>
  3877.  
  3878.  
  3879.  
  3880. <p>The example that caught my attention uses an SVG icon as a custom marker for the list items. But there’s also a single space character (<code>" "</code>) in the CSS value next to the <code>url()</code> function. The purpose of this space seems to be to insert a gap after the custom marker.</p>
  3881.  
  3882.  
  3883.  
  3884. <figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1373" height="843" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/svg-marker-and-space.png?resize=1373%2C843&#038;ssl=1" alt="" class="wp-image-376750" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/svg-marker-and-space.png?w=1373&amp;ssl=1 1373w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/svg-marker-and-space.png?resize=300%2C184&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/svg-marker-and-space.png?resize=1024%2C629&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/svg-marker-and-space.png?resize=768%2C472&amp;ssl=1 768w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /></figure>
  3885.  
  3886.  
  3887.  
  3888. <p>When I saw this code, I immediately wondered if there was a better way to create the gap. Appending a space to <code>content</code> feels more like a workaround than the optimal solution. CSS provides <code>margin</code> and <code>padding</code> and other standard ways to space out elements on the page. Could none of these properties be used in this situation?</p>
  3889.  
  3890.  
  3891.  
  3892. <span id="more-376748"></span>
  3893.  
  3894.  
  3895.  
  3896. <p>First, I tried to substitute the space character with a proper margin:</p>
  3897.  
  3898.  
  3899.  
  3900. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">::marker {
  3901.  content: url('/marker.svg');
  3902.  margin-right: 1ch;
  3903. }</code></pre>
  3904.  
  3905.  
  3906.  
  3907. <p>This didn’t work. As it turns out, <code>::marker</code> only supports a <a href="https://drafts.csswg.org/css-lists-3/#marker-properties" rel="noopener">small set of mostly text-related CSS properties</a>. For example, you can change the <code>font-size</code> and <code>color</code> of the marker, and define a custom marker by setting <code>content</code> to a string or URL, as shown above. But the <code>margin</code> and <code>padding</code> properties are <a href="https://github.com/w3c/csswg-drafts/issues/4571" rel="noopener">not supported</a>, so setting them has no effect. <strong>What a disappointment.</strong></p>
  3908.  
  3909.  
  3910.  
  3911. <p>Could it really be that a space character is the only way to insert a gap after a custom marker? I needed to find out. As I researched this topic, I made a few interesting discoveries that I’d like to share in this article.</p>
  3912.  
  3913.  
  3914. <h3 class="wp-block-heading" id="adding-padding-and-margins">Adding padding and margins</h3>
  3915.  
  3916.  
  3917. <p>First, let’s confirm what <code>margin</code> and <code>padding</code> do on the <code>&lt;ul&gt;</code> and <code>&lt;li&gt;</code> elements. I’ve created a test page for this purpose. Drag the relevant sliders and observe the effect on the spacing on each side of the list marker. Tip: Use the Reset button liberally to reset all controls to their initial values.</p>
  3918.  
  3919.  
  3920.  
  3921. <div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_rNrdXWB" src="//codepen.io/anon/embed/rNrdXWB?height=615&amp;theme-id=1&amp;slug-hash=rNrdXWB&amp;default-tab=result" height="615" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed rNrdXWB" title="CodePen Embed rNrdXWB" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>
  3922.  
  3923.  
  3924.  
  3925. <p class="is-style-explanation"><strong>Note:</strong> Browsers apply a default <code>padding-inline-left</code> of <code>40px</code> to <code>&lt;ol&gt;</code> and <code>&lt;ul&gt;</code> elements. The logical <code>padding-inline-left</code> property is equivalent to the physical <code>padding-left</code> property in writing systems with a left-to-right inline direction. In this article, I’m going to use physical properties for the sake of simplicity.</p>
  3926.  
  3927.  
  3928.  
  3929. <p>As you can see, <code>padding-left</code> on <code>&lt;li&gt;</code> increases the gap after the list marker. The other three properties control the spacing to the left of the marker, in other words, the indentation of the list item.</p>
  3930.  
  3931.  
  3932.  
  3933. <p>Notice that even when the list item’s <code>padding-left</code> is <code>0px</code>, there is still a minimum gap after the marker. This gap cannot be decreased with <code>margin</code> or <code>padding</code>. The exact length of the minimum gap depends on the browser.</p>
  3934.  
  3935.  
  3936.  
  3937. <figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1417" height="890" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/margin-padding-position-outside.png?resize=1417%2C890&#038;ssl=1" alt="First three properties: UL margin-left, UL padding-left, LI margin-left. Fourth property: LI padding-left." class="wp-image-376751" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/margin-padding-position-outside.png?w=1417&amp;ssl=1 1417w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/margin-padding-position-outside.png?resize=300%2C188&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/margin-padding-position-outside.png?resize=1024%2C643&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/margin-padding-position-outside.png?resize=768%2C482&amp;ssl=1 768w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /><figcaption class="wp-element-caption">The first three properties push the entire list item (including the marker) to the right. The fourth property pushes only the list item’s content to the right.</figcaption></figure>
  3938.  
  3939.  
  3940.  
  3941. <p>To sum up, the list item’s content is positioned at a browser-specific minimum distance from the marker, and this gap can be further increased by adding a <code>padding-left</code> to <code>&lt;li&gt;</code>.</p>
  3942.  
  3943.  
  3944.  
  3945. <p>Next, let’s see what happens when we position the marker <em>inside</em> the list item.</p>
  3946.  
  3947.  
  3948. <h3 class="wp-block-heading" id="moving-the-marker-inside-the-list-item">Moving the marker inside the list item</h3>
  3949.  
  3950.  
  3951. <p>The <code>list-style-position</code> property accepts two keywords: <code>outside</code>, which is the default, and <code>inside</code>, which moves the marker inside the list item. The latter is useful for creating designs with full-width list items.</p>
  3952.  
  3953.  
  3954.  
  3955. <figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1608" height="918" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/list-marker-position-inside.png?resize=1608%2C918&#038;ssl=1" alt="A grocery list. Each item has a thin bottom border that extends from the left to the right edge of the list." class="wp-image-376752" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/list-marker-position-inside.png?w=1608&amp;ssl=1 1608w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/list-marker-position-inside.png?resize=300%2C171&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/list-marker-position-inside.png?resize=1024%2C585&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/list-marker-position-inside.png?resize=768%2C438&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/list-marker-position-inside.png?resize=1536%2C877&amp;ssl=1 1536w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /><figcaption class="wp-element-caption">The list marker is positioned inside the list item, so that the list item’s bottom border can extend to the left edge of the list box</figcaption></figure>
  3956.  
  3957.  
  3958.  
  3959. <p>If the marker is now <em>inside</em> the list item, does this mean that <code>padding-left</code> on <code>&lt;li&gt;</code> no longer increases the gap after the marker? Let’s find out. On my test page, turn on <code>list-style-position: inside</code> via the checkbox. How are the four <code>padding</code> and <code>margin</code> properties affected by this change?</p>
  3960.  
  3961.  
  3962.  
  3963. <p>As you can see, <code>padding-left</code> on <code>&lt;li&gt;</code> now increases the spacing to the <em>left</em> of the marker. This means that we’ve lost the ability to increase the gap after the marker. In this situation, it would be useful to be able to add <code>margin-right</code> to the <code>::marker</code> itself, but that doesn’t work, as we’ve established above.</p>
  3964.  
  3965.  
  3966.  
  3967. <figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1417" height="889" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/margin-padding-position-inside.png?resize=1417%2C889&#038;ssl=1" alt="The four properties: UL margin-left, UL padding-left, LI margin-left, LI padding-left." class="wp-image-376753" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/margin-padding-position-inside.png?w=1417&amp;ssl=1 1417w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/margin-padding-position-inside.png?resize=300%2C188&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/margin-padding-position-inside.png?resize=1024%2C642&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/margin-padding-position-inside.png?resize=768%2C482&amp;ssl=1 768w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /><figcaption class="wp-element-caption">All four properties push the entire list item to the right. The minimum gap cannot be increased by standard means.</figcaption></figure>
  3968.  
  3969.  
  3970.  
  3971. <p>Additionally, there’s a <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=725972" rel="noopener">bug in Chromium</a> that causes the gap after the marker to <em>triple</em> after switching to <code>inside</code> positioning. By default, the length of the gap is about one-third of the text size. So at a default <code>font-size</code> of <code>16px</code>, the gap is about <code>5.5px</code>. After switching to <code>inside</code>, the gap grows to the full <code>16px</code> in Chrome. This bug affects the <code>disc</code>, <code>circle</code>, and <code>square</code> markers, but <a href="https://twitter.com/g16n/status/1616807361061978118" rel="noopener">not ordinal number markers</a>.</p>
  3972.  
  3973.  
  3974.  
  3975. <p>The following image shows the default rendering of outside and inside-positioned list markers across three major browsers on macOS. For your convenience, I’ve horizontally aligned all list items on their markers to make it easier to compare the differences in gap sizes.</p>
  3976.  
  3977.  
  3978.  
  3979. <figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="555" height="385" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/outside-inside-gap-difference.png?resize=555%2C385&#038;ssl=1" alt="Six list items with varying gaps between the marker and text." class="wp-image-376754" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/outside-inside-gap-difference.png?w=555&amp;ssl=1 555w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/outside-inside-gap-difference.png?resize=300%2C208&amp;ssl=1 300w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /><figcaption class="wp-element-caption">Only Firefox maintains the same gap size between the two marker positioning modes. This can be considered a browser interoperability (<a href="https://web.dev/interop-2022/#what-is-interop-2022" rel="noopener">interop</a>) issue.</figcaption></figure>
  3980.  
  3981.  
  3982.  
  3983. <p>To sum up, switching to <code>list-style-position: inside</code> introduces two problems. We can no longer increase the gap via <code>padding-left</code> on <code>&lt;li&gt;</code>, and the gap size is inconsistent between browsers.</p>
  3984.  
  3985.  
  3986.  
  3987. <p>Finally, let’s see what happens when we replace the default list marker with a custom marker.</p>
  3988.  
  3989.  
  3990. <h3 class="wp-block-heading" id="switching-to-a-custom-marker">Switching to a custom marker</h3>
  3991.  
  3992.  
  3993. <p>There are two ways to define a <a href="https://css-tricks.com/css-counters-custom-list-number-styling/">custom marker</a>:</p>
  3994.  
  3995.  
  3996.  
  3997. <ul>
  3998. <li><code>list-style-type</code> and <code>list-style-image</code> properties</li>
  3999.  
  4000.  
  4001.  
  4002. <li><code>content</code> property on the <code>::marker</code> pseudo-element</li>
  4003. </ul>
  4004.  
  4005.  
  4006.  
  4007. <p>The <code>content</code> property is more powerful. For example, it allows us to use the <code>counter()</code> function to access the list item’s ordinal number (the <a href="https://drafts.csswg.org/css-lists-3/#list-item-counter" rel="noopener">implicit <code>list-item</code> counter</a>) and decorate it with custom strings.</p>
  4008.  
  4009.  
  4010.  
  4011. <div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_RwBQjvZ" src="//codepen.io/anon/embed/RwBQjvZ?height=350&amp;theme-id=1&amp;slug-hash=RwBQjvZ&amp;default-tab=css,result" height="350" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed RwBQjvZ" title="CodePen Embed RwBQjvZ" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>
  4012.  
  4013.  
  4014.  
  4015. <p>Unfortunately, Safari doesn’t support the <code>content</code> property on <code>::marker</code> yet (<a href="https://bugs.webkit.org/show_bug.cgi?id=204163" rel="noopener">WebKit bug</a>). For this reason, I’m going to use the <code>list-style-type</code> property to define the custom marker. You can still use the <code>::marker</code> selector to style the custom marker declared via <code>list-style-type</code>. That aspect of <code>::marker</code> is supported in Safari.</p>
  4016.  
  4017.  
  4018.  
  4019. <div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_ZEjXOwL" src="//codepen.io/anon/embed/ZEjXOwL?height=350&amp;theme-id=1&amp;slug-hash=ZEjXOwL&amp;default-tab=css,result" height="350" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed ZEjXOwL" title="CodePen Embed ZEjXOwL" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>
  4020.  
  4021.  
  4022.  
  4023. <p>Any Unicode character can potentially serve as a custom list marker, but only a small set of characters actually have “Bullet” in their official name, so I thought I’d compile them here for reference.</p>
  4024.  
  4025.  
  4026.  
  4027. <figure class="wp-block-table"><table><thead><tr><th>Character</th><th>Name</th><th>Code point</th><th>CSS keyword</th></tr></thead><tbody><tr><td>•</td><td>Bullet</td><td><code>U+2022</code></td><td><code>disc</code></td></tr><tr><td>‣</td><td>Triangular Bullet</td><td><code>U+2023</code></td><td></td></tr><tr><td>⁃</td><td>Hyphen Bullet</td><td><code>U+2043</code></td><td></td></tr><tr><td>⁌</td><td>Black Leftwards Bullet</td><td><code>U+204C</code></td><td></td></tr><tr><td>⁍</td><td>Black Rightwards Bullet</td><td><code>U+204D</code></td><td></td></tr><tr><td>◘</td><td>Inverse Bullet</td><td><code>U+25D8</code></td><td></td></tr><tr><td>◦</td><td>White Bullet</td><td><code>U+25E6</code></td><td><code>circle</code></td></tr><tr><td>☙</td><td>Reversed Rotated Floral Heart Bullet</td><td><code>U+2619</code></td><td></td></tr><tr><td>❥</td><td>Rotated Heavy Black Heart Bullet</td><td><code>U+2765</code></td><td></td></tr><tr><td>❧</td><td>Rotated Floral Heart Bullet</td><td><code>U+2767</code></td><td></td></tr><tr><td>⦾</td><td>Circled White Bullet</td><td><code>U+29BE</code></td><td></td></tr><tr><td>⦿</td><td>Circled Bullet</td><td><code>U+29BF</code></td><td></td></tr></tbody></table></figure>
  4028.  
  4029.  
  4030.  
  4031. <p class="is-style-explanation"><strong>Note:</strong> The CSS <code>square</code> keyword does not have a corresponding “Bullet” character in Unicode. The character that comes closest is the Black Small Square (&#x25aa;&#xfe0f;) emoji (<code>U+25AA</code>).</p>
  4032.  
  4033.  
  4034.  
  4035. <p>Now let’s see what happens when we replace the default list marker with <code>list-style-type: "•"</code> (<code>U+2022</code> Bullet). This is the same character as the default bullet, so there shouldn’t be any major rendering differences. On my test page, turn on the <code>list-style-type</code> option and observe any changes to the marker.</p>
  4036.  
  4037.  
  4038.  
  4039. <p>As you can see, there are two significant changes:</p>
  4040.  
  4041.  
  4042.  
  4043. <ol>
  4044. <li>There is no longer a minimum gap after the marker.</li>
  4045.  
  4046.  
  4047.  
  4048. <li>The bullet has become smaller, as if it were rendered at a smaller <code>font-size</code>.</li>
  4049. </ol>
  4050.  
  4051.  
  4052.  
  4053. <p>According to <a href="https://drafts.csswg.org/css-counter-styles-3/#simple-symbolic" rel="noopener">CSS Counter Styles Level 3</a>, the default list marker (<code>disc</code>) should be “similar to • <code>U+2022</code> BULLET”. It seems that browsers increase the size of the default bullet to make it more legible. Firefox even uses a special font, <code>-moz-bullet-font</code>, for the marker.</p>
  4054.  
  4055.  
  4056.  
  4057. <figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="612" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/firefox-moz-bullet-font.png?resize=1024%2C612&#038;ssl=1" alt=":marker selected in the inspector. Fonts used: -moz-bullet-font." class="wp-image-376755" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/firefox-moz-bullet-font.png?resize=1024%2C612&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/firefox-moz-bullet-font.png?resize=300%2C179&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/firefox-moz-bullet-font.png?resize=768%2C459&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/firefox-moz-bullet-font.png?w=1258&amp;ssl=1 1258w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /><figcaption class="wp-element-caption">The &#8220;Fonts&#8221; pane in Firefox’s DOM inspector reveals the special font.</figcaption></figure>
  4058.  
  4059.  
  4060.  
  4061. <p>Can the small size problem be fixed with CSS? On my test page, turn on marker styling and observe what happens when you change the <code>font-size</code>, <code>line-height</code>, and <code>font-family</code> of the marker.</p>
  4062.  
  4063.  
  4064.  
  4065. <p>As you can see, increasing the <code>font-size</code> causes the custom marker to become vertically misaligned, and this cannot be corrected by decreasing the <code>line-height</code>. The <code>vertical-align</code> property, which could easily fix this problem, is not supported on <code>::marker</code>.</p>
  4066.  
  4067.  
  4068.  
  4069. <p>But did you notice that changing the <code>font-family</code> can cause the marker to become bigger? Try setting it to <code>Tahoma</code>. This could potentially be a good-enough workaround for the small-size problem, although I haven’t tested which font works best across the major browsers and operating systems.</p>
  4070.  
  4071.  
  4072.  
  4073. <p>You may also have noticed that the Chromium bug doesn’t occur anymore when you position the marker inside the list item. This means that a custom marker can serve as a workaround for this bug. And this leads me to the main problem, and the reason why I started researching this topic. <strong>If you define a custom marker and position it inside the list item, there is no gap after the marker and no way to insert a gap by standard means.</strong></p>
  4074.  
  4075.  
  4076.  
  4077. <ol>
  4078. <li>There is no minimum gap after custom markers.</li>
  4079.  
  4080.  
  4081.  
  4082. <li><code>::marker</code> doesn’t support <code>padding</code> or <code>margin</code>.</li>
  4083.  
  4084.  
  4085.  
  4086. <li><code>padding-left</code> on <code>&lt;li&gt;</code> doesn’t increase the gap, since the marker is positioned <code>inside</code>.</li>
  4087. </ol>
  4088.  
  4089.  
  4090. <h3 class="wp-block-heading" id="summary">Summary</h3>
  4091.  
  4092.  
  4093. <p>Here’s a summary of all the key facts that I’ve mentioned in the article:</p>
  4094.  
  4095.  
  4096.  
  4097. <ol>
  4098. <li>Browsers apply a default <code>padding-inline-start</code> of <code>40px</code> to <code>&lt;ul&gt;</code> and <code>&lt;ol&gt;</code> elements.</li>
  4099.  
  4100.  
  4101.  
  4102. <li>There is a minimum gap after built-in list markers (<code>disc</code>, <code>decimal</code>, etc.). There is no minimum gap after custom markers (string or URL).</li>
  4103.  
  4104.  
  4105.  
  4106. <li>The length of the gap can be increased by adding a <code>padding-left</code> to <code>&lt;ul&gt;</code>, but only if the marker is positioned outside the list item (the default mode).</li>
  4107.  
  4108.  
  4109.  
  4110. <li>Custom string markers have a smaller default size than built-in markers. Changing the <code>font-family</code> on <code>::marker</code> can increase their size.</li>
  4111. </ol>
  4112.  
  4113.  
  4114. <h3 class="wp-block-heading" id="conclusion">Conclusion</h3>
  4115.  
  4116.  
  4117. <p>Looking back at the code example from the beginning of the article, I think I understand now why there’s a space character in the <code>content</code> value. There is just no better way to insert a gap after the SVG marker. It’s a workaround that is needed because no amount of <code>margin</code> and <code>padding</code> can create a gap after a custom marker that is positioned inside the list item. A <code>margin-right</code> on <code>::marker</code> could easily do it, but that is not supported.</p>
  4118.  
  4119.  
  4120.  
  4121. <p>Until <code>::marker</code> adds support for more properties, web developers will often have no choice but to hide the marker and emulate it with a <code>::before</code> pseudo-element. I had to do that myself recently because I couldn’t change the marker’s <code>background-color</code>. Hopefully, we won’t have to wait too long for a more powerful <code>::marker</code> pseudo-element.</p>
  4122.  
  4123.  
  4124.  
  4125. <div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_YzjrGGM" src="//codepen.io/anon/embed/YzjrGGM?height=350&amp;theme-id=1&amp;slug-hash=YzjrGGM&amp;default-tab=css,result" height="350" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed YzjrGGM" title="CodePen Embed YzjrGGM" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>
  4126. <hr />
  4127. <p><small><a rel="nofollow" href="https://css-tricks.com/everything-you-need-to-know-about-the-gap-after-the-list-marker/">Everything You Need to Know About the Gap After the List Marker</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
  4128. ]]></content:encoded>
  4129. <wfw:commentRss>https://css-tricks.com/everything-you-need-to-know-about-the-gap-after-the-list-marker/feed/</wfw:commentRss>
  4130. <slash:comments>2</slash:comments>
  4131. <post-id xmlns="com-wordpress:feed-additions:1">376748</post-id> </item>
  4132. <item>
  4133. <title>An Approach to Lazy Loading Custom Elements</title>
  4134. <link>https://css-tricks.com/an-approach-to-lazy-loading-custom-elements/</link>
  4135. <comments>https://css-tricks.com/an-approach-to-lazy-loading-custom-elements/#comments</comments>
  4136. <dc:creator><![CDATA[Frederik Dohr]]></dc:creator>
  4137. <pubDate>Mon, 13 Feb 2023 15:10:41 +0000</pubDate>
  4138. <category><![CDATA[Article]]></category>
  4139. <category><![CDATA[custom elements]]></category>
  4140. <category><![CDATA[lazy loading]]></category>
  4141. <category><![CDATA[web components]]></category>
  4142. <guid isPermaLink="false">https://css-tricks.com/?p=376991</guid>
  4143.  
  4144. <description><![CDATA[<p>We&#8217;re fans of <a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements" rel="noopener">Custom Elements</a> around here. Their design makes them <a href="https://web.dev/custom-elements-v1/#progressively-enhanced-html" rel="noopener">particularly amenable to lazy loading</a>, which can be a boon for performance.</p>
  4145. <p>Inspired by <a href="https://www.innoq.com/en/staff/steffen-henschel/" rel="noopener">a colleague&#8217;s</a> experiments, I recently set about writing a simple auto-loader: Whenever a custom &#8230;</p>
  4146. <hr />
  4147. <p><small><a rel="nofollow" href="https://css-tricks.com/an-approach-to-lazy-loading-custom-elements/">An Approach to Lazy Loading Custom Elements</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
  4148. ]]></description>
  4149. <content:encoded><![CDATA[
  4150. <p>We&#8217;re fans of <a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements" rel="noopener">Custom Elements</a> around here. Their design makes them <a href="https://web.dev/custom-elements-v1/#progressively-enhanced-html" rel="noopener">particularly amenable to lazy loading</a>, which can be a boon for performance.</p>
  4151.  
  4152.  
  4153.  
  4154. <p>Inspired by <a href="https://www.innoq.com/en/staff/steffen-henschel/" rel="noopener">a colleague&#8217;s</a> experiments, I recently set about writing a simple auto-loader: Whenever a custom element appears in the DOM, we wanna load the corresponding implementation if it&#8217;s not available yet. The browser then takes care of upgrading such elements from there on out.</p>
  4155.  
  4156.  
  4157.  
  4158. <span id="more-376991"></span>
  4159.  
  4160.  
  4161.  
  4162. <p class="is-style-explanation">Chances are you won&#8217;t actually need all this; there&#8217;s usually a simpler approach. Used deliberately, the techniques shown here might still be a useful addition to your toolset.</p>
  4163.  
  4164.  
  4165.  
  4166. <p>For consistency, we want our auto-loader to be a custom element as well — which also means we can easily configure it via HTML. But first, let&#8217;s identify those unresolved custom elements, step by step:</p>
  4167.  
  4168.  
  4169.  
  4170. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">class AutoLoader extends HTMLElement {
  4171.  connectedCallback() {
  4172.    let scope = this.parentNode;
  4173.    this.discover(scope);
  4174.  }
  4175. }
  4176. customElements.define("ce-autoloader", AutoLoader);</code></pre>
  4177.  
  4178.  
  4179.  
  4180. <p>Assuming we&#8217;ve loaded this module up-front (using <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-async" rel="noopener"><code>async</code></a> is ideal), we can drop a <code>&lt;ce-autoloader&gt;</code> element into the <code>&lt;body&gt;</code> of our document. That will immediately start the discovery process for all child elements of <code>&lt;body&gt;</code>, which now constitutes our root element. We could limit discovery to a subtree of our document by adding <code>&lt;ce-autoloader&gt;</code> to the respective container element instead — indeed, we might even have multiple instances for different subtrees.</p>
  4181.  
  4182.  
  4183.  
  4184. <p>Of course, we still have to implement that <code>discover</code> method (as part of the <code>AutoLoader</code> class above):</p>
  4185.  
  4186.  
  4187.  
  4188. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">discover(scope) {
  4189.  let candidates = [scope, ...scope.querySelectorAll("*")];
  4190.  for(let el of candidates) {
  4191.    let tag = el.localName;
  4192.    if(tag.includes("-") &amp;&amp; !customElements.get(tag)) {
  4193.      this.load(tag);
  4194.    }
  4195.  }
  4196. }</code></pre>
  4197.  
  4198.  
  4199.  
  4200. <p>Here we check our root element along with every single descendant (<code>*</code>). If it&#8217;s a custom element — as indicated by hyphenated tags — but not yet upgraded, we&#8217;ll attempt to load the corresponding definition. Querying the DOM that way might be expensive, so we should be a little careful. We can alleviate load on the main thread by deferring this work:</p>
  4201.  
  4202.  
  4203.  
  4204. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">connectedCallback() {
  4205.  let scope = this.parentNode;
  4206.  requestIdleCallback(() => {
  4207.    this.discover(scope);
  4208.  });
  4209. }</code></pre>
  4210.  
  4211.  
  4212.  
  4213. <p><a href="https://developer.mozilla.org/en-US/docs/Web/API/window/requestIdleCallback" rel="noopener"><code>requestIdleCallback</code></a> is not universally supported yet, but we can use <a href="https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame" rel="noopener"><code>requestAnimationFrame</code></a> as a fallback:</p>
  4214.  
  4215.  
  4216.  
  4217. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">let defer = window.requestIdleCallback || requestAnimationFrame;
  4218.  
  4219. class AutoLoader extends HTMLElement {
  4220.  connectedCallback() {
  4221.    let scope = this.parentNode;
  4222.    defer(() => {
  4223.      this.discover(scope);
  4224.    });
  4225.  }
  4226.  // ...
  4227. }</code></pre>
  4228.  
  4229.  
  4230.  
  4231. <p>Now we can move on to implementing the missing <code>load</code> method to dynamically inject a <code>&lt;script&gt;</code> element:</p>
  4232.  
  4233.  
  4234.  
  4235. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">load(tag) {
  4236.  let el = document.createElement("script");
  4237.  let res = new Promise((resolve, reject) => {
  4238.    el.addEventListener("load", ev => {
  4239.      resolve(null);
  4240.    });
  4241.    el.addEventListener("error", ev => {
  4242.      reject(new Error("failed to locate custom-element definition"));
  4243.    });
  4244.  });
  4245.  el.src = this.elementURL(tag);
  4246.  document.head.appendChild(el);
  4247.  return res;
  4248. }
  4249.  
  4250. elementURL(tag) {
  4251.  return `${this.rootDir}/${tag}.js`;
  4252. }</code></pre>
  4253.  
  4254.  
  4255.  
  4256. <p>Note the hard-coded convention in <code>elementURL</code>. The <code>src</code> attribute&#8217;s URL assumes there&#8217;s a directory where all custom element definitions reside (e.g. <code>&lt;my-widget&gt;</code> → <code>/components/my-widget.js</code>). We could come up with more elaborate strategies, but this is good enough for our purposes. Relegating this URL to a separate method allows for project-specific subclassing when needed:</p>
  4257.  
  4258.  
  4259.  
  4260. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">class FancyLoader extends AutoLoader {
  4261.  elementURL(tag) {
  4262.    // fancy logic
  4263.  }
  4264. }</code></pre>
  4265.  
  4266.  
  4267.  
  4268. <p>Either way, note that we&#8217;re relying on <code>this.rootDir</code>. This is where the aforementioned configurability comes in. Let&#8217;s add a corresponding getter:</p>
  4269.  
  4270.  
  4271.  
  4272. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">get rootDir() {
  4273.  let uri = this.getAttribute("root-dir");
  4274.  if(!uri) {
  4275.    throw new Error("cannot auto-load custom elements: missing `root-dir`");
  4276.  }
  4277.  if(uri.endsWith("/")) { // remove trailing slash
  4278.    return uri.substring(0, uri.length - 1);
  4279.  }
  4280.  return uri;
  4281. }</code></pre>
  4282.  
  4283.  
  4284.  
  4285. <p class="is-style-explanation">You might be thinking of <code>observedAttributes</code> now, but that doesn&#8217;t really make things easier. Plus updating <code>root-dir</code> at runtime seems like something we’re never going to need.</p>
  4286.  
  4287.  
  4288.  
  4289. <p>Now we can — and must — configure our elements directory: <code>&lt;ce-autoloader root-dir="/components"&gt;</code>.</p>
  4290.  
  4291.  
  4292.  
  4293. <p>With this, our auto-loader can do its job. Except it only works once, for elements that already exist when the auto-loader is initialized. We&#8217;ll probably want to account for dynamically added elements as well. That&#8217;s where <a href="https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver" rel="noopener"><code>MutationObserver</code></a> comes into play:</p>
  4294.  
  4295.  
  4296.  
  4297. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">connectedCallback() {
  4298.  let scope = this.parentNode;
  4299.  defer(() => {
  4300.    this.discover(scope);
  4301.  });
  4302.  let observer = this._observer = new MutationObserver(mutations => {
  4303.    for(let { addedNodes } of mutations) {
  4304.      for(let node of addedNodes) {
  4305.        defer(() => {
  4306.          this.discover(node);
  4307.        });
  4308.      }
  4309.    }
  4310.  });
  4311.  observer.observe(scope, { subtree: true, childList: true });
  4312. }
  4313.  
  4314. disconnectedCallback() {
  4315.  this._observer.disconnect();
  4316. }</code></pre>
  4317.  
  4318.  
  4319.  
  4320. <p>This way, the browser notifies us whenever a new element appears in the DOM — or rather, our respective subtree — which we then use to restart the discovery process. (You might argue we’re re-inventing custom elements here, and you’d be kind of correct.)</p>
  4321.  
  4322.  
  4323.  
  4324. <p>Our auto-loader is now fully functional. Future enhancements might look into potential race conditions and investigate optimizations. But chances are this is good enough for most scenarios. Let me know in the comments if you have a different approach and we can compare notes!</p>
  4325. <hr />
  4326. <p><small><a rel="nofollow" href="https://css-tricks.com/an-approach-to-lazy-loading-custom-elements/">An Approach to Lazy Loading Custom Elements</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
  4327. ]]></content:encoded>
  4328. <wfw:commentRss>https://css-tricks.com/an-approach-to-lazy-loading-custom-elements/feed/</wfw:commentRss>
  4329. <slash:comments>5</slash:comments>
  4330. <post-id xmlns="com-wordpress:feed-additions:1">376991</post-id> </item>
  4331. <item>
  4332. <title>Different Ways to Get CSS Gradient Shadows</title>
  4333. <link>https://css-tricks.com/different-ways-to-get-css-gradient-shadows/</link>
  4334. <comments>https://css-tricks.com/different-ways-to-get-css-gradient-shadows/#respond</comments>
  4335. <dc:creator><![CDATA[Temani Afif]]></dc:creator>
  4336. <pubDate>Fri, 10 Feb 2023 15:13:42 +0000</pubDate>
  4337. <category><![CDATA[Article]]></category>
  4338. <category><![CDATA[box-shadow]]></category>
  4339. <category><![CDATA[gradients]]></category>
  4340. <category><![CDATA[shadow]]></category>
  4341. <guid isPermaLink="false">https://css-tricks.com/?p=376764</guid>
  4342.  
  4343. <description><![CDATA[<p>It’s a question I hear asked quite often: <em>Is it possible to create shadows from gradients instead of solid colors?</em> There is no specific CSS property that does this (believe me, I’ve looked) and any blog post you find about &#8230;</p>
  4344. <hr />
  4345. <p><small><a rel="nofollow" href="https://css-tricks.com/different-ways-to-get-css-gradient-shadows/">Different Ways to Get CSS Gradient Shadows</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
  4346. ]]></description>
  4347. <content:encoded><![CDATA[
  4348. <p>It’s a question I hear asked quite often: <em>Is it possible to create shadows from gradients instead of solid colors?</em> There is no specific CSS property that does this (believe me, I’ve looked) and any blog post you find about it is basically a lot of CSS tricks to approximate a gradient. We’ll actually cover some of those as we go.</p>
  4349.  
  4350.  
  4351.  
  4352. <p>But first… <em>another</em> article about gradient shadows? Really?</p>
  4353.  
  4354.  
  4355.  
  4356. <p>Yes, this is yet another post on the topic, but it is different. Together, we’re going to push the limits to get a solution that covers something I haven’t seen anywhere else: <strong>transparency</strong>. Most of the tricks work if the element has a non-transparent background but what if we have a transparent background? We will explore this case here!</p>
  4357.  
  4358.  
  4359.  
  4360. <p>Before we start, let me introduce <a href="https://css-generators.com/gradient-shadows" rel="noopener"></a><a href="https://css-generators.com/gradient-shadows/" rel="noopener">my gradient shadows generator</a>. All you have to do is to adjust the configuration, and get the code. But follow along because I’m going to help you understand all the logic behind the generated code.</p>
  4361.  
  4362.  
  4363.  
  4364. <p></p>
  4365.  
  4366.  
  4367.  
  4368. <span id="more-376764"></span>
  4369.  
  4370.  
  4371. <div class="simpletoc ticss-851b6bfe wp-block-simpletoc-toc"><h2 class="simpletoc-title ">Table of Contents</h2><ul class="simpletoc-list"   >
  4372. <li>
  4373. <a  href="#nontransparent-solution">Non-transparent solution</a></li><li>
  4374. <a  href="#transparent-solution">Transparent solution</a></li><li>
  4375. <a  href="#adding-a-border-radius">Adding a border radius</a></li><li>
  4376. <a  href="#wrapping-up">Wrapping up</a></li></ul></div>
  4377.  
  4378. <h3 class="wp-block-heading" id="nontransparent-solution">Non-transparent solution</h3>
  4379.  
  4380.  
  4381. <p>Let’s start with the solution that’ll work for 80% of most cases. The most typical case: you are using an element with a background, and you need to add a gradient shadow to it. No transparency issues to consider there.</p>
  4382.  
  4383.  
  4384.  
  4385. <p>The solution is to rely on a pseudo-element where the gradient is defined. You place it behind the actual element and <a href="https://css-tricks.com/almanac/properties/f/filter/">apply a blur filter to it</a>.</p>
  4386.  
  4387.  
  4388.  
  4389. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.box {
  4390.  position: relative;
  4391. }
  4392. .box::before {
  4393.  content: "";
  4394.  position: absolute;
  4395.  inset: -5px; /* control the spread */
  4396.  transform: translate(10px, 8px); /* control the offsets */
  4397.  z-index: -1; /* place the element behind */
  4398.  background: /* your gradient here */;
  4399.  filter: blur(10px); /* control the blur */
  4400. }</code></pre>
  4401.  
  4402.  
  4403.  
  4404. <p>It looks like a lot of code, and that’s because it is. Here’s how we could have done it with a <a href="https://css-tricks.com/almanac/properties/b/box-shadow/"><code>box-shadow</code></a> instead if we were using a solid color instead of a gradient.</p>
  4405.  
  4406.  
  4407.  
  4408. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">box-shadow: 10px 8px 10px 5px orange;</code></pre>
  4409.  
  4410.  
  4411.  
  4412. <p>That should give you a good idea of what the values in the first snippet are doing. We have X and Y offsets, the blur radius, and the spread distance. Note that we need a negative value for the spread distance that comes from the <a href="https://css-tricks.com/almanac/properties/i/inset/"><code>inset</code></a> property.</p>
  4413.  
  4414.  
  4415.  
  4416. <p>Here’s a demo showing the gradient shadow next to a classic <code>box-shadow</code>:</p>
  4417.  
  4418.  
  4419.  
  4420. <div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_xxJWzzz/27ea51cfa0ddc49a9ae80ea87e6b7042" src="//codepen.io/anon/embed/xxJWzzz/27ea51cfa0ddc49a9ae80ea87e6b7042?height=450&amp;theme-id=1&amp;slug-hash=xxJWzzz/27ea51cfa0ddc49a9ae80ea87e6b7042&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed xxJWzzz/27ea51cfa0ddc49a9ae80ea87e6b7042" title="CodePen Embed xxJWzzz/27ea51cfa0ddc49a9ae80ea87e6b7042" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>
  4421.  
  4422.  
  4423.  
  4424. <p>If you look closely you will notice that both shadows are a little different, especially the blur part. It’s not a surprise because I am pretty sure the <code>filter</code> property’s algorithm works differently than the one for <code>box-shadow</code>. That’s not a big deal since the result is, in the end, quite similar.</p>
  4425.  
  4426.  
  4427.  
  4428. <p>This solution is good, but still has a few drawbacks related to the <code>z-index: -1</code> declaration. Yes, there is <a href="https://css-tricks.com/its-always-the-stacking-context/">“stacking context”</a> happening there!</p>
  4429.  
  4430.  
  4431.  
  4432. <div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_eYjMKqM/2b08f902b70e8fd8d6c63a1c850b6c96" src="//codepen.io/anon/embed/eYjMKqM/2b08f902b70e8fd8d6c63a1c850b6c96?height=450&amp;theme-id=1&amp;slug-hash=eYjMKqM/2b08f902b70e8fd8d6c63a1c850b6c96&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed eYjMKqM/2b08f902b70e8fd8d6c63a1c850b6c96" title="CodePen Embed eYjMKqM/2b08f902b70e8fd8d6c63a1c850b6c96" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>
  4433.  
  4434.  
  4435.  
  4436. <p>I applied a <a href="https://css-tricks.com/almanac/properties/t/transform/"><code>transform</code></a> to the main element, and boom! The shadow is no longer below the element. This is not a bug but the logical result of a stacking context. Don’t worry, I will not start a boring explanation about stacking context (<a href="https://stackoverflow.com/a/54903621/8620333" rel="noopener">I already did that in a Stack Overflow thread</a>), but I’ll still show you how to work around it.</p>
  4437.  
  4438.  
  4439.  
  4440. <p>The first solution that I recommend is to use a 3D <code>transform</code>:</p>
  4441.  
  4442.  
  4443.  
  4444. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line="3,9"><code markup="tt">.box {
  4445.  position: relative;
  4446.  transform-style: preserve-3d;
  4447. }
  4448. .box::before {
  4449.  content: "";
  4450.  position: absolute;
  4451.  inset: -5px;
  4452.  transform: translate3d(10px, 8px, -1px); /* (X, Y, Z) */
  4453.  background: /* .. */;
  4454.  filter: blur(10px);
  4455. }</code></pre>
  4456.  
  4457.  
  4458.  
  4459. <p>Instead of using <code>z-index: -1</code>, we will use a negative translation along the Z-axis. We will put everything inside <code>translate3d()</code>. Don’t forget to use <code>transform-style: preserve-3d</code> on the main element; otherwise, the 3D <code>transform</code> won’t take effect.</p>
  4460.  
  4461.  
  4462.  
  4463. <div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_xxJWJbm/fea263d6c68838cc0d1ca7dc2c09a905" src="//codepen.io/anon/embed/xxJWJbm/fea263d6c68838cc0d1ca7dc2c09a905?height=450&amp;theme-id=1&amp;slug-hash=xxJWJbm/fea263d6c68838cc0d1ca7dc2c09a905&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed xxJWJbm/fea263d6c68838cc0d1ca7dc2c09a905" title="CodePen Embed xxJWJbm/fea263d6c68838cc0d1ca7dc2c09a905" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>
  4464.  
  4465.  
  4466.  
  4467. <p>As far as I know, there is no side effect to this solution… but maybe you see one. If that’s the case, share it in the comment section, and let’s try to find a fix for it!</p>
  4468.  
  4469.  
  4470.  
  4471. <p>If for some reason you are unable to use a 3D <code>transform</code>, the other solution is to rely on two pseudo-elements — <code>::before</code> and <code>::after</code>. One creates the gradient shadow, and the other reproduces the main background (and other styles you might need). That way, we can easily control the stacking order of both pseudo-elements.</p>
  4472.  
  4473.  
  4474.  
  4475. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.box {
  4476.  position: relative;
  4477.  z-index: 0; /* We force a stacking context */
  4478. }
  4479. /* Creates the shadow */
  4480. .box::before {
  4481.  content: "";
  4482.  position: absolute;
  4483.  z-index: -2;
  4484.  inset: -5px;
  4485.  transform: translate(10px, 8px);
  4486.  background: /* .. */;
  4487.  filter: blur(10px);
  4488. }
  4489. /* Reproduces the main element styles */
  4490. .box::after {
  4491.  content: "&quot;";
  4492.  position: absolute;
  4493.  z-index: -1;
  4494.  inset: 0;
  4495.  /* Inherit all the decorations defined on the main element */
  4496.  background: inherit;
  4497.  border: inherit;
  4498.  box-shadow: inherit;
  4499. }</code></pre>
  4500.  
  4501.  
  4502.  
  4503. <div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_rNrdrWZ/58c29bf226f701daf9430aba16e9fcaa" src="//codepen.io/anon/embed/rNrdrWZ/58c29bf226f701daf9430aba16e9fcaa?height=450&amp;theme-id=1&amp;slug-hash=rNrdrWZ/58c29bf226f701daf9430aba16e9fcaa&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed rNrdrWZ/58c29bf226f701daf9430aba16e9fcaa" title="CodePen Embed rNrdrWZ/58c29bf226f701daf9430aba16e9fcaa" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>
  4504.  
  4505.  
  4506.  
  4507. <p>It’s important to note that we are <em>forcing</em> the main element to create a stacking context by declaring <code>z-index: 0</code>, or <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context#description" rel="noopener">any other property that do the same</a>, on it. Also, don’t forget that pseudo-elements consider the padding box of the main element as a reference. So, if the main element has a border, you need to take that into account when defining the pseudo-element styles. You will notice that I am using <code>inset: -2px</code> on <code>::after</code> to account for the border defined on the main element.</p>
  4508.  
  4509.  
  4510.  
  4511. <p>As I said, this solution is probably good enough in a majority of cases where you want a gradient shadow, as long as you don’t need to support transparency. But we are here for the challenge and to push the limits, so even if you don’t need what is coming next, stay with me. You will probably learn new CSS tricks that you can use elsewhere.</p>
  4512.  
  4513.  
  4514. <h3 class="wp-block-heading" id="transparent-solution">Transparent solution</h3>
  4515.  
  4516.  
  4517. <p>Let’s pick up where we left off on the 3D <code>transform</code> and remove the background from the main element. I will start with a shadow that has both offsets and spread distance equal to <code>0</code>.</p>
  4518.  
  4519.  
  4520.  
  4521. <div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_dyjmjaM/e16bf3655086e062830a818b9133484c" src="//codepen.io/anon/embed/dyjmjaM/e16bf3655086e062830a818b9133484c?height=450&amp;theme-id=1&amp;slug-hash=dyjmjaM/e16bf3655086e062830a818b9133484c&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed dyjmjaM/e16bf3655086e062830a818b9133484c" title="CodePen Embed dyjmjaM/e16bf3655086e062830a818b9133484c" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>
  4522.  
  4523.  
  4524.  
  4525. <p>The idea is to find a way to cut or hide everything inside the area of the element (inside the green border) while keeping what is outside. We are going to use <a href="https://css-tricks.com/almanac/properties/c/clip-path/"><code>clip-path</code></a> for that. But you might wonder how <code>clip-path</code> can make a cut <em>inside</em> an element.</p>
  4526.  
  4527.  
  4528.  
  4529. <p>Indeed, there’s no way to do that, but we can simulate it using a particular polygon pattern:</p>
  4530.  
  4531.  
  4532.  
  4533. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">clip-path: polygon(-100vmax -100vmax,100vmax -100vmax,100vmax 100vmax,-100vmax 100vmax,-100vmax -100vmax,0 0,0 100%,100% 100%,100% 0,0 0)</code></pre>
  4534.  
  4535.  
  4536.  
  4537. <div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_RwBMBmp/79f53e14661aa0ac1684079eb8162ff0" src="//codepen.io/anon/embed/RwBMBmp/79f53e14661aa0ac1684079eb8162ff0?height=450&amp;theme-id=1&amp;slug-hash=RwBMBmp/79f53e14661aa0ac1684079eb8162ff0&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed RwBMBmp/79f53e14661aa0ac1684079eb8162ff0" title="CodePen Embed RwBMBmp/79f53e14661aa0ac1684079eb8162ff0" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>
  4538.  
  4539.  
  4540.  
  4541. <p>Tada! We have a gradient shadow that supports transparency. All we did is add a <code>clip-path</code> to the previous code. Here is a figure to illustrate the polygon part.</p>
  4542.  
  4543.  
  4544.  
  4545. <figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="763" height="502" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/s_48C6EEC0315F934C02900CCBCCCDFBD0A1B256CE69F7A4B8A991D9F184217B87_1674637694432_image.png?resize=763%2C502&#038;ssl=1" alt="Showing the clip-path coordinates for the element." class="wp-image-376767" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/s_48C6EEC0315F934C02900CCBCCCDFBD0A1B256CE69F7A4B8A991D9F184217B87_1674637694432_image.png?w=763&amp;ssl=1 763w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2023/01/s_48C6EEC0315F934C02900CCBCCCDFBD0A1B256CE69F7A4B8A991D9F184217B87_1674637694432_image.png?resize=300%2C197&amp;ssl=1 300w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1" /></figure>
  4546.  
  4547.  
  4548.  
  4549. <p>The blue area is the visible part after applying the <code>clip-path</code>. I am only using the blue color to illustrate the concept, but in reality, we will only see the shadow inside that area. As you can see, we have four points defined with a big value (<code>B</code>). My big value is <code>100vmax</code>, but it can be any big value you want. The idea is to ensure we have enough space for the shadow. We also have four points that are the corners of the pseudo-element.</p>
  4550.  
  4551.  
  4552.  
  4553. <p>The arrows illustrate the path that defines the polygon. We start from <code>(-B, -B)</code> until we reach <code>(0,0)</code>. In total, we need 10 points. Not eight points because two points are repeated twice in the path (<code>(-B,-B)</code> and <code>(0,0)</code>).</p>
  4554.  
  4555.  
  4556.  
  4557. <p>There’s still <em>one more thing</em> left for us to do, and it’s to account for the spread distance and the offsets. The only reason the demo above works is because it is a particular case where the offsets and spread distance are equal to <code>0</code>.</p>
  4558.  
  4559.  
  4560.  
  4561. <p>Let’s define the spread and see what happens. Remember that we use <code>inset</code> with a negative value to do this:</p>
  4562.  
  4563.  
  4564.  
  4565. <div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_YzjaMOY/b3df7064381094b61c26687918bebf19" src="//codepen.io/anon/embed/YzjaMOY/b3df7064381094b61c26687918bebf19?height=450&amp;theme-id=1&amp;slug-hash=YzjaMOY/b3df7064381094b61c26687918bebf19&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed YzjaMOY/b3df7064381094b61c26687918bebf19" title="CodePen Embed YzjaMOY/b3df7064381094b61c26687918bebf19" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>
  4566.  
  4567.  
  4568.  
  4569. <p>The pseudo-element is now bigger than the main element, so the <code>clip-path</code> cuts more than we need it to. Remember, we always need to cut the part <em>inside</em> the main element (the area inside the green border of the example). We need to adjust the position of the four points inside of <code>clip-path</code>.</p>
  4570.  
  4571.  
  4572.  
  4573. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.box {
  4574.  --s: 10px; /* the spread  */
  4575.  position: relative;
  4576. }
  4577. .box::before {
  4578.  inset: calc(-1 * var(--s));
  4579.  clip-path: polygon(
  4580.    -100vmax -100vmax,
  4581.     100vmax -100vmax,
  4582.     100vmax 100vmax,
  4583.    -100vmax 100vmax,
  4584.    -100vmax -100vmax,
  4585.    calc(0px  + var(--s)) calc(0px  + var(--s)),
  4586.    calc(0px  + var(--s)) calc(100% - var(--s)),
  4587.    calc(100% - var(--s)) calc(100% - var(--s)),
  4588.    calc(100% - var(--s)) calc(0px  + var(--s)),
  4589.    calc(0px  + var(--s)) calc(0px  + var(--s))
  4590.  );
  4591. }</code></pre>
  4592.  
  4593.  
  4594.  
  4595. <p>We’ve defined a CSS variable, <code>--s</code>, for the spread distance and updated the polygon points. I didn’t touch the points where I am using the big value. I only update the points that define the corners of the pseudo-element. I increase all the zero values by <code>--s</code> and decrease the <code>100%</code> values by <code>--s</code>.</p>
  4596.  
  4597.  
  4598.  
  4599. <div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_jOpzRdg/26b06416f709fa6f69cd9a328f6e7a79" src="//codepen.io/anon/embed/jOpzRdg/26b06416f709fa6f69cd9a328f6e7a79?height=450&amp;theme-id=1&amp;slug-hash=jOpzRdg/26b06416f709fa6f69cd9a328f6e7a79&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed jOpzRdg/26b06416f709fa6f69cd9a328f6e7a79" title="CodePen Embed jOpzRdg/26b06416f709fa6f69cd9a328f6e7a79" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>
  4600.  
  4601.  
  4602.  
  4603. <p>It’s the same logic with the offsets. When we translate the pseudo-element, the shadow is out of alignment, and we need to rectify the polygon again and move the points in the opposite direction.</p>
  4604.  
  4605.  
  4606.  
  4607. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.box {
  4608.  --s: 10px; /* the spread */
  4609.  --x: 10px; /* X offset */
  4610.  --y: 8px;  /* Y offset */
  4611.  position: relative;
  4612. }
  4613. .box::before {
  4614.  inset: calc(-1 * var(--s));
  4615.  transform: translate3d(var(--x), var(--y), -1px);
  4616.  clip-path: polygon(
  4617.    -100vmax -100vmax,
  4618.     100vmax -100vmax,
  4619.     100vmax 100vmax,
  4620.    -100vmax 100vmax,
  4621.    -100vmax -100vmax,
  4622.    calc(0px  + var(--s) - var(--x)) calc(0px  + var(--s) - var(--y)),
  4623.    calc(0px  + var(--s) - var(--x)) calc(100% - var(--s) - var(--y)),
  4624.    calc(100% - var(--s) - var(--x)) calc(100% - var(--s) - var(--y)),
  4625.    calc(100% - var(--s) - var(--x)) calc(0px  + var(--s) - var(--y)),
  4626.    calc(0px  + var(--s) - var(--x)) calc(0px  + var(--s) - var(--y))
  4627.  );
  4628. }</code></pre>
  4629.  
  4630.  
  4631.  
  4632. <p>There are two more variables for the offsets: <code>--x</code> and <code>--y</code>. We use them inside of <code>transform</code> and we also update the <code>clip-path</code> values. We still don’t touch the polygon points with big values, but we offset all the others — we reduce <code>--x</code> from the X coordinates, and <code>--y</code> from the Y coordinates.</p>
  4633.  
  4634.  
  4635.  
  4636. <p>Now all we have to do is to update a few variables to control the gradient shadow. And while we are at it, let’s also make the blur radius a variable as well:</p>
  4637.  
  4638.  
  4639.  
  4640. <div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_poZLmgj/7a0244a4dca0b363791881fe50f235f8" src="//codepen.io/anon/embed/poZLmgj/7a0244a4dca0b363791881fe50f235f8?height=550&amp;theme-id=1&amp;slug-hash=poZLmgj/7a0244a4dca0b363791881fe50f235f8&amp;default-tab=result" height="550" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed poZLmgj/7a0244a4dca0b363791881fe50f235f8" title="CodePen Embed poZLmgj/7a0244a4dca0b363791881fe50f235f8" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>
  4641.  
  4642.  
  4643.  
  4644. <blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
  4645. <p>Do we still need the 3D <code>transform</code> trick?</p>
  4646. </blockquote>
  4647.  
  4648.  
  4649.  
  4650. <p>It all depends on the border. Don’t forget that the reference for a pseudo-element is the padding box, so if you apply a border to your main element, you will have an overlap. You either keep the 3D <code>transform</code> trick or update the <code>inset</code> value to account for the border.</p>
  4651.  
  4652.  
  4653.  
  4654. <p>Here is the previous demo with an updated <code>inset</code> value in place of the 3D <code>transform</code>:</p>
  4655.  
  4656.  
  4657.  
  4658. <div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_NWBYVRK/bf9c95af498115ea9f2819d5aa6505b8" src="//codepen.io/anon/embed/NWBYVRK/bf9c95af498115ea9f2819d5aa6505b8?height=550&amp;theme-id=1&amp;slug-hash=NWBYVRK/bf9c95af498115ea9f2819d5aa6505b8&amp;default-tab=result" height="550" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed NWBYVRK/bf9c95af498115ea9f2819d5aa6505b8" title="CodePen Embed NWBYVRK/bf9c95af498115ea9f2819d5aa6505b8" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>
  4659.  
  4660.  
  4661.  
  4662. <p>I‘d say this is a more suitable way to go because the spread distance will be more accurate, as it starts from the border-box instead of the padding-box. But you will need to adjust the <code>inset</code> value according to the main element’s border. Sometimes, the border of the element is unknown and you have to use the previous solution.</p>
  4663.  
  4664.  
  4665.  
  4666. <p>With the earlier non-transparent solution, it’s possible you will face a stacking context issue. And with the transparent solution, it’s possible you face a border issue instead. Now you have options and ways to work around those issues. The 3D transform trick is my favorite solution because it fixes all the issues (<a href="https://css-generators.com/gradient-shadows/" rel="noopener">The online generator</a> will consider it as well)</p>
  4667.  
  4668.  
  4669. <h3 class="wp-block-heading" id="adding-a-border-radius">Adding a border radius</h3>
  4670.  
  4671.  
  4672. <p>If you try adding <code>border-radius</code> to the element when using the non-transparent solution we started with, it is a fairly trivial task. All you need to do is to inherit the same value from the main element, and you are done.</p>
  4673.  
  4674.  
  4675.  
  4676. <div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_abjYrYq/652ab5c0da5084a21bb11345ed426273" src="//codepen.io/anon/embed/abjYrYq/652ab5c0da5084a21bb11345ed426273?height=450&amp;theme-id=1&amp;slug-hash=abjYrYq/652ab5c0da5084a21bb11345ed426273&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed abjYrYq/652ab5c0da5084a21bb11345ed426273" title="CodePen Embed abjYrYq/652ab5c0da5084a21bb11345ed426273" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>
  4677.  
  4678.  
  4679.  
  4680. <p>Even if you don’t have a border radius, it’s a good idea to define <code>border-radius: inherit</code>. That accounts for any potential <code>border-radius</code> you might want to add later or a border radius that comes from somewhere else.</p>
  4681.  
  4682.  
  4683.  
  4684. <p>It’s a different story when dealing with the transparent solution. Unfortunately, it means finding another solution because <code>clip-path</code> cannot deal with curvatures. That means we won’t be able to cut the area inside the main element.</p>
  4685.  
  4686.  
  4687.  
  4688. <p>We will introduce the <a href="https://css-tricks.com/almanac/properties/m/mask/"><code>mask</code></a> property to the mix.</p>
  4689.  
  4690.  
  4691.  
  4692. <p>This part was very tedious, and I struggled to find a general solution that doesn’t rely on <a href="https://css-tricks.com/magic-numbers-in-css/">magic numbers</a>. I ended up with a very complex solution that uses only one pseudo-element, but the code was a lump of spaghetti that covers only a few particular cases. I don&#8217;t think it is worth exploring that route.</p>
  4693.  
  4694.  
  4695.  
  4696. <p>I decided to insert an extra element for the sake of simpler code. Here’s the markup:</p>
  4697.  
  4698.  
  4699.  
  4700. <pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;div class="box">
  4701.  &lt;sh>&lt;/sh>
  4702. &lt;/div></code></pre>
  4703.  
  4704.  
  4705.  
  4706. <p>I am using a custom element, <code>&lt;sh&gt;</code>, to avoid any potential conflict with external CSS. I could have used a <code>&lt;div&gt;</code>, but since it’s a common element, it can easily be targeted by another CSS rule coming from somewhere else that can break our code.</p>
  4707.  
  4708.  
  4709.  
  4710. <p>The first step is to position the <code>&lt;sh&gt;</code> element and purposely create an overflow:</p>
  4711.  
  4712.  
  4713.  
  4714. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.box {
  4715.  --r: 50px;
  4716.  position: relative;
  4717.  border-radius: var(--r);
  4718. }
  4719. .box sh {
  4720.  position: absolute;
  4721.  inset: -150px;
  4722.  border: 150px solid #0000;
  4723.  border-radius: calc(150px + var(--r));
  4724. }</code></pre>
  4725.  
  4726.  
  4727.  
  4728. <p>The code may look a bit strange, but we’ll get to the logic behind it as we go. Next, we create the gradient shadow using a pseudo-element of <code>&lt;sh&gt;</code>.</p>
  4729.  
  4730.  
  4731.  
  4732. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.box {
  4733.  --r: 50px;
  4734.  position: relative;
  4735.  border-radius: var(--r);
  4736.  transform-style: preserve-3d;
  4737. }
  4738. .box sh {
  4739.  position: absolute;
  4740.  inset: -150px;
  4741.  border: 150px solid #0000;
  4742.  border-radius: calc(150px + var(--r));
  4743.  transform: translateZ(-1px)
  4744. }
  4745. .box sh::before {
  4746.  content: "";
  4747.  position: absolute;
  4748.  inset: -5px;
  4749.  border-radius: var(--r);
  4750.  background: /* Your gradient */;
  4751.  filter: blur(10px);
  4752.  transform: translate(10px,8px);
  4753. }</code></pre>
  4754.  
  4755.  
  4756.  
  4757. <p>As you can see, the pseudo-element uses the same code as all the previous examples. The only difference is the 3D <code>transform</code> defined on the <code>&lt;sh&gt;</code> element instead of the pseudo-element. For the moment, we have a gradient shadow without the transparency feature:</p>
  4758.  
  4759.  
  4760.  
  4761. <div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_MWBGJoV/ccdb3fd5c762a95f1d580f02babfba3f" src="//codepen.io/anon/embed/MWBGJoV/ccdb3fd5c762a95f1d580f02babfba3f?height=600&amp;theme-id=1&amp;slug-hash=MWBGJoV/ccdb3fd5c762a95f1d580f02babfba3f&amp;default-tab=result" height="600" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed MWBGJoV/ccdb3fd5c762a95f1d580f02babfba3f" title="CodePen Embed MWBGJoV/ccdb3fd5c762a95f1d580f02babfba3f" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>
  4762.  
  4763.  
  4764.  
  4765. <p>Note that the area of the <code>&lt;sh&gt;</code> element is defined with the black outline. Why I am doing this? Because that way, I am able to apply a <code>mask</code> on it to hide the part inside the green area and keep the overflowing part where we need to see the shadow.</p>
  4766.  
  4767.  
  4768.  
  4769. <p>I know it’s a bit tricky, but unlike <code>clip-path</code>, the <code>mask</code> property doesn’t account for the area <em>outside</em> an element to show and hide things. That’s why I was obligated to introduce the extra element — to simulate the “outside” area.</p>
  4770.  
  4771.  
  4772.  
  4773. <p>Also, note that I am using a combination of <code>border</code> and <code>inset</code> to define that area. This allows me to keep the padding-box of that extra element the same as the main element so that the pseudo-element won’t need additional calculations.</p>
  4774.  
  4775.  
  4776.  
  4777. <p>Another useful thing we get from using an extra element is that the element is fixed, and only the pseudo-element is moving (using <code><a href="https://css-tricks.com/almanac/properties/t/transform/#aa-translate">translate</a></code>). This will allow me to easily define the mask, which is the <em>last</em> step of this trick.</p>
  4778.  
  4779.  
  4780.  
  4781. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">mask:
  4782.  linear-gradient(#000 0 0) content-box,
  4783.  linear-gradient(#000 0 0);
  4784. mask-composite: exclude;</code></pre>
  4785.  
  4786.  
  4787.  
  4788. <div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_ExpLZMz/2eeab3ea368b67035e79fea37fa52e83" src="//codepen.io/anon/embed/ExpLZMz/2eeab3ea368b67035e79fea37fa52e83?height=450&amp;theme-id=1&amp;slug-hash=ExpLZMz/2eeab3ea368b67035e79fea37fa52e83&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed ExpLZMz/2eeab3ea368b67035e79fea37fa52e83" title="CodePen Embed ExpLZMz/2eeab3ea368b67035e79fea37fa52e83" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>
  4789.  
  4790.  
  4791.  
  4792. <p>It’s done! We have our gradient shadow, and it supports <code>border-radius</code>! You probably expected a complex <code>mask</code> value with oodles of gradients, but no! We only need two simple gradients and a <code><a href="https://css-tricks.com/almanac/properties/m/mask-composite/">mask-composite</a></code> to complete the magic.</p>
  4793.  
  4794.  
  4795.  
  4796. <p>Let’s isolate the <code>&lt;sh&gt;</code> element to understand what is happening there:</p>
  4797.  
  4798.  
  4799.  
  4800. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.box sh {
  4801.  position: absolute;
  4802.  inset: -150px;
  4803.  border: 150px solid red;
  4804.  background: lightblue;
  4805.  border-radius: calc(150px + var(--r));
  4806. }</code></pre>
  4807.  
  4808.  
  4809.  
  4810. <p>Here’s what we get:</p>
  4811.  
  4812.  
  4813.  
  4814. <div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_yLqjMLQ/6d7ee140a1d1c2906543547dfc7489dd" src="//codepen.io/anon/embed/yLqjMLQ/6d7ee140a1d1c2906543547dfc7489dd?height=600&amp;theme-id=1&amp;slug-hash=yLqjMLQ/6d7ee140a1d1c2906543547dfc7489dd&amp;default-tab=result" height="600" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed yLqjMLQ/6d7ee140a1d1c2906543547dfc7489dd" title="CodePen Embed yLqjMLQ/6d7ee140a1d1c2906543547dfc7489dd" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>
  4815.  
  4816.  
  4817.  
  4818. <p>Note how the inner radius matches the main element’s <code>border-radius</code>. I have defined a big border (<code>150px</code>) and a <code>border-radius</code> equal to the big border <em>plus</em> the main element’s radius. On the outside, I have a radius equal to <code>150px + R</code>. On the inside, I have <code>150px + R - 150px = R</code>.</p>
  4819.  
  4820.  
  4821.  
  4822. <p>We must hide the inner (blue) part and make sure the border (red) part is still visible. To do that, I’ve defined two mask layers —One that covers only the content-box area and another that covers the border-box area (the default value). Then I excluded one from another to reveal the border.</p>
  4823.  
  4824.  
  4825.  
  4826. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">mask:
  4827.  linear-gradient(#000 0 0) content-box,
  4828.  linear-gradient(#000 0 0);
  4829. mask-composite: exclude;</code></pre>
  4830.  
  4831.  
  4832.  
  4833. <div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_QWBrpjB/e8d4fb927227a5e3682e46220d9c9af0" src="//codepen.io/anon/embed/QWBrpjB/e8d4fb927227a5e3682e46220d9c9af0?height=600&amp;theme-id=1&amp;slug-hash=QWBrpjB/e8d4fb927227a5e3682e46220d9c9af0&amp;default-tab=result" height="600" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed QWBrpjB/e8d4fb927227a5e3682e46220d9c9af0" title="CodePen Embed QWBrpjB/e8d4fb927227a5e3682e46220d9c9af0" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>
  4834.  
  4835.  
  4836.  
  4837. <p>I used the same technique to <a href="https://dev.to/afif/border-with-gradient-and-radius-387f" rel="noopener">create a border that supports gradients and <code>border-radius</code></a>. Ana Tudor has also a good article <a href="https://css-tricks.com/mask-compositing-the-crash-course/">about masking composite</a> that I invite you to read.</p>
  4838.  
  4839.  
  4840.  
  4841. <blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
  4842. <p>Are there any drawbacks to this method?</p>
  4843. </blockquote>
  4844.  
  4845.  
  4846.  
  4847. <p>Yes, this definitely not perfect. The first issue you may face is related to using a border on the main element. This may create a small misalignment in the radii if you don’t account for it. We have this issue in our example, but perhaps you can hardly notice it.</p>
  4848.  
  4849.  
  4850.  
  4851. <p>The fix is relatively easy: Add the border’s width for the <code>&lt;sh&gt;</code> element’s <code>inset</code>.</p>
  4852.  
  4853.  
  4854.  
  4855. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line="4,8"><code markup="tt">.box {
  4856.  --r: 50px;
  4857.  border-radius: var(--r);
  4858.  border: 2px solid;
  4859. }
  4860. .box sh {
  4861.  position: absolute;
  4862.  inset: -152px; /* 150px + 2px */
  4863.  border: 150px solid #0000;
  4864.  border-radius: calc(150px + var(--r));
  4865. }</code></pre>
  4866.  
  4867.  
  4868.  
  4869. <p>Another drawback is the big value we’re using for the border (<code>150px</code> in the example). This value should be big enough to contain the shadow but not too big to avoid overflow and scrollbar issues. Luckily, <a href="https://css-generators.com/gradient-shadows/" rel="noopener">the online generator</a> will calculate the optimal value considering all the parameters.</p>
  4870.  
  4871.  
  4872.  
  4873. <p>The last drawback I am aware of is when you’re working with a complex <code>border-radius</code>. For example, if you want a different radius applied to each corner, you must define a variable for each side. It’s not really a drawback, I suppose, but it can make your code a bit tougher to maintain.</p>
  4874.  
  4875.  
  4876.  
  4877. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.box {
  4878.  --r-top: 10px;
  4879.  --r-right: 40px;
  4880.  --r-bottom: 30px;
  4881.  --r-left: 20px;
  4882.  border-radius: var(--r-top) var(--r-right) var(--r-bottom) var(--r-left);
  4883. }
  4884. .box sh {
  4885.  border-radius: calc(150px + var(--r-top)) calc(150px + var(--r-right)) calc(150px + var(--r-bottom)) calc(150px + var(--r-left));
  4886. }
  4887. .box sh:before {
  4888.  border-radius: var(--r-top) var(--r-right) var(--r-bottom) var(--r-left);
  4889. }</code></pre>
  4890.  
  4891.  
  4892.  
  4893. <div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_LYBmWLR/9f04e99ed847d585938ab6202a231fc3" src="//codepen.io/anon/embed/LYBmWLR/9f04e99ed847d585938ab6202a231fc3?height=450&amp;theme-id=1&amp;slug-hash=LYBmWLR/9f04e99ed847d585938ab6202a231fc3&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed LYBmWLR/9f04e99ed847d585938ab6202a231fc3" title="CodePen Embed LYBmWLR/9f04e99ed847d585938ab6202a231fc3" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>
  4894.  
  4895.  
  4896.  
  4897. <p><a href="https://css-generators.com/gradient-shadows/" rel="noopener">The online generator</a> only considers a uniform radius for the sake of simplicity, but you now know how to modify the code if you want to consider a complex radius configuration.</p>
  4898.  
  4899.  
  4900. <h3 class="wp-block-heading" id="wrapping-up">Wrapping up</h3>
  4901.  
  4902.  
  4903. <p>We’ve reached the end! The magic behind gradient shadows is no longer a mystery. I tried to cover all the possibilities and any possible issues you might face. If I missed something or you discover any issue, please feel free to report it in the comment section, and I’ll check it out.</p>
  4904.  
  4905.  
  4906.  
  4907. <p>Again, a lot of this is likely overkill considering that the de facto solution will cover most of your use cases. Nevertheless, it’s good to know the “why” and “how” behind the trick, and how to overcome its limitations. Plus, we got good exercise playing with CSS clipping and masking.</p>
  4908.  
  4909.  
  4910.  
  4911. <p>And, of course, you have <a href="https://css-generators.com/gradient-shadows/" rel="noopener">the online generator</a> you can reach for anytime you want to avoid the hassle.</p>
  4912. <hr />
  4913. <p><small><a rel="nofollow" href="https://css-tricks.com/different-ways-to-get-css-gradient-shadows/">Different Ways to Get CSS Gradient Shadows</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
  4914. ]]></content:encoded>
  4915. <wfw:commentRss>https://css-tricks.com/different-ways-to-get-css-gradient-shadows/feed/</wfw:commentRss>
  4916. <slash:comments>0</slash:comments>
  4917. <post-id xmlns="com-wordpress:feed-additions:1">376764</post-id> </item>
  4918. <item>
  4919. <title>Healthcare, Selling Lemons, and the Price of Developer Experience</title>
  4920. <link>https://css-tricks.com/healthcare-selling-lemons-and-the-price-of-developer-experience/</link>
  4921. <comments>https://css-tricks.com/healthcare-selling-lemons-and-the-price-of-developer-experience/#comments</comments>
  4922. <dc:creator><![CDATA[Geoff Graham]]></dc:creator>
  4923. <pubDate>Thu, 09 Feb 2023 19:45:48 +0000</pubDate>
  4924. <category><![CDATA[Article]]></category>
  4925. <category><![CDATA[dx]]></category>
  4926. <category><![CDATA[javascript framework]]></category>
  4927. <category><![CDATA[third-party]]></category>
  4928. <category><![CDATA[user testing]]></category>
  4929. <category><![CDATA[ux]]></category>
  4930. <guid isPermaLink="false">https://css-tricks.com/?p=377169</guid>
  4931.  
  4932. <description><![CDATA[<p>Every now and then, a one blog post is published and it spurs a reaction or response in others that are, in turn, published as blogs posts, and a theme starts to emerge. That&#8217;s what happened this past week and &#8230;</p>
  4933. <hr />
  4934. <p><small><a rel="nofollow" href="https://css-tricks.com/healthcare-selling-lemons-and-the-price-of-developer-experience/">Healthcare, Selling Lemons, and the Price of Developer Experience</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
  4935. ]]></description>
  4936. <content:encoded><![CDATA[
  4937. <p>Every now and then, a one blog post is published and it spurs a reaction or response in others that are, in turn, published as blogs posts, and a theme starts to emerge. That&#8217;s what happened this past week and the theme developed around the cost of JavaScript frameworks — a cost that, in this case, reveals just how darn important it is to <a href="https://css-tricks.com/responsible-javascript/">use JavaScript responsibly</a>.</p>
  4938.  
  4939.  
  4940.  
  4941. <span id="more-377169"></span>
  4942.  
  4943.  
  4944. <h3 class="wp-block-heading" id="eric-bailey-modern-health-frameworks-performance-and-harm">Eric Bailey: <a href="https://ericwbailey.website/published/modern-health-frameworks-performance-and-harm/" rel="noopener">Modern Health, frameworks, performance, and harm</a></h3>
  4945.  
  4946.  
  4947. <p>This is where the story begins. Eric goes to a health service provider website to book an appointment and gets&#8230; a blank screen.</p>
  4948.  
  4949.  
  4950.  
  4951. <blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
  4952. <p>In addition to&nbsp;<a href="https://builtwith.com/advanced?back=joinmodernhealth.com" rel="noopener">a terrifying amount of telemetry</a>, Modern Health’s customer-facing experience is delivered using React and Webpack.</p>
  4953.  
  4954.  
  4955.  
  4956. <p>If you are familiar with how the web is built, what happened is pretty obvious: A website that over-relies on JavaScript to power its experience had its logic collide with one or more other errant pieces of logic that it summons. This created a deadlock.</p>
  4957.  
  4958.  
  4959.  
  4960. <p>If you do not make digital experiences for a living, what happened is not obvious at all. All you see is a tiny fake loading spinner that never stops.</p>
  4961. </blockquote>
  4962.  
  4963.  
  4964.  
  4965. <p><em>D&#8217;oh.</em> This might be mere nuisance — or even laughable — in some situations, but not when someone&#8217;s health is on the line:</p>
  4966.  
  4967.  
  4968.  
  4969. <blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
  4970. <p>A person seeking help in a time of crisis does not care about TypeScript, tree shaking, hot module replacement, A/B tests, burndown charts, NPS, OKRs, KPIs, or other startup jargon.&nbsp;<a href="https://andy-bell.co.uk/speed-for-who/" rel="noopener">Developer experience does not count for shit</a>&nbsp;if the person using the thing they built can’t actually get what they need.</p>
  4971. </blockquote>
  4972.  
  4973.  
  4974.  
  4975. <p>This is the big smack of reality. What happens when our tooling and reporting — the very things that are supposed to make our work more effective — get in the way of the user experience? These are tools that provide insights that can help us <a href="https://css-tricks.com/preventing-suicide-with-ux-a-case-study-on-google-search/">anticipate a user&#8217;s needs, especially in a time of need</a>.</p>
  4976.  
  4977.  
  4978.  
  4979. <p>I realize that pointing the finger at JavaScript frameworks is already divisive. But this goes beyond whether you use React or <em>framework d&#8217;jour</em>. It&#8217;s about business priorities and developer experience conflicting with user experiences.</p>
  4980.  
  4981.  
  4982. <h3 class="wp-block-heading" id="alex-russell-the-market-for-lemons">Alex Russell: <a href="https://infrequently.org/2023/02/the-market-for-lemons/" rel="noopener">The Market for Lemons</a></h3>
  4983.  
  4984.  
  4985. <blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
  4986. <p>Partisans for slow, complex frameworks have successfully marketed lemons as the hot new thing, despite the pervasive failures in their wake, crowding out higher-quality options in the process.</p>
  4987.  
  4988.  
  4989.  
  4990. <p>These technologies were initially pitched on the back of&nbsp;<em>&#8220;better user experiences&#8221;</em>, but have&nbsp;<a href="https://dev.to/tigt/making-the-worlds-fastest-website-and-other-mistakes-56na" rel="noopener">utterly failed</a>&nbsp;to deliver on that promise outside of the&nbsp;<a href="https://infrequently.org/2022/05/performance-management-maturity/" rel="noopener">high-management-maturity organisations</a>&nbsp;in which they were born. Transplanted into the wider web, these new stacks have proven to be&nbsp;<a href="https://infrequently.org/2022/12/performance-baseline-2023/" rel="noopener">expensive duds</a>.</p>
  4991. </blockquote>
  4992.  
  4993.  
  4994.  
  4995. <p>There&#8217;s the rub. Alex ain&#8217;t mincing words, but notice that the onus is on the way frameworks haved been marketed to developers than developers themselves. The sales pitch?</p>
  4996.  
  4997.  
  4998.  
  4999. <blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
  5000. <p>Once the lemon sellers embed the data-light idea that improved &#8220;Developer Experience&#8221; (&#8220;DX&#8221;) leads to better user outcomes, improving &#8220;DX&#8221; became and end unto itself, and many who knew better felt forced to play along. The long lead times in falsifying trickle-down UX was a feature, not a bug; they don&#8217;t need you to succeed, only to keep buying.</p>
  5001.  
  5002.  
  5003.  
  5004. <p>As marketing goes, the &#8220;DX&#8221;&nbsp;<a href="https://infrequently.org/2018/09/the-developer-experience-bait-and-switch/" rel="noopener">bait-and-switch</a>&nbsp;is brilliant, but the tech isn&#8217;t delivering for anyone&nbsp;<em>but</em>&nbsp;developers.</p>
  5005. </blockquote>
  5006.  
  5007.  
  5008.  
  5009. <p>Tough to stomach, right? No one wants to be duped, and it&#8217;s tough to admit a sunken cost when there is one. It gets downright personal if you&#8217;ve invested time in a specific piece of tech and effort integrating it into your stack. Development workflows are hard and settling into one is sorta like settling into a house you plan on living in a little while. But you&#8217;d want to know if your house was built on what Alex calls a <a href="https://infrequently.org/2023/02/the-market-for-lemons/#sandy-foundations" rel="noopener">&#8220;sandy foundation&#8221;</a>.</p>
  5010.  
  5011.  
  5012.  
  5013. <p>I&#8217;d just like to pause here a moment to say I have no skin in this debate. As a web generalist, I tend to adopt new tools early for familiarity then drop them fast, relegating them to my toolshed until I find a good use for them. In other words, my knowledge is <em>wide</em> but not very <em>deep</em> in one area or thing. <a href="https://css-tricks.com/the-best-cocktail-in-town/">HTML, CSS, and JavaScript is my go-to cocktail</a>, but I do care a great deal about user experience and know when to reach for a tool to solve a particular thing.</p>
  5014.  
  5015.  
  5016.  
  5017. <p>And let&#8217;s acknowledge that not everyone has a say in the matter. Many of us work on managed teams that are prescribed the tools we use. Alex says as much, which I think is important to call out because it&#8217;s clear this isn&#8217;t meant to be personal. It&#8217;s a statement on our priorities and making sure they along to user expectations.</p>
  5018.  
  5019.  
  5020.  
  5021. <p>Let&#8217;s alow Chris to steer us back to the story&#8230;</p>
  5022.  
  5023.  
  5024. <h3 class="wp-block-heading" id="chris-coyier-endtoend-tests-with-content-blockers">Chris Coyier: <a href="https://chriscoyier.net/2023/02/03/end-to-end-tests-with-content-blockers/" rel="noopener">End-To-End Tests with Content Blockers?</a></h3>
  5025.  
  5026.  
  5027. <p>So, maybe your app is built on React and it doesn&#8217;t matter why it&#8217;s that way. There&#8217;s still work to do to <a href="https://bradfrost.com/blog/post/enforcing-accessibility-best-practices-with-automatically-generated-ids/" rel="noopener">ensure the app is reliable and accessible</a>.</p>
  5028.  
  5029.  
  5030.  
  5031. <blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
  5032. <p>Just blocking a file shouldn’t totally wreck a website, but it often does! In JavaScript, that may be because the developers have written first-party JavaScript (which I’ll generally allow) that depends on third-party JavaScript (which I’ll generally block).</p>
  5033.  
  5034.  
  5035.  
  5036. <p>[&#8230;]</p>
  5037.  
  5038.  
  5039.  
  5040. <p>If I block resources from&nbsp;<code>tracking-website.com</code>, now my first-party JavaScript is going to throw an error. JavaScript isn’t chill. If an error is thrown, it doesn’t execute more JavaScript further down in the file. If further down in that file is&nbsp;<code>transitionToOnboarding();</code>— that ain’t gonna work.</p>
  5041. </blockquote>
  5042.  
  5043.  
  5044.  
  5045. <p>Maybe it&#8217;s worth revisiting your workflow and tweaking it to account to identify more points of failure.</p>
  5046.  
  5047.  
  5048.  
  5049. <blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
  5050. <p>So here’s an idea: Run your end-to-end tests in browsers that have popular content blockers with default configs installed.&nbsp;</p>
  5051.  
  5052.  
  5053.  
  5054. <p>Doing so may uncover problems like this that stop your customers, and indeed people in need, from being stopped in their tracks.</p>
  5055. </blockquote>
  5056.  
  5057.  
  5058.  
  5059. <p>Good idea! Hey, anything that helps paint a more realistic picture of how the app is used. That sort of clarity could happen a lot earlier in the process, perhaps before settling on development decisions. Know your users. Why are they using the app? How do they browse the web? Where are they phsically located? What problems could get in their way? <a href="https://css-tricks.com/what-we-dont-know/">Chris has a great talk on that, too.</a></p>
  5060. <hr />
  5061. <p><small><a rel="nofollow" href="https://css-tricks.com/healthcare-selling-lemons-and-the-price-of-developer-experience/">Healthcare, Selling Lemons, and the Price of Developer Experience</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
  5062. ]]></content:encoded>
  5063. <wfw:commentRss>https://css-tricks.com/healthcare-selling-lemons-and-the-price-of-developer-experience/feed/</wfw:commentRss>
  5064. <slash:comments>4</slash:comments>
  5065. <post-id xmlns="com-wordpress:feed-additions:1">377169</post-id> </item>
  5066. <item>
  5067. <title>Moving Backgrounds</title>
  5068. <link>https://css-tricks.com/moving-backgrounds/</link>
  5069. <comments>https://css-tricks.com/moving-backgrounds/#comments</comments>
  5070. <dc:creator><![CDATA[Saleh Mubashar]]></dc:creator>
  5071. <pubDate>Thu, 09 Feb 2023 15:03:55 +0000</pubDate>
  5072. <category><![CDATA[Article]]></category>
  5073. <category><![CDATA[animation]]></category>
  5074. <category><![CDATA[background-image]]></category>
  5075. <category><![CDATA[background-position]]></category>
  5076. <guid isPermaLink="false">https://css-tricks.com/?p=376723</guid>
  5077.  
  5078. <description><![CDATA[<p>We often think of background images as texture or something that provides contrast for legible content — in other words, not really content. If it was content, you’d probably reach for an <code>&#60;img&#62;</code> anyway, accessibility and whatnot.</p>
  5079. <p>But there are &#8230;</p>
  5080. <hr />
  5081. <p><small><a rel="nofollow" href="https://css-tricks.com/moving-backgrounds/">Moving Backgrounds</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
  5082. ]]></description>
  5083. <content:encoded><![CDATA[
  5084. <p>We often think of background images as texture or something that provides contrast for legible content — in other words, not really content. If it was content, you’d probably reach for an <code>&lt;img&gt;</code> anyway, accessibility and whatnot.</p>
  5085.  
  5086.  
  5087.  
  5088. <p>But there are times when the <em>position</em> or <em>scale</em> of a background image might sit somewhere between the poles of content and decoration. Context is king, right? If we change the background image&#8217;s position, it may convey a bit more context or experience.</p>
  5089.  
  5090.  
  5091.  
  5092. <p>How so? Let’s look at a few examples I’ve seen floating around.</p>
  5093.  
  5094.  
  5095.  
  5096. <span id="more-376723"></span>
  5097.  
  5098.  
  5099.  
  5100. <p class="is-style-explanation">As we get started, I&#8217;ll caution that there&#8217;s a fine line in these demos between images used for decoration and images used as content. The difference has accessibility implications where backgrounds are not announced to screen readers. <a href="https://www.smashingmagazine.com/2021/06/img-alt-attribute-alternate-description-decorative/" rel="noopener">If your image is really an image</a>, then maybe consider an <code>&lt;img&gt;</code> tag with proper <code>alt</code> text. And while we&#8217;re talking accessibility, it&#8217;s a good idea to <a href="https://css-tricks.com/nuking-motion-with-prefers-reduced-motion/">consider a user&#8217;s motion preference&#8217;s</a> as well.</p>
  5101.  
  5102.  
  5103. <h3 class="wp-block-heading" id="show-me-more">Show me more!</h3>
  5104.  
  5105.  
  5106. <p>Chris Coyier has this neat little demo from several years back.</p>
  5107.  
  5108.  
  5109.  
  5110. <div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_zRLLEy" src="//codepen.io/anon/embed/zRLLEy?height=450&amp;theme-id=1&amp;slug-hash=zRLLEy&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed zRLLEy" title="CodePen Embed zRLLEy" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>
  5111.  
  5112.  
  5113.  
  5114. <p>The demo is super practical in lots of ways because it’s a neat approach for displaying ads in content. You have the sales pitch and an enticing image to supplement it.</p>
  5115.  
  5116.  
  5117.  
  5118. <p>The big limitation for most ads, I’d wager, is the limited real estate. I don’t know if you’ve ever had to drop an ad onto a page, but I have and typically ask the advertiser for an image that meets exact pixel dimensions, so the asset fits the space.</p>
  5119.  
  5120.  
  5121.  
  5122. <p>But Chris’s demo alleviates the space issue. Hover the image and watch it both move and scale. The user actually gets <em>more</em> context for the product than they would have when the image was in its original position. That’s a win-win, right? The advertiser gets to create an eye-catching image without compromising context. Meanwhile, the user gets a little extra value from the newly revealed portions of the image.</p>
  5123.  
  5124.  
  5125.  
  5126. <p>If you peek at the demo’s markup, you’ll notice it’s pretty much what you’d expect. Here’s an abridged version:</p>
  5127.  
  5128.  
  5129.  
  5130. <pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;div class="ad-container">
  5131.  &lt;a href="#" target="_blank" rel="noopener">
  5132.    &lt;!-- Background image container  -->
  5133.    &lt;div class="ad-image">&lt;/div>
  5134.  &lt;/a>
  5135.  &lt;div class="ad-content">
  5136.    &lt;!-- Content -->
  5137.  &lt;/div>
  5138. &lt;/div></code></pre>
  5139.  
  5140.  
  5141.  
  5142. <p>We could probably quibble over the semantics a bit, but that’s not the point. We have a container with a linked-up <code>&lt;div&gt;</code> for the background image and another <code>&lt;div&gt;</code> to hold the content.</p>
  5143.  
  5144.  
  5145.  
  5146. <p>As far as styling goes, the important pieces are here:</p>
  5147.  
  5148.  
  5149.  
  5150. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.container {
  5151.  background-image: url("/path/to/some/image.png");
  5152.  background-repeat: no-repeat;
  5153.  background-position: 0 0;
  5154.  height: 400px;
  5155.  width: 350px;
  5156. }</code></pre>
  5157.  
  5158.  
  5159.  
  5160. <p>Not bad, right? We give the container some dimensions and set a background image on it that doesn’t repeat and is positioned by its bottom-left edge.</p>
  5161.  
  5162.  
  5163.  
  5164. <p>The real trick is with JavaScript. We will use that to get the mouse position and the container’s offset, then convert that value to an appropriate scale to set the <code>background-position</code>. First, let’s listen for mouse movements on the <code>.container</code> element:</p>
  5165.  
  5166.  
  5167.  
  5168. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">let container = document.querySelector(".container");
  5169. container.addEventListener("mousemove", function(e) {
  5170.    // Our function
  5171.  }
  5172. );</code></pre>
  5173.  
  5174.  
  5175.  
  5176. <p>From here, we can use the container’s <code>offsetX</code> and <code>offsetY</code> properties. But we won’t use these values directly, as the value for the X coordinate is smaller than what we need, and the Y coordinate is larger. We will have to play around a bit to find a constant that we can use as a multiplier.</p>
  5177.  
  5178.  
  5179.  
  5180. <p>It’s a bit touch-and-feel, but I’ve found that <code>1.32</code> and <code>0.455</code> work perfectly for the X and Y coordinates, respectively. We multiply the offsets by those values, append a <code>px</code> unit on the result, then apply it to the <code>background-position</code> values.</p>
  5181.  
  5182.  
  5183.  
  5184. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">let container = document.querySelector(".container");
  5185. container.addEventListener("mousemove", function(e) {
  5186.    container.style.backgroundPositionX = -e.offsetX * 1.32 + "px";
  5187.    container.style.backgroundPositionY = -e.offsetY * 0.455 + "px";
  5188.  }
  5189. );</code></pre>
  5190.  
  5191.  
  5192.  
  5193. <p>Lastly, we can also reset the background positions back to the original if the user leaves the image container.</p>
  5194.  
  5195.  
  5196.  
  5197. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">container.addEventListener("mouseleave", function() {
  5198.    container.style.backgroundPosition = "0px 0px";
  5199.  }
  5200. );</code></pre>
  5201.  
  5202.  
  5203.  
  5204. <div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_poZprej" src="//codepen.io/anon/embed/poZprej?height=450&amp;theme-id=1&amp;slug-hash=poZprej&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed poZprej" title="CodePen Embed poZprej" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>
  5205.  
  5206.  
  5207.  
  5208. <p>Since we’re on CSS-Tricks, I’ll offer that we could have done a much cheaper version of this with a little hover transition in vanilla CSS:</p>
  5209.  
  5210.  
  5211.  
  5212. <div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_xxJjOao" src="//codepen.io/anon/embed/xxJjOao?height=450&amp;theme-id=1&amp;slug-hash=xxJjOao&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed xxJjOao" title="CodePen Embed xxJjOao" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>
  5213.  
  5214.  
  5215. <h3 class="wp-block-heading" id="paint-a-bigger-picture">Paint a bigger picture</h3>
  5216.  
  5217.  
  5218. <p>No doubt you’ve been to some online clothing store or whatever and encountered the ol’ zoom-on-hover feature.</p>
  5219.  
  5220.  
  5221.  
  5222. <figure class="wp-block-video wp-block-embed is-type-video is-provider-videopress"><div class="wp-block-embed__wrapper">
  5223. <iframe title="VideoPress Video Player" aria-label='VideoPress Video Player' width='500' height='281' src='https://videopress.com/embed/ZIkxQ3Ky?cover=1&amp;playsinline=1&amp;posterUrl=https%3A%2F%2Fcss-tricks.com%2Fwp-content%2Fuploads%2F2023%2F01%2Fgap-zoom-to-hover_mp4_hd.original.jpg&amp;preloadContent=metadata&amp;useAverageColor=1&amp;hd=0' frameborder='0' allowfullscreen data-resize-to-parent="true" allow='clipboard-write'></iframe><script src='https://v0.wordpress.com/js/next/videopress-iframe.js?m=1674852142'></script>
  5224. </div></figure>
  5225.  
  5226.  
  5227.  
  5228. <p>This pattern has been around for what feels like forever (Dylan Winn-Brown shared his approach <a href="https://css-tricks.com/zooming-background-images/">back in 2016</a>), but that’s just a testament (I hope) to its usefulness. The user gets more context as they zoom in and get a better idea of a sweater’s stitching or what have you.</p>
  5229.  
  5230.  
  5231.  
  5232. <p>There’s two pieces to this: the <strong>container</strong> and the <strong>magnifier</strong>. The container is the only thing we need in the markup, as we’ll inject the magnifier element during the user’s interaction. So, behold our HTML!</p>
  5233.  
  5234.  
  5235.  
  5236. <pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;div class="container">&lt;/div></code></pre>
  5237.  
  5238.  
  5239.  
  5240. <p>​​In the CSS, we will create <code>width</code> and <code>height</code> variables to store the dimensions of the of the magnifier glass itself. &nbsp;Then we’ll&nbsp;give that&nbsp;<code>.container</code>​&nbsp;some shape and a&nbsp;<code>background-image</code>​:</p>
  5241.  
  5242.  
  5243.  
  5244. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">​​:root {
  5245. ​​  --magnifer-width: 85;
  5246. ​​  --magnifer-height: 85;
  5247. ​​}
  5248.  
  5249. .container {
  5250.  width: 500px;
  5251.  height: 400px;
  5252.  background-size: cover;
  5253.  background-image: url("/path/to/image.png");
  5254.  background-repeat: no-repeat;
  5255.  position: relative;
  5256. }</code></pre>
  5257.  
  5258.  
  5259.  
  5260. <p>There are some things we already know about the magnifier before we even see it, and we can define those styles up-front, specifically the previously defined variables for the <code>.maginifier</code>&#8216;s <code>width</code> and <code>height</code>:</p>
  5261.  
  5262.  
  5263.  
  5264. <pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.magnifier {
  5265.  position: absolute;
  5266.  width: calc(var(--magnifer-width) * 1px);
  5267. ​​  height: calc(var(--magnifer-height) * 1px);
  5268. ​​  border: 3px solid #000;
  5269. ​​  cursor: none;
  5270. ​​  background-image: url("/path/to/image.png");
  5271. ​​  background-repeat: no-repeat;
  5272. }</code></pre>
  5273.  
  5274.  
  5275.  
  5276. <p>It’s an absolutely-positioned little square that uses the <em>same</em> background image file as the <code>.container</code>. Do note that the calc function is solely used here to convert the unit-less value in the variable to pixels. Feel free to arrange that however you see fit as far as eliminating repetition in your code.</p>
  5277.  
  5278.  
  5279.  
  5280. <p>Now, let’s turn to the JavaScript that pulls this all together. First we need to access the CSS variable defined earlier. We will use this in multiple places later on. Then we need get the mouse position within the container because that’s the value we’ll use for the the magnifier’s background position.</p>
  5281.  
  5282.  
  5283.  
  5284. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">​​// Get the css variables
  5285. ​​let root = window.getComputedStyle(document.documentElement);
  5286. ​​let magnifier_width = root.getPropertyValue("--magnifer-width");
  5287. ​​let magnifier_height = root.getPropertyValue("--magnifer-height");
  5288.  
  5289. let container = document.querySelector(".container");
  5290. let rect = container.getBoundingClientRect();
  5291. let x = (e.pageX - rect.left);
  5292. let y = (e.pageY - rect.top);
  5293.  
  5294. // Take page scrolling into account
  5295. x = x - window.pageXOffset;
  5296. y = y - window.pageYOffset;</code></pre>
  5297.  
  5298.  
  5299.  
  5300. <p>What we need is basically a <code>mousemove</code> event listener on the <code>.container</code>. Then, we will use the <code>event.pageX</code> or <code>event.pageY</code> property to get the X or Y coordinate of the mouse. But to get the <em>exact</em> relative position of the mouse on an element, we need to subtract the position of the parent element from the mouse position we get from the JavaScript above. A “simple” way to do this is to use <code>getBoundingClientRect()</code>, which returns the size of an element and its position relative to the viewport.</p>
  5301.  
  5302.  
  5303.  
  5304. <p class="is-style-explanation">Notice how I’m taking scrolling into account. If there is overflow, subtracting the window <code>pageX</code> and <code>pageY</code> offsets will ensure the effect runs as expected.</p>
  5305.  
  5306.  
  5307.  
  5308. <p>We will first create the magnifier div. Next, we will create a <code>mousemove</code> function and add it to the image container. In this function, we will give the magnifier a class attribute. We will also calculate the mouse position and give the magnifier the left and top values we calculated earlier.</p>
  5309.  
  5310.  
  5311.  
  5312. <p>Let’s go ahead and build the <code>magnifier</code> when we hear a <code>mousemove</code> event on the <code>.container</code>:</p>
  5313.  
  5314.  
  5315.  
  5316. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">// create the magnifier
  5317. let magnifier = document.createElement("div");
  5318. container.append(magnifier);</code></pre>
  5319.  
  5320.  
  5321.  
  5322. <p>Now we need to make sure it has a class name we can scope to the CSS:</p>
  5323.  
  5324.  
  5325.  
  5326. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">// run the function on `mousemove`
  5327. container.addEventListener("mousemove", (e) => {
  5328.  magnifier.setAttribute("class", "magnifier");
  5329. }</code></pre>
  5330.  
  5331.  
  5332.  
  5333. <p>The example video I showed earlier positions the magnifier outside of the container. We’re gonna keep this simple and overlay it on top of the container instead as the mouse moves. We will use <code>if</code> statements to set the magnifier’s position only if the X and Y values are <em>greater</em> or <em>equal</em> to zero, and <em>less</em> than the container’s width or height. That should keep it in bounds. Just be sure to subtract the width and height of the magnifier from the X and Y values.</p>
  5334.  
  5335.  
  5336.  
  5337. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">// Run the function on mouse move.
  5338. container.addEventListener("mousemove", (e) => {
  5339.  magnifier.setAttribute("class", "magnifier");
  5340.  
  5341.  // Get mouse position
  5342.  let rect = container.getBoundingClientRect();
  5343.  let x = (e.pageX - rect.left);
  5344.  let y = (e.pageY - rect.top);
  5345.  
  5346.  // Take page scrolling into account
  5347.  x = x - window.pageXOffset;
  5348.  y = y - window.pageYOffset;
  5349.  
  5350.  // Prevent magnifier from exiting the container
  5351.  // Then set top and left values of magnifier
  5352.  if (x >= 0 &amp;&amp; x &lt;= container.clientWidth - magnifier_width) {
  5353.    magnifier.style.left = x + "px";
  5354.  }
  5355.  if (y >= 0 &amp;&amp; y &lt;= container.clientHeight - magnifier_height) {
  5356.    magnifier.style.top = y + "px";
  5357.  }
  5358. });</code></pre>
  5359.  
  5360.  
  5361.  
  5362. <p>Last, but certainly not least… we need to play with the magnifier’s background image a bit. The whole point is that the user gets a BIGGER view of the background image based on where the hover is taking place. So, let’s define a magnifier we can use to scale things up. Then we’ll define variables for the background image’s width and height so we have something to base that scale on, and set all of those values on the <code>.magnifier</code> styles:</p>
  5363.  
  5364.  
  5365.  
  5366. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">// Magnifier image configurations
  5367. let magnify = 2;
  5368. let imgWidth = 500;
  5369. let imgHeight = 400;
  5370.  
  5371. magnifier.style.backgroundSize = imgWidth * magnify + "px " + imgHeight * magnify + "px";</code></pre>
  5372.  
  5373.  
  5374.  
  5375. <p>​​Let’s take the X and Y coordinates of the magnifier’s image and apply them to the&nbsp;<code>.magnifier</code>​&nbsp;element’s&nbsp;<code>background-position</code>​. As before with the magnifier position, we need to subtract the width and height of the magnifier from the X and Y values using the CSS variables.</p>
  5376.  
  5377.  
  5378.  
  5379. <pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">// the x and y positions of the magnifier image
  5380. let magnify_x = x * magnify + 15;
  5381. let magnify_y = y * magnify + 15;
  5382.  
  5383. // set backgroundPosition for magnifier if it is within image
  5384. if (
  5385.  x &lt;= container.clientWidth - magnifier_width &amp;&amp;
  5386.  y &lt;= container.clientHeight - magnifier_height
  5387. ) {
  5388.  magnifier.style.backgroundPosition = -magnify_x + "px " + -magnify_y + "px";
  5389. }</code></pre>
  5390.  
  5391.  
  5392.  
  5393. <p>Tada!</p>
  5394.  
  5395.  
  5396.  
  5397. <div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_WNKzJQg" src="//codepen.io/anon/embed/WNKzJQg?height=450&amp;theme-id=1&amp;slug-hash=WNKzJQg&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed WNKzJQg" title="CodePen Embed WNKzJQg" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>
  5398.  
  5399.  
  5400. <h3 class="wp-block-heading" id="make-it-cinematic">Make it cinematic</h3>
  5401.  
  5402.  
  5403. <p>Have you seen the <a href="https://en.wikipedia.org/wiki/Ken_Burns_effect" rel="noopener">Ken Burns effect</a>? It’s classic and timeless thing where an image is bigger than the container it’s in, then sorta slides and scales slow as a slug. Just about every documentary film in the world seems to use it for image stills. If you have an Apple TV, then you’ve certainly seen it on the screen saver.</p>
  5404.  
  5405.  
  5406.  
  5407. <p>There are <em>plenty</em> of <a href="https://codepen.io/search/pens?q=ken+burns+effect" rel="noopener">examples over at CodePen</a> if you wanna get a better idea.</p>
  5408.  
  5409.  
  5410.  
  5411. <p>You’ll see that there are a number of ways to approach this. Some use JavaScript. Others are 100% CSS. I’m sure the JavaScript approaches are good for some uses cases, but if the goal is simply to subtly scale the image, CSS is perfectly suitable.</p>
  5412.  
  5413.  
  5414.  
  5415. <div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_WNKJjXb" src="//codepen.io/anon/embed/WNKJjXb?height=450&amp;theme-id=1&amp;slug-hash=WNKJjXb&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed WNKJjXb" title="CodePen Embed WNKJjXb" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>
  5416.  
  5417.  
  5418.  
  5419. <p>We could spice things up a bit using multiple backgrounds rather than one. Or, better yet, if we expand the rules to use elements instead of background images, we can apply the same animation to all of the backgrounds and use a dash of <code>animation-delay</code> to stagger the effect.</p>
  5420.  
  5421.  
  5422.  
  5423. <div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_KKBRmxg" src="//codepen.io/anon/embed/KKBRmxg?height=450&amp;theme-id=1&amp;slug-hash=KKBRmxg&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed KKBRmxg" title="CodePen Embed KKBRmxg" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>
  5424.  
  5425.  
  5426.  
  5427. <p>Lots of ways to do this, of course! It can certainly be optimized with Sass and/or CSS variables. Heck, maybe you can pull it off with a single <code>&lt;div&gt;</code> If so, share it in the comments!</p>
  5428.  
  5429.  
  5430. <h3 class="wp-block-heading" id="bonus-make-it-immersive">Bonus: Make it immersive</h3>
  5431.  
  5432.  
  5433. <p>I don’t know if anything is cooler than Sarah Drasner’s <a href="https://codepen.io/sdras/pen/rMEdjB" rel="noopener">&#8220;Happy Halloween” pen</a>… and that’s from 2016! It is a great example that layers backgrounds and moves them at varying speeds to create an almost cinematic experience. Good gosh is that cool!</p>
  5434.  
  5435.  
  5436.  
  5437. <p>GSAP is the main driver there, but I imagine we could make a boiled-down version that simply translates each background layer from left to right at different speeds. Not as cool, of course, but certainly the baseline experience. Gotta make sure the start and end of each background image is consistent so it repeats seamlessly when the animation repeats.</p>
  5438.  
  5439.  
  5440.  
  5441. <hr class="wp-block-separator has-alpha-channel-opacity"/>
  5442.  
  5443.  
  5444.  
  5445. <p>That’s it for now! Pretty neat that we can use backgrounds for much more than texture and contrast. I’m absolutely positive there are tons of other clever interactions we can apply to backgrounds. Temani Afif did exactly that with <a href="https://css-tricks.com/cool-hover-effects-using-background-properties/">a bunch of neat hover effects for links</a>. What do you have in mind? Share it with me in the comments!</p>
  5446. <hr />
  5447. <p><small><a rel="nofollow" href="https://css-tricks.com/moving-backgrounds/">Moving Backgrounds</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
  5448. ]]></content:encoded>
  5449. <wfw:commentRss>https://css-tricks.com/moving-backgrounds/feed/</wfw:commentRss>
  5450. <slash:comments>1</slash:comments>
  5451. <post-id xmlns="com-wordpress:feed-additions:1">376723</post-id> </item>
  5452. </channel>
  5453. </rss>
  5454.  
  5455. <!-- plugin=object-cache-pro client=phpredis metric#hits=10112 metric#misses=110 metric#hit-ratio=98.9 metric#bytes=6981574 metric#prefetches=374 metric#store-reads=105 metric#store-writes=2 metric#store-hits=381 metric#store-misses=107 metric#sql-queries=3 metric#ms-total=579.13 metric#ms-cache=55.46 metric#ms-cache-avg=0.5232 metric#ms-cache-ratio=9.6 -->
  5456.  
Copyright © 2002-9 Sam Ruby, Mark Pilgrim, Joseph Walton, and Phil Ringnalda