<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Mads Stoumann</title>
    <description>The latest articles on Forem by Mads Stoumann (@madsstoumann).</description>
    <link>https://forem.com/madsstoumann</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F515396%2Fbf9dcb06-ff90-4fb1-b507-c3dc21c82b85.png</url>
      <title>Forem: Mads Stoumann</title>
      <link>https://forem.com/madsstoumann</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/madsstoumann"/>
    <language>en</language>
    <item>
      <title>Re-creating a Pantone Color Deck in CSS</title>
      <dc:creator>Mads Stoumann</dc:creator>
      <pubDate>Wed, 11 Feb 2026 13:05:10 +0000</pubDate>
      <link>https://forem.com/madsstoumann/re-creating-a-pantone-color-deck-in-css-3108</link>
      <guid>https://forem.com/madsstoumann/re-creating-a-pantone-color-deck-in-css-3108</guid>
      <description>&lt;p&gt;If you’ve ever held a Pantone fan deck — the kind graphic designers used to carry around like a sacred artifact — you know the satisfying way those cards fan out from a single rivet point. Each card swings on its own arc, and you flip through the colors by hand.&lt;/p&gt;

&lt;p&gt;I wanted to recreate that experience for this week’s CodePen challenge, which is all about color palettes. &lt;/p&gt;

&lt;p&gt;Follow along as we build a fully interactive color fan deck where the spread adapts to the container width, cards know their position among their siblings, and clicking a card to "focus" it is handled entirely by the browser’s native &lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt; element. &lt;/p&gt;

&lt;p&gt;No JavaScript! Let’s dive in!&lt;/p&gt;




&lt;h2&gt;
  
  
  The Markup
&lt;/h2&gt;

&lt;p&gt;Our fan deck is a &lt;code&gt;&amp;lt;section&amp;gt;&lt;/code&gt; containing a cover card, followed by color cards, each wrapped in a &lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt; element:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;section&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- cover card --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;details&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"deck"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;summary&amp;gt;&lt;/span&gt;Reds &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;×&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&amp;lt;/summary&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"--c: lab(45% 67 30)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;strong&amp;gt;&lt;/span&gt;Poppy Red&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;dl&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;dt&amp;gt;&lt;/span&gt;HEX&lt;span class="nt"&gt;&amp;lt;/dt&amp;gt;&amp;lt;dd&amp;gt;&lt;/span&gt;#DC3D4C&lt;span class="nt"&gt;&amp;lt;/dd&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;dt&amp;gt;&lt;/span&gt;RGB&lt;span class="nt"&gt;&amp;lt;/dt&amp;gt;&amp;lt;dd&amp;gt;&lt;/span&gt;220, 61, 76&lt;span class="nt"&gt;&amp;lt;/dd&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;dt&amp;gt;&lt;/span&gt;LAB&lt;span class="nt"&gt;&amp;lt;/dt&amp;gt;&amp;lt;dd&amp;gt;&lt;/span&gt;45, 56, 25&lt;span class="nt"&gt;&amp;lt;/dd&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/dl&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class="c"&gt;&amp;lt;!-- more colors --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/details&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;details&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"deck"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;summary&amp;gt;&lt;/span&gt;Blues &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;×&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&amp;lt;/summary&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/details&amp;gt;&lt;/span&gt;

  &lt;span class="c"&gt;&amp;lt;!-- more cards --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things to note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;cover card&lt;/strong&gt; doesn’t toggle — it just sits at the front of the deck.&lt;/li&gt;
&lt;li&gt;Each &lt;strong&gt;color card&lt;/strong&gt; is a &lt;code&gt;&amp;lt;details name="deck"&amp;gt;&lt;/code&gt; element. The &lt;code&gt;name&lt;/code&gt; attribute is the key — it makes them an &lt;em&gt;exclusive accordion&lt;/em&gt;. Only one can be open at a time, and clicking the open one closes it.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;&amp;lt;summary&amp;gt;&lt;/code&gt; serves as both the card label and the click target.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not much to see yet. Let’s add some CSS:&lt;/p&gt;

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

&lt;p&gt;I won’t go into the CSS in depth here; it’s simply a &lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt; with the color values defined in a &lt;code&gt;&amp;lt;dl&amp;gt;&lt;/code&gt; and wrapped up in a grid.&lt;/p&gt;




&lt;h2&gt;
  
  
  Stacking the Deck
&lt;/h2&gt;

&lt;p&gt;First, we need all cards to occupy the same grid cell, stacked on top of each other:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;section&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;container-type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inline-size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;place-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;section&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;grid-area&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;z-index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sibling-count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="n"&gt;sibling-index&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Setting &lt;code&gt;container-type: inline-size&lt;/code&gt; on the &lt;code&gt;&amp;lt;section&amp;gt;&lt;/code&gt; lets us use &lt;strong&gt;container query units&lt;/strong&gt; later. Every direct child is placed in the same grid cell with &lt;code&gt;grid-area: 1 / -1&lt;/code&gt;, creating a stack.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;z-index&lt;/code&gt; line uses two new CSS functions — &lt;code&gt;sibling-count()&lt;/code&gt; and &lt;code&gt;sibling-index()&lt;/code&gt; — to ensure the first card sits on top. The first child has &lt;code&gt;sibling-index()&lt;/code&gt; of 1, so it gets the highest &lt;code&gt;z-index&lt;/code&gt;. The last child gets 1. Natural stacking order — no hardcoded values, no counters, no JavaScript.&lt;/p&gt;

&lt;p&gt;So, for now, we just see the cover card — the color cards are hidden behind it (the rivet is an &lt;code&gt;::after&lt;/code&gt; pseudo-element with a &lt;code&gt;radial-gradient&lt;/code&gt;):&lt;/p&gt;

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




&lt;h2&gt;
  
  
  The Fan Spread with &lt;code&gt;progress()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;This is where it gets interesting. A real fan deck spreads wider when you have room, and collapses into a tight stack in a narrow space. We want the same behavior — and the new CSS &lt;code&gt;progress()&lt;/code&gt; function makes it elegant:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;section&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--spread&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="n"&gt;cqi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;300px&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1440px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--end-degree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--spread&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;45deg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--start-degree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--spread&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;-45deg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;progress()&lt;/code&gt;&lt;/strong&gt; returns a value between &lt;code&gt;0&lt;/code&gt; and &lt;code&gt;1&lt;/code&gt; based on where a value falls within a range. Here, &lt;code&gt;progress(100cqi, 300px, 1440px)&lt;/code&gt; asks: "How far is the container’s inline size between 300px and 1440px?"&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;At 300px or below: &lt;code&gt;--spread&lt;/code&gt; is &lt;code&gt;0&lt;/code&gt; — no fan, cards stacked flat.&lt;/li&gt;
&lt;li&gt;At 1440px or above: &lt;code&gt;--spread&lt;/code&gt; is &lt;code&gt;1&lt;/code&gt; — full fan, cards spanning from -45° to +45°.&lt;/li&gt;
&lt;li&gt;At 870px (midpoint): &lt;code&gt;--spread&lt;/code&gt; is &lt;code&gt;0.5&lt;/code&gt; — half fan.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No &lt;code&gt;@container&lt;/code&gt; queries, just one line of CSS, and the spread is &lt;em&gt;continuously responsive&lt;/em&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Positioning Each Card with &lt;code&gt;sibling-index()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Now each card needs its own rotation angle, interpolated between &lt;code&gt;--start-degree&lt;/code&gt; and &lt;code&gt;--end-degree&lt;/code&gt; based on its position in the deck:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;section&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--start-degree&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--end-degree&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--start-degree&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sibling-index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sibling-count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;transform-origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100%&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--rivet&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100%&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--rivet&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s break it down:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;sibling-index() - 1&lt;/code&gt; gives us a zero-based position (0 for first card, 1 for second, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sibling-count() - 1&lt;/code&gt; gives us the total number of "gaps" between cards&lt;/li&gt;
&lt;li&gt;Dividing them gives a progress value from &lt;code&gt;0&lt;/code&gt; to &lt;code&gt;1&lt;/code&gt; for each card’s position&lt;/li&gt;
&lt;li&gt;We multiply that by the degree range and add the start offset&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;code&gt;transform-origin&lt;/code&gt; is set to the bottom-right corner — offset by &lt;code&gt;--rivet&lt;/code&gt; — so all cards rotate around the same pivot point, just like a physical fan deck with a rivet pin.&lt;/p&gt;

&lt;p&gt;Cool! The cards now fan out from a single point, and the spread adjusts automatically with the container width, but they’re not interactive yet.&lt;/p&gt;

&lt;p&gt;Now we have:&lt;/p&gt;

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

&lt;p&gt;Let’s resize the browser:&lt;/p&gt;

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

&lt;p&gt;I find this incredibly satisfying!&lt;/p&gt;




&lt;h2&gt;
  
  
  Click-to-Focus with Exclusive &lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Here’s where the &lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt; element earns its place. By giving all color cards &lt;code&gt;name="deck"&lt;/code&gt;, the browser enforces &lt;em&gt;exclusive accordion&lt;/em&gt; behavior:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Click a card’s summary&lt;/strong&gt; → it opens (gets the &lt;code&gt;[open]&lt;/code&gt; attribute), any other open card closes automatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Click the same summary again&lt;/strong&gt; → it closes, returning to the default fan.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the &lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt; element normally &lt;em&gt;hides&lt;/em&gt; its content when closed. We want the color cards to always be visible — the open/closed state should only affect the card’s &lt;em&gt;rotation&lt;/em&gt;, not its content visibility. This is where the new &lt;code&gt;::details-content&lt;/code&gt; pseudo-element comes in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;details&lt;/span&gt;&lt;span class="nd"&gt;::details-content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;content-visibility&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;::details-content&lt;/code&gt; pseudo-element targets the content slot of a &lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt; — everything that isn’t the &lt;code&gt;&amp;lt;summary&amp;gt;&lt;/code&gt;. By overriding &lt;code&gt;content-visibility&lt;/code&gt; to &lt;code&gt;visible&lt;/code&gt; and setting &lt;code&gt;display: contents&lt;/code&gt;, the card’s color list is always rendered, regardless of the open state.&lt;/p&gt;

&lt;p&gt;Let’s see how it looks when we select a card:&lt;/p&gt;

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




&lt;h2&gt;
  
  
  CSS-Only State Detection
&lt;/h2&gt;

&lt;p&gt;When a card is open, we want three things to happen:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;strong&gt;active card&lt;/strong&gt; rotates to 0° (straight up)&lt;/li&gt;
&lt;li&gt;Cards &lt;strong&gt;before&lt;/strong&gt; it collapse toward the start&lt;/li&gt;
&lt;li&gt;Cards &lt;strong&gt;after&lt;/strong&gt; it push toward the end&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We need boolean-like flags — &lt;code&gt;0&lt;/code&gt; or &lt;code&gt;1&lt;/code&gt; — that each card can use in its rotation formula. And we can set them entirely with CSS selectors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* Any card is active */&lt;/span&gt;
&lt;span class="nt"&gt;section&lt;/span&gt;&lt;span class="nd"&gt;:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;details&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;open&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;--has-active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* Cards before the active one */&lt;/span&gt;
&lt;span class="nt"&gt;section&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nd"&gt;:has&lt;/span&gt;&lt;span class="o"&gt;(~&lt;/span&gt; &lt;span class="nt"&gt;details&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;open&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;--is-before&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* The active card itself */&lt;/span&gt;
&lt;span class="nt"&gt;details&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;open&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;--is-active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* Cards after the active one */&lt;/span&gt;
&lt;span class="nt"&gt;details&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;open&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;--is-after&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four selectors, four flags. Let’s unpack them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;section:has(details[open])&lt;/code&gt; matches the section &lt;em&gt;when any details child is open&lt;/em&gt;, then sets &lt;code&gt;--has-active: 1&lt;/code&gt; on all children.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;:has(~ details[open])&lt;/code&gt; matches any element that has a &lt;em&gt;subsequent sibling&lt;/em&gt; that is &lt;code&gt;details[open]&lt;/code&gt; — i.e., it comes &lt;em&gt;before&lt;/em&gt; the active card.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;details[open]&lt;/code&gt; matches the active card directly.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;details[open] ~ *&lt;/code&gt; matches all &lt;em&gt;subsequent siblings&lt;/em&gt; — the cards &lt;em&gt;after&lt;/em&gt; the active one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The defaults are all &lt;code&gt;0&lt;/code&gt;, set on the base &lt;code&gt;section &amp;gt; *&lt;/code&gt; rule. When no card is open, all flags are &lt;code&gt;0&lt;/code&gt;, and the cards fan normally.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Full Rotation Formula
&lt;/h3&gt;

&lt;p&gt;With the flags in place, the rotation formula handles all states:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;section&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--start-degree&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--end-degree&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--start-degree&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sibling-index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sibling-count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--is-active&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--is-before&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--end-degree&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--start-degree&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sibling-index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sibling-count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;0.85&lt;/span&gt;
    &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--is-after&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--end-degree&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--start-degree&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sibling-index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sibling-count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;0.85&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rotate&lt;/span&gt; &lt;span class="m"&gt;.25s&lt;/span&gt; &lt;span class="n"&gt;linear&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So what’s going on?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Line 1–2:&lt;/strong&gt; The normal fan rotation — the same formula from before.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;* (1 - var(--is-active))&lt;/code&gt;:&lt;/strong&gt; Multiplying by 0 when active zeroes out the rotation — the card snaps to 0°.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Before cards:&lt;/strong&gt; Subtract a value that pushes them further toward the start. The &lt;code&gt;0.85&lt;/code&gt; factor collapses them tightly but not completely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;After cards:&lt;/strong&gt; Add a value that pushes them further toward the end, using the &lt;em&gt;inverse&lt;/em&gt; position &lt;code&gt;(1 - progress)&lt;/code&gt; so they fan toward the opposite edge.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;transition&lt;/code&gt; gives it a smooth, satisfying swing.&lt;/p&gt;




&lt;h2&gt;
  
  
  The New CSS Features — a Recap
&lt;/h2&gt;

&lt;p&gt;This component leans on several CSS features that are all relatively new — so use a modern browser.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;What It Does Here&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;progress()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Returns 0–1 based on container width, driving the fan spread&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sibling-index()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Each card knows its position — used for rotation and z-index&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sibling-count()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Total number of cards — used to normalize position to 0–1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;details name=""&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Exclusive accordion — click to open/close, only one active&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;::details-content&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Override content visibility so cards always show their colors&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;I’m constantly blown away by how far CSS has progressed — and is progressing. What I find exciting is how these new features &lt;em&gt;compose&lt;/em&gt;. None of them alone are revolutionary — but &lt;code&gt;progress()&lt;/code&gt; feeding into &lt;code&gt;sibling-index()&lt;/code&gt;-driven rotation, toggled by native &lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt; state detected via &lt;code&gt;:has()&lt;/code&gt; selectors, all without a single line of JavaScript.&lt;/p&gt;

&lt;p&gt;Here’s a CodePen demo. I urge you to open it full-screen, resize, click etc.:&lt;/p&gt;

&lt;p&gt;

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


&lt;/p&gt;

</description>
      <category>html</category>
      <category>css</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Creating a Maintenance Page using a Cloudflare Worker</title>
      <dc:creator>Mads Stoumann</dc:creator>
      <pubDate>Fri, 31 Oct 2025 10:52:36 +0000</pubDate>
      <link>https://forem.com/madsstoumann/creating-a-maintenance-page-using-a-cloudflare-worker-8m6</link>
      <guid>https://forem.com/madsstoumann/creating-a-maintenance-page-using-a-cloudflare-worker-8m6</guid>
      <description>&lt;p&gt;Last night, an Azure outage took down several sites my company maintains. While the servers were running, a critical downstream service — the headless CMS, Umbraco Cloud — was down. The result? Users saw a blank page. They did what any of us would do: they refreshed, and refreshed again. This flood of requests hammered the services, even though the root cause was elsewhere.&lt;/p&gt;

&lt;p&gt;When a site's availability depends on other services, it is vulnerable to their downtime. A simple, robust maintenance page served from the edge is a cost-effective solution to this problem. It provides a clear, calm message to users, letting them know we're aware of the issue and working on it. This — hopefully! — stops the frantic refreshing that can overload the infrastructure.&lt;/p&gt;

&lt;p&gt;This article will guide you through creating a maintenance worker using Cloudflare Workers. It's a "break glass in case of emergency" tool that can be activated in seconds when a site is in trouble, ensuring users are never left staring at a blank screen again.&lt;/p&gt;

&lt;p&gt;We'll start with ...&lt;/p&gt;

&lt;h2&gt;
  
  
  The Basics
&lt;/h2&gt;

&lt;p&gt;In your IDE of choice, create a simple HTML page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en-US"&lt;/span&gt; &lt;span class="na"&gt;dir=&lt;/span&gt;&lt;span class="s"&gt;"ltr"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Site Maintenance&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1, viewport-fit=cover"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"description"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"Site is under maintenance"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"view-transition"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"same-origin"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Canvas&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;CanvasText&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="py"&gt;color-scheme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;light&lt;/span&gt; &lt;span class="n"&gt;dark&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;system-ui&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="py"&gt;max-inline-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1024px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0ch&lt;/span&gt; &lt;span class="m"&gt;4ch&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;clamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2em&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;5vw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;4em&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nl"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;clamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1em&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2.5vw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1.5em&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;We'll Be Right Back&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;The site is currently undergoing maintenance. Please check back later.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt; 
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, in the Cloudflare Dashboard:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;code&gt;Compute &amp;amp; AI&lt;/code&gt; &amp;gt; &lt;code&gt;Workers &amp;amp; Pages&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Click on &lt;code&gt;Create application&lt;/code&gt;, and just pick the "Hello world" template.&lt;/li&gt;
&lt;li&gt;Go to the new application, and replace the script with:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Get the client's IP address&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;clientIP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CF-Connecting-IP&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Define the rate limit key based on the client's IP&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rateLimitKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`ratelimit:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;clientIP&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Check rate limit in KV&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RATE_LIMIT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Fetch the current request count for the IP from KV&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RATE_LIMIT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rateLimitKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requestCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// If rate limit exceeded (60 requests in a minute)&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requestCount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Too many requests. Please try again later.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/plain&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Retry-After&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;60&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="c1"&gt;// Increment the request count and set expiration to 1 minute&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RATE_LIMIT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rateLimitKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requestCount&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;expirationTtl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Maintenance page HTML&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;maintenanceHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang="en-US" dir="ltr"&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;title&amp;gt;Site Maintenance&amp;lt;/title&amp;gt;
  &amp;lt;meta charset="UTF-8"&amp;gt;
  &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"&amp;gt;
  &amp;lt;meta name="description" content="Site is under maintenance"&amp;gt;
  &amp;lt;meta name="view-transition" content="same-origin"&amp;gt;
  &amp;lt;style&amp;gt;
    body {
      background: Canvas;
      color: CanvasText;
      color-scheme: light dark;
      font-family: system-ui;
      line-height: 1.6;
      margin: 0 auto;
      max-inline-size: 1024px;
      padding: 0ch 4ch
    }
    h1 {
      font-size: clamp(2em, 5vw, 4em);
      line-height: 1.1;
    }
    p {
      font-size: clamp(1em, 2.5vw, 1.5em);
    }
  &amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;h1&amp;gt;We'll Be Right Back&amp;lt;/h1&amp;gt;
  &amp;lt;p&amp;gt;The site is currently undergoing maintenance. Please check back later.&amp;lt;/p&amp;gt; 
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Return the maintenance page immediately (no origin fetch)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;maintenanceHTML&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/html;charset=UTF-8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cache-Control&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no-store, no-cache, must-revalidate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Retry-After&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;3600&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Rate Limiting
&lt;/h2&gt;

&lt;p&gt;We'll limit how many requests a single origin can make to our maintenance page.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;code&gt;Storage &amp;amp; Databases&lt;/code&gt;, then &lt;code&gt;Workers KV&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Click on &lt;code&gt;Create Instance&lt;/code&gt;, call the namespace "RATE_LIMIT"&lt;/li&gt;
&lt;li&gt;Next, go to &lt;code&gt;Workers &amp;amp; Pages&lt;/code&gt;, then click on your maintenance worker.&lt;/li&gt;
&lt;li&gt;Click on &lt;code&gt;Bindings&lt;/code&gt;, then &lt;code&gt;Add binding&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Click on &lt;code&gt;KV namespace&lt;/code&gt;, then on &lt;code&gt;Add Binding&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Call your variable &lt;code&gt;RATE_LIMIT&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;p&gt;Now, the worker won't run unless we add a &lt;strong&gt;route&lt;/strong&gt;. This is something we only want to do when the site is actually failing.&lt;/p&gt;

&lt;p&gt;Go to your domain in Cloudflare, and select &lt;code&gt;Workers Routes&lt;/code&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click &lt;code&gt;Add route&lt;/code&gt; and fill out &lt;code&gt;route&lt;/code&gt; — most likely your root domain followed by an asterisk, for example: &lt;code&gt;browser.style/*&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Select your maintenance worker from the &lt;code&gt;Worker&lt;/code&gt; dropdown&lt;/li&gt;
&lt;li&gt;Save, and after a minute or less, go to your domain. &lt;/li&gt;
&lt;li&gt;Do you see the maintenance page?&lt;/li&gt;
&lt;/ol&gt;

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




&lt;p&gt;Now, &lt;strong&gt;remove&lt;/strong&gt; the route — and next time a service is down and your site fails, re-enable the route.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Not Just Update DNS?
&lt;/h2&gt;

&lt;p&gt;You might wonder why you can't just point your DNS records to a different server with a maintenance page. The primary reason is time. DNS changes can take hours to propagate across the globe, as different DNS servers cache records for varying amounts of time. During a critical outage, you need a solution that works in seconds, not hours.&lt;/p&gt;

&lt;p&gt;A Cloudflare Worker, on the other hand, is deployed to Cloudflare's entire global network almost instantly. When you enable the route, the change is reflected worldwide within moments, ensuring all users see the maintenance page immediately. This makes it a far superior solution for rapid response during an incident.&lt;/p&gt;




&lt;h2&gt;
  
  
  Other Services
&lt;/h2&gt;

&lt;p&gt;While this article focuses on Cloudflare, other services offer similar edge computing capabilities that could be used to deploy a maintenance page. I haven't personally tested these, but they operate on similar principles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;AWS:&lt;/strong&gt; Lambda@Edge and CloudFront Functions&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Fastly:&lt;/strong&gt; Compute@Edge&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Vercel &amp;amp; Netlify:&lt;/strong&gt; Edge Functions&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Akamai:&lt;/strong&gt; EdgeWorkers&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;a href="https://www.pexels.com/da-dk/foto/1148820/" rel="noopener noreferrer"&gt;Photo by Panumas Nikhomkhai, Pexels.&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>security</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Adding Security Headers on Cloudflare</title>
      <dc:creator>Mads Stoumann</dc:creator>
      <pubDate>Thu, 30 Oct 2025 07:54:30 +0000</pubDate>
      <link>https://forem.com/madsstoumann/adding-security-headers-on-cloudflare-2dag</link>
      <guid>https://forem.com/madsstoumann/adding-security-headers-on-cloudflare-2dag</guid>
      <description>&lt;p&gt;Four years ago, I wrote a posts about &lt;a href="https://dev.to/madsstoumann/frontend-security-security-headers-517p"&gt;Security Headers&lt;/a&gt;. In that post, I mentioned how to set up Security Headers on Cloudflare using a Worker.&lt;/p&gt;

&lt;p&gt;Since then, a lot has happened. Nowadays, you can add &lt;code&gt;Transform Rules&lt;/code&gt;, which will save you Worker Requests. So — to keep this short — this is how I handle Security Headers in Cloudflare now:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Got to &lt;code&gt;Rules&lt;/code&gt; &amp;gt; &lt;code&gt;Overview&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Click on "Create Rule"&lt;/li&gt;
&lt;li&gt;Select &lt;code&gt;Response Header Transform Rule&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Fill out &lt;code&gt;Rule name&lt;/code&gt; — I simply call it "Security Headers"&lt;/li&gt;
&lt;li&gt;Select &lt;code&gt;All incoming requests&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Then, from &lt;code&gt;Select item&lt;/code&gt;, chose &lt;code&gt;Set static&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Next, set &lt;code&gt;Header name&lt;/code&gt; and &lt;code&gt;value&lt;/code&gt; for the headers you want to add.&lt;/p&gt;

&lt;p&gt;In my case, it's:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Header Name&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Content-Security-Policy&lt;/td&gt;
&lt;td&gt;upgrade-insecure-requests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pemissions-Policy&lt;/td&gt;
&lt;td&gt;geolocation=(), microphone=(self)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Referrer-Policy&lt;/td&gt;
&lt;td&gt;strict-origin-when-cross-origin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;X-Frame-Options&lt;/td&gt;
&lt;td&gt;DENY&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;A Note on Content Security Policy (CSP)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You might notice my global CSP is quite minimal - it only includes &lt;code&gt;upgrade-insecure-requests&lt;/code&gt;. This is intentional. Since my site, browser.style, is a code-sharing platform where different pages have different security requirements, I handle Content Security Policy on a &lt;strong&gt;per-page basis&lt;/strong&gt; using the &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; tag in the HTML &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Remove Headers
&lt;/h2&gt;

&lt;p&gt;Now, there's also a lot of headers you want to &lt;em&gt;remove&lt;/em&gt;. These headers leak information about your infrastructure and internal systems, which isn't necessarily a security vulnerability, but follows the principle of "don't advertise what you don't need to." For these, I've created &lt;em&gt;another&lt;/em&gt; Transform Rule. Instead of &lt;code&gt;Set static&lt;/code&gt;, select &lt;code&gt;Remove&lt;/code&gt;, and repeat for each of these:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Headers to Remove:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;via&lt;/code&gt;&lt;/strong&gt; - Reveals intermediary proxy servers and CDN infrastructure in your request chain (e.g., "1.1 varnish"). This tells attackers which proxy software you're using and potential version information.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;x-cache-hits&lt;/code&gt;&lt;/strong&gt; - Shows cache statistics, revealing how many times content was served from cache. This is internal performance data that visitors don't need to see.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;x-fastly-request-id&lt;/code&gt;&lt;/strong&gt; - Exposes that you're using Fastly CDN and provides an internal request tracking ID. While not directly exploitable, it reveals your infrastructure stack.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;x-github-request-id&lt;/code&gt;&lt;/strong&gt; - If you're using GitHub Pages (like I am), this header reveals both that fact and GitHub's internal request tracking ID.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;x-origin-cache&lt;/code&gt;&lt;/strong&gt; - Reveals your origin server's caching status (HIT/MISS), which is internal infrastructure information.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;x-proxy-cache&lt;/code&gt;&lt;/strong&gt; - Similar to above, shows proxy layer caching behavior (HIT/MISS/BYPASS), revealing details about your caching architecture.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;x-timer&lt;/code&gt;&lt;/strong&gt; - Contains internal timing and performance metrics from your CDN, formatted as &lt;code&gt;S{timestamp},{metrics}&lt;/code&gt;. This is debugging information that shouldn't be public.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why remove these?&lt;/strong&gt; While these headers aren't direct security vulnerabilities, they provide reconnaissance information that attackers can use to fingerprint your infrastructure. Security through obscurity isn't a primary defense, but there's no reason to advertise your complete technology stack to everyone who makes a request.&lt;/p&gt;

&lt;p&gt;Give the rule a name, I simply call it "Remove Headers".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Cloudflare may prevent you from removing certain headers like &lt;code&gt;x-cache&lt;/code&gt; and &lt;code&gt;x-served-by&lt;/code&gt; as they're protected for debugging purposes. That's normal and expected.&lt;/p&gt;




&lt;p&gt;Finally, check your headers via &lt;code&gt;curl -I https://your.domain&lt;/code&gt; or go to &lt;a href="https://securityheaders.com/" rel="noopener noreferrer"&gt;securityheaders.com&lt;/a&gt; — Go for &lt;strong&gt;A+&lt;/strong&gt; ;-)&lt;/p&gt;

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

</description>
      <category>security</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Weather Widgets</title>
      <dc:creator>Mads Stoumann</dc:creator>
      <pubDate>Fri, 24 Oct 2025 11:33:15 +0000</pubDate>
      <link>https://forem.com/madsstoumann/weather-widgets-4751</link>
      <guid>https://forem.com/madsstoumann/weather-widgets-4751</guid>
      <description>&lt;p&gt;Everyone talks about it — but no one does anything about it. What? The weather, of course! So, at this year's &lt;em&gt;Developer Day&lt;/em&gt; at AKQA Denmark, we decided to do something about it.&lt;/p&gt;

&lt;p&gt;Developers were asked to use AI to create movie-inspired weather widgets. The results were amazing! A Ghostbuster-themed New York forecast; the weather in Rome set in a Gladiator-inspired arena; a futuristic, gloomy Bladerunner L.A. forecast. The exercise was fun for both backend- and frontend developers alike, using Claude Code, Vercel v0 and more.&lt;/p&gt;

&lt;p&gt;Afterwards, I decided to recreate the &lt;strong&gt;Apple Weather Widgets&lt;/strong&gt; using a single baseline weather widget web component. Most of the widgets are using &lt;strong&gt;other&lt;/strong&gt; components I've created recently (and written about here on dev.to), so there wasn't that much to code!&lt;/p&gt;

&lt;h2&gt;
  
  
  Data
&lt;/h2&gt;

&lt;p&gt;Data is coming from WeatherAPI.com. They have a bunch of APIs, all with a generous free tier. For this project, I used the &lt;strong&gt;forecast API&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;weatherAPI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//api.weatherapi.com/v1/&lt;/span&gt;
  &lt;span class="nx"&gt;forecast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
  &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WEATHER_API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;
  &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;)};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;weatherResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;weatherAPI&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since I also use data from OpenStreetMap (with local country- and city-names), I created a Cloudflare Worker (using the KV store for API keys), filtering the merged data to only return what I need.&lt;/p&gt;

&lt;p&gt;For these demos however, in order not to spend my free API calls at WeatherAPI, we use a static JSON file.&lt;/p&gt;




&lt;h2&gt;
  
  
  Baseline
&lt;/h2&gt;

&lt;p&gt;The baseline component is mostly handling locales and units. In the weather-data we get both celsius, fahrenheit, miles, kilometers etc., so the component is handling &lt;code&gt;lang&lt;/code&gt; and &lt;code&gt;locale&lt;/code&gt; for us, so the user — depending on these — see the correct values.&lt;/p&gt;




&lt;h2&gt;
  
  
  Widgets
&lt;/h2&gt;

&lt;p&gt;But let's take a look at the individual widgets — there are 13 of them! &lt;strong&gt;Note&lt;/strong&gt;, that most of the widgets are using container queries, changing layout depending on their width.&lt;/p&gt;




&lt;h3&gt;
  
  
  Feels Like
&lt;/h3&gt;

&lt;p&gt;This is one of the simplest widgets. It just shows what the temperature feels like and a short description. It uses a "unit-switcher" — °C | °F — from the baseline component:&lt;/p&gt;

&lt;p&gt;

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


&lt;/p&gt;




&lt;h3&gt;
  
  
  Forecast Days
&lt;/h3&gt;

&lt;p&gt;This is the classic forecast widget we know from all other weather widgets. The icons are from &lt;code&gt;cdn.weatherwidgets.com&lt;/code&gt;. For the wind direction, I use a simple arrow (svg), and then rotate it based on numbers I get from WeatherAPI:&lt;/p&gt;

&lt;p&gt;

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


&lt;/p&gt;




&lt;h3&gt;
  
  
  Forecast Hours
&lt;/h3&gt;

&lt;p&gt;Similar to &lt;em&gt;Forecast Days&lt;/em&gt;, this one also use the icons from &lt;code&gt;cdn.weatherwidgets.com&lt;/code&gt; and a 24-step hour-by-hour forecast:&lt;/p&gt;

&lt;p&gt;

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


&lt;/p&gt;




&lt;h3&gt;
  
  
  Humidity
&lt;/h3&gt;

&lt;p&gt;This one is just as simple as &lt;em&gt;Feels Like&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;

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


&lt;/p&gt;




&lt;h3&gt;
  
  
  Moon Phase
&lt;/h3&gt;

&lt;p&gt;This one was easy for me, because I recently made &lt;a href="https://dev.to/madsstoumann/phases-of-the-moon-in-css-2lbo"&gt;Phases of the Moon&lt;/a&gt; in CSS, so it was just a few tweaks and copy/paste:&lt;/p&gt;

&lt;p&gt;

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


&lt;/p&gt;




&lt;h3&gt;
  
  
  Precipitation
&lt;/h3&gt;

&lt;p&gt;This one is using my CSS-only charts component, which I &lt;a href="https://dev.to/madsstoumann/charts-in-css-1di1"&gt;wrote about here&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;

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


&lt;/p&gt;




&lt;h3&gt;
  
  
  Temperature
&lt;/h3&gt;

&lt;p&gt;Same as the previous one, using column chart:&lt;/p&gt;

&lt;p&gt;

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


&lt;/p&gt;




&lt;h3&gt;
  
  
  Pressure
&lt;/h3&gt;

&lt;p&gt;This one is using my &lt;a href="https://dev.to/madsstoumann/how-to-create-gauges-in-css-3581"&gt;CSS Gauges&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;

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


&lt;/p&gt;




&lt;h3&gt;
  
  
  Sunphase
&lt;/h3&gt;

&lt;p&gt;This is the only one that actually required coding-from-scratch! I made a dedicated &lt;a href="https://browser.style/ui/sun-phase/" rel="noopener noreferrer"&gt;sun-phase component&lt;/a&gt; that is imported in the weather widget. It's a ton of complex SVG-based calculations, check the source code if you want to go down a rabbit-hole:&lt;/p&gt;

&lt;p&gt;

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


&lt;/p&gt;




&lt;h3&gt;
  
  
  UV
&lt;/h3&gt;

&lt;p&gt;Next up is UV — should you wear sunscreen or not? It's an &lt;code&gt;&amp;lt;output&amp;gt;&lt;/code&gt; styled as a slider, with the thumb indicating the current UV index:&lt;/p&gt;

&lt;p&gt;

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


&lt;/p&gt;




&lt;h3&gt;
  
  
  Visibility
&lt;/h3&gt;

&lt;p&gt;We're almost there — only a few left! This one is just as simple as &lt;em&gt;Feels Like:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;

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


&lt;/p&gt;




&lt;h3&gt;
  
  
  Wind
&lt;/h3&gt;

&lt;p&gt;This one is using a lot of the same techniques as I wrote about in &lt;a href="https://dev.to/madsstoumann/clocks-and-watches-in-css-36mm"&gt;Clocks and Watches in CSS&lt;/a&gt;. I made a dedicated &lt;a href="https://browser.style/ui/nav-compass/" rel="noopener noreferrer"&gt;nav-compass component&lt;/a&gt;, which is imported into the wind widget:&lt;/p&gt;

&lt;p&gt;

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


&lt;/p&gt;




&lt;h3&gt;
  
  
  Overview
&lt;/h3&gt;

&lt;p&gt;The final widget, is the one you see on a lot of news-sites and dashboard: The all-in-one city-based overview component:&lt;/p&gt;

&lt;p&gt;

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


&lt;/p&gt;




&lt;h2&gt;
  
  
  An even greater overview
&lt;/h2&gt;

&lt;p&gt;I love Apple's Weather Widgets - and on MacOS you get a complete overview including &lt;strong&gt;all&lt;/strong&gt; the widgets. I had to do that one as well — and it's here, you can see the power of container queries, as the widgets "morph" themselves depending on screen width.&lt;/p&gt;

&lt;p&gt;Container queries allow each widget to respond to its own width rather than the viewport width. This means a widget can be compact when placed in a narrow column, showing just essential data, then automatically expand to reveal charts, graphs, and additional details when given more space. This self-contained responsiveness is what makes the widgets truly reusable across different layouts.&lt;/p&gt;

&lt;p&gt;I urge you to open this one in a new window and resize your browser!&lt;/p&gt;

&lt;p&gt;

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


&lt;/p&gt;




&lt;p&gt;So there you have it: &lt;strong&gt;13 weather widgets&lt;/strong&gt; and an overview widget. &lt;/p&gt;

&lt;p&gt;I don't think I've ever written a post with so many Codepen demos!&lt;/p&gt;

&lt;p&gt;To make it even easier for you, I've gathered all the widgets in a &lt;a href="https://codepen.io/collection/EPgkee" rel="noopener noreferrer"&gt;collection at Codepen&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>css</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Piano Chords in CSS</title>
      <dc:creator>Mads Stoumann</dc:creator>
      <pubDate>Fri, 25 Jul 2025 15:05:46 +0000</pubDate>
      <link>https://forem.com/madsstoumann/piano-chords-in-css-4jop</link>
      <guid>https://forem.com/madsstoumann/piano-chords-in-css-4jop</guid>
      <description>&lt;p&gt;A couple of months ago, I wrote an article about &lt;a href="https://dev.to/madsstoumann/guitar-chords-in-css-3hk8"&gt;Guitar Chords in CSS&lt;/a&gt;, using the new typed &lt;code&gt;attr()&lt;/code&gt; method in CSS. I didn't plan to write a follow-up, but then I saw &lt;a href="https://detkuloerteudvalg.dk/?s=klaverakkorder&amp;amp;post_type=product" rel="noopener noreferrer"&gt;these beautiful posters&lt;/a&gt; and wanted to do something similar. It turned out to be &lt;em&gt;way&lt;/em&gt; more complicated than guitar chords!&lt;/p&gt;




&lt;h2&gt;
  
  
  Markup
&lt;/h2&gt;

&lt;p&gt;The markup consists of a single custom element, with a &lt;code&gt;keys&lt;/code&gt;-attribute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;piano-chord&lt;/span&gt; &lt;span class="na"&gt;keys=&lt;/span&gt;&lt;span class="s"&gt;"14"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/piano-chord&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;keys&lt;/code&gt; indicates how many &lt;em&gt;white&lt;/em&gt; keys we want to show on our piano. For now, that's all we need; all the heavy lifting is done in CSS.&lt;/p&gt;




&lt;h2&gt;
  
  
  CSS
&lt;/h2&gt;

&lt;p&gt;The first thing we do in CSS is grab the &lt;code&gt;keys&lt;/code&gt; attribute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;piano-chord&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--_keys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we need a private const to indicate how many white keys are in a regular octave:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--_octave-keys: 7;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;OK, so now we can already draw the white keys, which will be simple lines at calculated intervals:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;background&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;linear-gradient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;90&lt;/span&gt;&lt;span class="nt"&gt;deg&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; 
&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--piano-chord-black-key&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; 
&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--piano-chord-key-gap&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; 
&lt;span class="err"&gt;#0000&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--piano-chord-key-gap&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

&lt;span class="nt"&gt;border-block&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
  &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--piano-chord-key-gap&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="nt"&gt;solid&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--piano-chord-black-key&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I've added a few properties to set the line color and width. We also need to set the &lt;code&gt;background-size&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;background-size&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--_keys&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;-&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--piano-chord-key-gap&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--_keys&lt;/span&gt;&lt;span class="o"&gt;)))&lt;/span&gt; &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives us:&lt;/p&gt;

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




&lt;p&gt;Next, we need to calculate the octave width, as the five black keys in a regular octave will repeat in the next octave, creating the distinct pattern we know from a piano.&lt;/p&gt;

&lt;p&gt;For the next calculations, we need a few more private properties:&lt;/p&gt;

&lt;h3&gt;
  
  
  Octave width
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;--_ow&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--_keys&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--_octave-keys&lt;/span&gt;&lt;span class="o"&gt;)));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Black key ratio
&lt;/h3&gt;

&lt;p&gt;The black key is always 60% of the width of a white key, but because we change the octave width dynamically, we need another calculation to keep this ratio:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;--_r&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--_keys&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--_octave-keys&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;6&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  White key width:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;--_wkw&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--_keys&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Black key width
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;--_bkw&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--_wkw&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--_r&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Phew! And we're not done yet! You might recall that the black key is 60% of a white key and placed exactly in the middle between two white keys. Half of 60% is 30%, so we need to place the first black key (between C and D on a piano) at 70% of the first white key (C):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;--_off1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;7&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--_octave-keys&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To skip forward: The next ones are placed at &lt;code&gt;1.7&lt;/code&gt;, &lt;code&gt;3.7&lt;/code&gt;, &lt;code&gt;4.7&lt;/code&gt;, and &lt;code&gt;5.7&lt;/code&gt;. Now, we need to add five extra gradients to our background, one for each black key in an octave.&lt;/p&gt;

&lt;p&gt;I'll just show the code for the first here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;linear-gradient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;90&lt;/span&gt;&lt;span class="nt"&gt;deg&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="err"&gt;#0000&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--_off1&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
  &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--piano-chord-black-key&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--_off1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--_off1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--_bkw&lt;/span&gt;&lt;span class="o"&gt;)),&lt;/span&gt;
  &lt;span class="err"&gt;#0000&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thankfully, the &lt;code&gt;background-size&lt;/code&gt; is simple this time: &lt;code&gt;var(--_ow) 60%&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Let's see what we've got:&lt;/p&gt;

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

&lt;p&gt;Cool! Let's change &lt;code&gt;keys&lt;/code&gt; to &lt;code&gt;keys="21"&lt;/code&gt;:&lt;/p&gt;

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

&lt;p&gt;Isn't that cool? A CSS-only, attribute-controlled piano pattern?&lt;/p&gt;




&lt;h2&gt;
  
  
  Adding chord indicators
&lt;/h2&gt;

&lt;p&gt;Now, let's add chords. For that, we need a new custom element, &lt;code&gt;&amp;lt;piano-key&amp;gt;&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;piano-chord&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"C"&lt;/span&gt; &lt;span class="na"&gt;keys=&lt;/span&gt;&lt;span class="s"&gt;"14"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;piano-key&lt;/span&gt; &lt;span class="na"&gt;note=&lt;/span&gt;&lt;span class="s"&gt;"C"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/piano-key&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;piano-key&lt;/span&gt; &lt;span class="na"&gt;note=&lt;/span&gt;&lt;span class="s"&gt;"E"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/piano-key&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;piano-key&lt;/span&gt; &lt;span class="na"&gt;note=&lt;/span&gt;&lt;span class="s"&gt;"G"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/piano-key&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;/piano-chord&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That wraps up what we need to do in HTML. Let's go back to CSS. First, we set a piano key to be a grid:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;piano-key&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we need to set at which column location the key is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;piano-key&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;note&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"C"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  &lt;span class="nl"&gt;grid-column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;piano-key&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;note&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"D"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  &lt;span class="nl"&gt;grid-column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;/* etc. */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we add a pseudo-element showing a small dot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;piano-key&lt;/span&gt;&lt;span class="nd"&gt;::after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_bg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;margin-block&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;place-self&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This renders:&lt;/p&gt;

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

&lt;p&gt;... and that's &lt;em&gt;almost&lt;/em&gt; it! We need to handle sharp and flat notes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;piano-key&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;note&lt;/span&gt;&lt;span class="o"&gt;*=&lt;/span&gt;&lt;span class="s1"&gt;"#"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt; &lt;span class="m"&gt;-40%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;piano-key&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;note&lt;/span&gt;&lt;span class="o"&gt;*=&lt;/span&gt;&lt;span class="s1"&gt;"b"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-50%&lt;/span&gt; &lt;span class="m"&gt;-40%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And to make them look just as cool as in the posters I mentioned in the beginning, let's wrap the piano in a wrapper with a dusty blue color and a retro SVG filter (see the final demo for the code!):&lt;/p&gt;

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

&lt;p&gt;And that's it! There is, of course, much more to it, but I've covered most of the basics. Here's a CodePen with a bunch of chords:&lt;/p&gt;

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

</description>
      <category>css</category>
      <category>tutorial</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Charts in CSS</title>
      <dc:creator>Mads Stoumann</dc:creator>
      <pubDate>Mon, 09 Jun 2025 10:43:37 +0000</pubDate>
      <link>https://forem.com/madsstoumann/charts-in-css-1di1</link>
      <guid>https://forem.com/madsstoumann/charts-in-css-1di1</guid>
      <description>&lt;p&gt;Charts have been done in CSS many times before — there's even a &lt;a href="https://chartscss.org/" rel="noopener noreferrer"&gt;dedicated project&lt;/a&gt;. So why this post? Because CSS evolves all the time — new, cool techniques emerge, allowing us to do things &lt;em&gt;simpler&lt;/em&gt;, or add even more complicated new features. &lt;/p&gt;

&lt;p&gt;Let's dive in!&lt;/p&gt;

&lt;h2&gt;
  
  
  Markup
&lt;/h2&gt;

&lt;p&gt;Like the &lt;em&gt;CSS Charts&lt;/em&gt;-project, we'll use a &lt;code&gt;&amp;lt;table&amp;gt;&lt;/code&gt; for our charts. It's accessible, simple and readable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;table&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;caption&amp;gt;&lt;/span&gt;Monthly Sales Figures ($)&lt;span class="nt"&gt;&amp;lt;/caption&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;tbody&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;th&lt;/span&gt; &lt;span class="na"&gt;scope=&lt;/span&gt;&lt;span class="s"&gt;"row"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;January&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;td&lt;/span&gt; &lt;span class="na"&gt;data-value=&lt;/span&gt;&lt;span class="s"&gt;"28500"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;$28,500&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;th&lt;/span&gt; &lt;span class="na"&gt;scope=&lt;/span&gt;&lt;span class="s"&gt;"row"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;February&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;td&lt;/span&gt; &lt;span class="na"&gt;data-value=&lt;/span&gt;&lt;span class="s"&gt;"22300"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;$22,300&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;th&lt;/span&gt; &lt;span class="na"&gt;scope=&lt;/span&gt;&lt;span class="s"&gt;"row"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;March&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;td&lt;/span&gt; &lt;span class="na"&gt;data-value=&lt;/span&gt;&lt;span class="s"&gt;"30100"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;$30,100&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- etc. --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/tbody&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/table&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the &lt;code&gt;data-value&lt;/code&gt;-attribute? We'll grab that with the new, typed &lt;code&gt;attr()&lt;/code&gt; in CSS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;--_v&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;attr&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;data-value&lt;/span&gt; &lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="o"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;),&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before we continue, let's wrap the &lt;code&gt;&amp;lt;table&amp;gt;&lt;/code&gt; in a custom element, allowing us to add a few custom attributes, as well as functioning as a &lt;strong&gt;container&lt;/strong&gt;, we can use to make &lt;strong&gt;responsive charts&lt;/strong&gt; (more on that later!):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;data-chart&lt;/span&gt;
  &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Annual Revenue Growth"&lt;/span&gt;
  &lt;span class="na"&gt;max=&lt;/span&gt;&lt;span class="s"&gt;"50000"&lt;/span&gt;
  &lt;span class="na"&gt;min=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;
  &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"figure"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;table&amp;gt;&lt;/span&gt; ... &lt;span class="nt"&gt;&amp;lt;/table&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/data-chart&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll grab the &lt;code&gt;min&lt;/code&gt; and &lt;code&gt;max&lt;/code&gt; attributes with &lt;code&gt;attr()&lt;/code&gt; as well, setting fallbacks to &lt;code&gt;0&lt;/code&gt; and &lt;code&gt;100&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;  &lt;span class="nt"&gt;--_min&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;attr&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;min&lt;/span&gt; &lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="o"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;),&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="nt"&gt;--_max&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;attr&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;max&lt;/span&gt; &lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="o"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;),&lt;/span&gt; &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we'll add a simple grid to the &lt;code&gt;&amp;lt;table&amp;gt;&lt;/code&gt;, setting fixed heights for the caption and the label-area, and &lt;code&gt;1fr&lt;/code&gt; for the chart itself:&lt;/p&gt;

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

&lt;h3&gt;
  
  
  A bit of math
&lt;/h3&gt;

&lt;p&gt;The first chart we'll build is a &lt;em&gt;Column Chart&lt;/em&gt;. For that, we need to calculate the height of each column:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;td&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_min&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; 
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_max&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_min&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; 
  &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="n"&gt;cqb&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--data-chart-label-h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;So what's going on?&lt;/strong&gt; We calculate the height based on &lt;code&gt;min&lt;/code&gt; and &lt;code&gt;max&lt;/code&gt; and the current value, then multiply by &lt;code&gt;100cqb&lt;/code&gt;, which is the height of the "container" — finally, we deduct the height of the labels.&lt;/p&gt;

&lt;p&gt;We'll skip the logic for colors (see final demo) — let's just add the &lt;code&gt;&amp;lt;tbody&amp;gt;&lt;/code&gt;-part and see what we have so far:&lt;/p&gt;

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

&lt;p&gt;Cool! Now, let's add some grid lines and numeric labels along the y-axis. Because it's a &lt;code&gt;&amp;lt;table&amp;gt;&lt;/code&gt;, we can't just add any tag. Since we're not using &lt;code&gt;&amp;lt;thead&amp;gt;&lt;/code&gt;, let's use that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;thead&lt;/span&gt; &lt;span class="na"&gt;aria-hidden=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;th&lt;/span&gt; &lt;span class="na"&gt;colspan=&lt;/span&gt;&lt;span class="s"&gt;"2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;50000&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;th&lt;/span&gt; &lt;span class="na"&gt;colspan=&lt;/span&gt;&lt;span class="s"&gt;"2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;th&lt;/span&gt; &lt;span class="na"&gt;colspan=&lt;/span&gt;&lt;span class="s"&gt;"2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;40000&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- etc. --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/thead&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need &lt;code&gt;aria-hidden&lt;/code&gt;, so it's not picked up by screen readers. &lt;/p&gt;

&lt;p&gt;This is styled like this:&lt;/p&gt;

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

&lt;p&gt;Let's see how &lt;code&gt;&amp;lt;thead&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;tbody&amp;gt;&lt;/code&gt; look together without Dev Tool's grid inspector:&lt;/p&gt;

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

&lt;p&gt;Nice! Now, how about we create some variants?&lt;/p&gt;

&lt;h2&gt;
  
  
  Variants
&lt;/h2&gt;

&lt;p&gt;The variants will use the &lt;strong&gt;exact same markup&lt;/strong&gt;, but because of some limitations, we have to add a few extra attributes (for now).&lt;/p&gt;

&lt;h3&gt;
  
  
  Area Chart
&lt;/h3&gt;

&lt;p&gt;For an &lt;em&gt;Area Chart&lt;/em&gt;, we can re-use most of the &lt;em&gt;Column Chart&lt;/em&gt;; we just need the value of the &lt;em&gt;previous&lt;/em&gt; element for each table cell. Unfortunately, we can't grab the previous value with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:nth-of-type&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;sibling-index&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="nt"&gt;-&lt;/span&gt; &lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We &lt;em&gt;could&lt;/em&gt; create a bunch of &lt;code&gt;:has(tr:nth-of-type(x))&lt;/code&gt;-selectors to grab the value of the previous cell, but that would make the code a bit messy.&lt;/p&gt;

&lt;p&gt;So — for now — we set the &lt;em&gt;previous value&lt;/em&gt; on each &lt;code&gt;&amp;lt;td&amp;gt;&lt;/code&gt; as &lt;code&gt;data-prev&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;th&lt;/span&gt; &lt;span class="na"&gt;scope=&lt;/span&gt;&lt;span class="s"&gt;"row"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;January&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;td&lt;/span&gt; &lt;span class="na"&gt;data-value=&lt;/span&gt;&lt;span class="s"&gt;"28500"&lt;/span&gt; &lt;span class="na"&gt;data-prev=&lt;/span&gt;&lt;span class="s"&gt;"28500"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;$28,500&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we create a simple polygon &lt;code&gt;clip-path&lt;/code&gt; that creates a diagonal line from the &lt;em&gt;previous value&lt;/em&gt; to the &lt;em&gt;current value&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;clip-path&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;polygon&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;-1&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt;
  &lt;span class="nt"&gt;-1&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--_py&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%),&lt;/span&gt;
  &lt;span class="err"&gt;101&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--_y&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%),&lt;/span&gt;
  &lt;span class="err"&gt;101&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's not dive too deep into this, it's simply y-coordinates, calculated from &lt;em&gt;value&lt;/em&gt; and &lt;em&gt;previous value&lt;/em&gt;.&lt;/p&gt;

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

&lt;p&gt;OK, this is one of the cases where a single color looks better. Let's update &lt;code&gt;--data-chart-bar-bg&lt;/code&gt;:&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Line Chart
&lt;/h3&gt;

&lt;p&gt;A line chart is &lt;em&gt;almost&lt;/em&gt; the same as an area chart. We simply need to cut off the bottom part as well. That distance is the height of the line, which we define in &lt;code&gt;--line-chart-line-h&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;clip-path&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;polygon&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;-1&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--_py&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--line-chart-line-h&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="err"&gt;2&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
  &lt;span class="nt"&gt;-1&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--_py&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nt"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--line-chart-line-h&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="err"&gt;2&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
  &lt;span class="err"&gt;101&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--_y&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nt"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--line-chart-line-h&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="err"&gt;2&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
  &lt;span class="err"&gt;101&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--_y&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--line-chart-line-h&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="err"&gt;2&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we get:&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Bar Chart
&lt;/h3&gt;

&lt;p&gt;The &lt;em&gt;Bar Chart&lt;/em&gt; does not need the &lt;code&gt;data-prev&lt;/code&gt;-attribute, but it &lt;strong&gt;does&lt;/strong&gt; require a few style-changes. Let's not dive deep into these changes — instead, inspect the styles in the final demo.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Pie Chart
&lt;/h3&gt;

&lt;p&gt;The &lt;em&gt;Pie Chart&lt;/em&gt; is a bit different! Here, we need to use &lt;code&gt;conic-gradient&lt;/code&gt;s, where each gradient needs to start where the previous one ends. Thus, we need an accumulated value (&lt;code&gt;--_av&lt;/code&gt;) and a &lt;strong&gt;total&lt;/strong&gt; of all values (&lt;code&gt;--_t&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;tbody&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--_t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data-t&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;td&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--_av&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data-av&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--_start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_av&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_t&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; 
    &lt;span class="m"&gt;360deg&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--_end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_t&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; 
    &lt;span class="m"&gt;360deg&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;conic-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_start&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_bg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_end&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="m"&gt;#0000&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
    &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="m"&gt;360deg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives us:&lt;/p&gt;

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

&lt;p&gt;I'd &lt;em&gt;really&lt;/em&gt; like it if we could use CSS counters to do the accumulated value and total, but couldn't get it to work (maybe someone like Temani Afif can!?).&lt;/p&gt;

&lt;h3&gt;
  
  
  Donut Chart
&lt;/h3&gt;

&lt;p&gt;A donut chart is the same as a pie chart, but with a "cut out", for which we use a simple CSS Mask:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;tbody&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;radial-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;circle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#0000&lt;/span&gt; &lt;span class="m"&gt;40%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#000&lt;/span&gt; &lt;span class="m"&gt;40%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;... and voilà, we get:&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Grouped Charts
&lt;/h2&gt;

&lt;p&gt;Grouped charts are simply done with multiple cells per table row:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;th&lt;/span&gt; &lt;span class="na"&gt;scope=&lt;/span&gt;&lt;span class="s"&gt;"row"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;January&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;td&lt;/span&gt; &lt;span class="na"&gt;data-value=&lt;/span&gt;&lt;span class="s"&gt;"12500"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;12500&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;td&lt;/span&gt; &lt;span class="na"&gt;data-value=&lt;/span&gt;&lt;span class="s"&gt;"9800"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;9800&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;td&lt;/span&gt; &lt;span class="na"&gt;data-value=&lt;/span&gt;&lt;span class="s"&gt;"6200"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;6200&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the styles we then group them — and in some cases, present them like individual "layers". Inspect the styles in the final demo for more details — here's how they look:&lt;/p&gt;

&lt;h3&gt;
  
  
  Grouped Column Chart
&lt;/h3&gt;

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

&lt;h3&gt;
  
  
  Grouped Area Chart
&lt;/h3&gt;

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

&lt;h3&gt;
  
  
  Grouped Line Chart
&lt;/h3&gt;

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

&lt;h3&gt;
  
  
  Grouped Bar Chart
&lt;/h3&gt;

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

&lt;h3&gt;
  
  
  Grouped Donut Chart
&lt;/h3&gt;

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




&lt;h2&gt;
  
  
  Responsive Charts
&lt;/h2&gt;

&lt;p&gt;One cool thing about controlling charts in CSS, is how easily we can make them responsive. First, let's add two new attributes to our &lt;code&gt;&amp;lt;data-chart&amp;gt;&lt;/code&gt;-component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;data-chart&lt;/span&gt;
  &lt;span class="na"&gt;items-sm=&lt;/span&gt;&lt;span class="s"&gt;"7"&lt;/span&gt;
  &lt;span class="na"&gt;items-xs=&lt;/span&gt;&lt;span class="s"&gt;"4"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These indicate how many columns we want to show per breakpoint, in this case "sm" (small) and "xs" (x-small). Add as many as you want, and control the breakpoint in a &lt;code&gt;@container&lt;/code&gt;-query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@container&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;400px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nd"&gt;:is&lt;/span&gt;&lt;span class="o"&gt;([&lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"area"&lt;/span&gt;&lt;span class="o"&gt;],&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"column"&lt;/span&gt;&lt;span class="o"&gt;],&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"line"&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;&amp;amp;[items-xs="1"]&lt;/span&gt; &lt;span class="err"&gt;tbody&lt;/span&gt; &lt;span class="py"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;nth-of-type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="err"&gt;+&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="err"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;items-xs&lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"2"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;tbody&lt;/span&gt; &lt;span class="n"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;nth-of-type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="err"&gt;+&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So — in this case — if &lt;code&gt;items-sm="1"&lt;/code&gt;, hide all items after &lt;code&gt;n+2&lt;/code&gt; etc. Not rocket science, and hopefully something we can make more beautiful in the future.&lt;/p&gt;

&lt;p&gt;Let's resize the screen and check it out. First, "sm":&lt;/p&gt;

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

&lt;p&gt;Next, "xs":&lt;/p&gt;

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




&lt;h2&gt;
  
  
  Demos
&lt;/h2&gt;

&lt;p&gt;I've created a bunch of demos, all forked from the first, as they all share the same CSS.&lt;/p&gt;

&lt;p&gt;The first is shown below. Go to &lt;a href="https://codepen.io/collection/ojgBRg" rel="noopener noreferrer"&gt;this collection&lt;/a&gt; to see them all.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; As the responsiveness is controlled by the size of the &lt;em&gt;container&lt;/em&gt;, you'll most likely see the "sm"-version of the charts. Open CodePen in full screen to play around and resize. Also, note that Safari and Firefox &lt;strong&gt;do not&lt;/strong&gt; support typed &lt;code&gt;attr()&lt;/code&gt;, and thus the respnsiveness only works with a JS fallback.&lt;/p&gt;
&lt;/blockquote&gt;

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

</description>
      <category>css</category>
      <category>tutorial</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Guitar Chords in CSS</title>
      <dc:creator>Mads Stoumann</dc:creator>
      <pubDate>Wed, 07 May 2025 09:38:23 +0000</pubDate>
      <link>https://forem.com/madsstoumann/guitar-chords-in-css-3hk8</link>
      <guid>https://forem.com/madsstoumann/guitar-chords-in-css-3hk8</guid>
      <description>&lt;p&gt;In my previous article, I looked into the new, improved &lt;code&gt;attr()&lt;/code&gt; method in CSS. I was &lt;a href="https://dev.to/madsstoumann/phases-of-the-moon-in-css-2lbo"&gt;over the moon&lt;/a&gt; (pun intended). This time, I’ll continue looking into the &lt;code&gt;attr()&lt;/code&gt; method, showing how we can make easily readable components — at least for guitarists — like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;fret-board&lt;/span&gt; &lt;span class="na"&gt;frets=&lt;/span&gt;&lt;span class="s"&gt;"4"&lt;/span&gt; &lt;span class="na"&gt;strings=&lt;/span&gt;&lt;span class="s"&gt;"6"&lt;/span&gt; &lt;span class="na"&gt;chord=&lt;/span&gt;&lt;span class="s"&gt;"C Major"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;string-note&lt;/span&gt; &lt;span class="na"&gt;string=&lt;/span&gt;&lt;span class="s"&gt;"6"&lt;/span&gt; &lt;span class="na"&gt;mute&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/string-note&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;string-note&lt;/span&gt; &lt;span class="na"&gt;string=&lt;/span&gt;&lt;span class="s"&gt;"5"&lt;/span&gt; &lt;span class="na"&gt;fret=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt; &lt;span class="na"&gt;finger=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/string-note&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;string-note&lt;/span&gt; &lt;span class="na"&gt;string=&lt;/span&gt;&lt;span class="s"&gt;"4"&lt;/span&gt; &lt;span class="na"&gt;fret=&lt;/span&gt;&lt;span class="s"&gt;"2"&lt;/span&gt; &lt;span class="na"&gt;finger=&lt;/span&gt;&lt;span class="s"&gt;"2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/string-note&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;string-note&lt;/span&gt; &lt;span class="na"&gt;string=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt; &lt;span class="na"&gt;open&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/string-note&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;string-note&lt;/span&gt; &lt;span class="na"&gt;string=&lt;/span&gt;&lt;span class="s"&gt;"2"&lt;/span&gt; &lt;span class="na"&gt;fret=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;finger=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/string-note&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;string-note&lt;/span&gt; &lt;span class="na"&gt;string=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;open&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/string-note&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/fret-board&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;... become this — with &lt;strong&gt;no JavaScript at all&lt;/strong&gt;:&lt;/p&gt;

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

&lt;p&gt;Let’s get started!&lt;/p&gt;

&lt;h3&gt;
  
  
  Basic grid
&lt;/h3&gt;

&lt;p&gt;First, we need a basic grid. How many cells and rows depend on the &lt;code&gt;frets&lt;/code&gt; and &lt;code&gt;strings&lt;/code&gt; attributes we defined in the HTML — so let’s grab those as &lt;code&gt;&amp;lt;number&amp;gt;&lt;/code&gt; and set two custom properties:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;--_frets&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;attr&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;frets&lt;/span&gt; &lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="o"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;),&lt;/span&gt; &lt;span class="err"&gt;4&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nt"&gt;--_strings&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;attr&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;strings&lt;/span&gt; &lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="o"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;),&lt;/span&gt; &lt;span class="err"&gt;6&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In CSS, we’ll &lt;em&gt;double&lt;/em&gt; the number of strings, so we can place notes &lt;strong&gt;on&lt;/strong&gt; the strings — one grid-unit to the left of the string itself. We also want top and bottom rows for chord-name and open/mute indicators, so our grid looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;fret-board&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_strings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;grid-template-rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--fret-board-top-row-h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;12%&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_frets&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--fret-board-bottom-row-h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;15%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We now have:&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Frets and strings
&lt;/h3&gt;

&lt;p&gt;The frets and strings are added to a pseudo-element as two linear gradients:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;fret-board&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--fret-board-fret-c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;light-dark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;#000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#FFF&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--fret-board-fret-w&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;clamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.0625rem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.03125rem&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="m"&gt;0.5&lt;/span&gt;&lt;span class="n"&gt;cqi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.5rem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--fret-board-string-c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;light-dark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;#0008&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#FFF8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--fret-board-string-w&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;clamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.0625rem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.03125rem&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="m"&gt;0.5&lt;/span&gt;&lt;span class="n"&gt;cqi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.125rem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nl"&gt;background-image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c"&gt;/* Vertical strings */&lt;/span&gt;
    &lt;span class="n"&gt;linear-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="m"&gt;90deg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
      &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--fret-board-string-c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--fret-board-string-w&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
      &lt;span class="nb"&gt;transparent&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--fret-board-string-w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="c"&gt;/* Horizontal frets */&lt;/span&gt;
    &lt;span class="n"&gt;linear-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="m"&gt;180deg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
      &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--fret-board-fret-c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--fret-board-fret-w&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
      &lt;span class="nb"&gt;transparent&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--fret-board-fret-w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nl"&gt;background-position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; 
    &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--fret-board-fret-w&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
    &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;background-repeat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; 
    &lt;span class="nb"&gt;repeat-x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="nb"&gt;repeat-y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;background-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c"&gt;/* Width and height for strings */&lt;/span&gt;
    &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100%&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_strings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--fret-board-string-w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_strings&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; 
      &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100%&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--fret-board-fret-w&lt;/span&gt;&lt;span class="p"&gt;))),&lt;/span&gt;
    &lt;span class="c"&gt;/* Width and height for frets */&lt;/span&gt;
    &lt;span class="m"&gt;100%&lt;/span&gt; 
      &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100%&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_frets&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--fret-board-fret-w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_frets&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OK, that was a handful — but we basically created horizontal lines for frets from top to bottom, and vertical lines for the strings running across the board.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Adding notes and fingers
&lt;/h3&gt;

&lt;p&gt;Let’s add some notes — but first, let’s grab those attributes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;--barre&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;attr&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;barre&lt;/span&gt; &lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="o"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;),&lt;/span&gt; &lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nt"&gt;--fret&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="nt"&gt;attr&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;fret&lt;/span&gt; &lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="o"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;),&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nt"&gt;--string&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="nt"&gt;attr&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;string&lt;/span&gt; &lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="o"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;),&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’ll get to &lt;code&gt;--barre&lt;/code&gt; soon, but for now, we place the note using this formula:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;string-note&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;grid-column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_strings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--barre&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
  &lt;span class="nl"&gt;grid-row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--fret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s break this down:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Grid Column Positioning:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;(var(--_strings) * 2)&lt;/code&gt; - This starts at the rightmost side of our grid&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;(var(--string) * 2 - 1)&lt;/code&gt; - This calculates how far to move from the right&lt;/li&gt;
&lt;li&gt;For example, with 6 strings:

&lt;ul&gt;
&lt;li&gt;String 1 (highest) goes at position &lt;code&gt;12 - (1*2-1) = 11&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;String 6 (lowest) goes at position &lt;code&gt;12 - (6*2-1) = 1&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Span Calculation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;span calc(var(--barre) * 2)&lt;/code&gt; - For regular notes, spans 2 columns&lt;/li&gt;
&lt;li&gt;For barre chords, spans more columns depending on how many strings are covered&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Grid Row Formula:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;calc(var(--fret) + 1)&lt;/code&gt; - The &lt;code&gt;+1&lt;/code&gt; accounts for the header row&lt;/li&gt;
&lt;li&gt;Open strings (fret = 0) go in row 1, first fret in row 2, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This gets us:&lt;/p&gt;

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

&lt;h4&gt;
  
  
  Displaying finger numbers
&lt;/h4&gt;

&lt;p&gt;For good tablature, we need to show which finger to use for each note. We get this from the &lt;code&gt;finger&lt;/code&gt; attribute in our HTML and display it using a pseudo-element:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;string-note&lt;/span&gt;&lt;span class="nd"&gt;::after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--string-note-c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;light-dark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;#FFF&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#222&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;finger&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--string-note-fs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;&lt;span class="n"&gt;cqi&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--string-note-fw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;text-box&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cap&lt;/span&gt; &lt;span class="n"&gt;alphabetic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;code&gt;text-box: cap alphabetic&lt;/code&gt; is a modern CSS feature that trims spacing created by &lt;code&gt;line-height&lt;/code&gt;. The &lt;code&gt;light-dark()&lt;/code&gt; function automatically adjusts text color for light or dark mode.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Muted and open strings
&lt;/h4&gt;

&lt;p&gt;Guitar chords often involve strings that aren’t pressed down but are either played open or muted (not played):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;string-note&lt;/span&gt; &lt;span class="na"&gt;string=&lt;/span&gt;&lt;span class="s"&gt;"6"&lt;/span&gt; &lt;span class="na"&gt;mute&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/string-note&amp;gt;&lt;/span&gt;    &lt;span class="c"&gt;&amp;lt;!-- Muted string --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;string-note&lt;/span&gt; &lt;span class="na"&gt;string=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt; &lt;span class="na"&gt;open&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/string-note&amp;gt;&lt;/span&gt;    &lt;span class="c"&gt;&amp;lt;!-- Open string --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the visual representation of string states:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For &lt;strong&gt;muted strings&lt;/strong&gt;, we create an X symbol using Temani Atif’s &lt;a href="https://css-shape.com/cross/" rel="noopener noreferrer"&gt;cross shape&lt;/a&gt; with &lt;code&gt;border-image&lt;/code&gt; and a 45° rotation&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;open strings&lt;/strong&gt;, we use a CSS mask with a radial gradient to create a hollow circle effect
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;string-note&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;mute&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border-image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;conic-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--fret-board-bg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;50%&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="m"&gt;0.25&lt;/span&gt;&lt;span class="n"&gt;cqi&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;45deg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;string-note&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;open&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;radial-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;circle&lt;/span&gt; &lt;span class="n"&gt;farthest-side&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="nb"&gt;transparent&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100%&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;cqi&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
    &lt;span class="m"&gt;#000&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100%&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;cqi&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Barre Chords
&lt;/h3&gt;

&lt;p&gt;A "barre chord" is one where you hold down multiple strings with one finger. Let’s add the &lt;code&gt;barre&lt;/code&gt; attribute in HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;string-note&lt;/span&gt; &lt;span class="na"&gt;string=&lt;/span&gt;&lt;span class="s"&gt;"6"&lt;/span&gt; &lt;span class="na"&gt;fret=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;barre=&lt;/span&gt;&lt;span class="s"&gt;"6"&lt;/span&gt; &lt;span class="na"&gt;finger=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/string-note&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We already covered the calculations above, but here’s how it looks visually:&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Fret Numbers
&lt;/h3&gt;

&lt;p&gt;Sometimes, a chord does not start on the first fret, and we need to indicate a fret number. For this, we use an ordered list, &lt;code&gt;&amp;lt;ol&amp;gt;&lt;/code&gt;, where we set the &lt;code&gt;value&lt;/code&gt; attribute on the first item:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;ol&amp;gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"7"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/li&amp;gt;&amp;lt;/ol&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can inspect the styles in the final demo, but it looks like this:&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Other instruments with frets and strings
&lt;/h2&gt;

&lt;p&gt;Now, the great thing about controlling the CSS from a few attributes is how easy we can make it work for &lt;em&gt;other&lt;/em&gt; instruments. Here are some examples:&lt;/p&gt;

&lt;h3&gt;
  
  
  Ukulele — 4 strings:
&lt;/h3&gt;

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

&lt;h3&gt;
  
  
  Banjo — 5 strings:
&lt;/h3&gt;

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




&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Here’s a demo. Please note that it &lt;strong&gt;only works in Chrome&lt;/strong&gt; (for now):&lt;/p&gt;

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

&lt;/h2&gt;

&lt;p&gt;I hope you enjoyed this article as much as I did writing it — now, I’ll grab my guitar and play some of these chords.&lt;/p&gt;

</description>
      <category>css</category>
      <category>webdev</category>
      <category>showdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Phases of the Moon in CSS</title>
      <dc:creator>Mads Stoumann</dc:creator>
      <pubDate>Wed, 09 Apr 2025 17:54:51 +0000</pubDate>
      <link>https://forem.com/madsstoumann/phases-of-the-moon-in-css-2lbo</link>
      <guid>https://forem.com/madsstoumann/phases-of-the-moon-in-css-2lbo</guid>
      <description>&lt;p&gt;You might have heard that the humble, old &lt;code&gt;attr()&lt;/code&gt; method in CSS &lt;a href="https://developer.chrome.com/blog/advanced-attr" rel="noopener noreferrer"&gt;just got a big update&lt;/a&gt;. It's only in Chrome for now, but I assume the Firefox and Safari teams are working hard on implementing this!&lt;/p&gt;

&lt;p&gt;So what's the difference? Well, we now have &lt;strong&gt;types&lt;/strong&gt; and &lt;strong&gt;fallbacks&lt;/strong&gt;, so we can directly update a CSS custom property to a numeric value, with a fallback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;--property&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;attr&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;my-attr&lt;/span&gt; &lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;,&lt;/span&gt; &lt;span class="err"&gt;15&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How cool is that?! It means we can now create really complex &lt;strong&gt;CSS-only&lt;/strong&gt; components. In this tutorial, we'll build a &lt;code&gt;&amp;lt;moon-phase&amp;gt;&lt;/code&gt;-component, using &lt;strong&gt;only&lt;/strong&gt; CSS!&lt;/p&gt;

&lt;h2&gt;
  
  
  Markup
&lt;/h2&gt;

&lt;p&gt;To start with, let's create a simple custom element called &lt;code&gt;&amp;lt;moon-phase&amp;gt;&lt;/code&gt;. We give it two attributes: &lt;code&gt;illumination&lt;/code&gt; and &lt;code&gt;phase&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Valid phases are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;new moon&lt;/li&gt;
&lt;li&gt;waxing crescent&lt;/li&gt;
&lt;li&gt;first quarter&lt;/li&gt;
&lt;li&gt;waxing gibbous&lt;/li&gt;
&lt;li&gt;full moon&lt;/li&gt;
&lt;li&gt;waning gibbous&lt;/li&gt;
&lt;li&gt;last quarter&lt;/li&gt;
&lt;li&gt;waning crescent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;moon-phase&lt;/span&gt;
  &lt;span class="na"&gt;illumination=&lt;/span&gt;&lt;span class="s"&gt;"58"&lt;/span&gt;
  &lt;span class="na"&gt;phase=&lt;/span&gt;&lt;span class="s"&gt;"waxing crescent"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/moon-phase&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And ... that's it (for now). Let's switch to CSS!&lt;/p&gt;

&lt;h2&gt;
  
  
  CSS
&lt;/h2&gt;

&lt;p&gt;Our custom element needs a few default styles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;moon-phase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;aspect-ratio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;clip&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We add an image of the moon to a &lt;code&gt;::before&lt;/code&gt;-pseudo element:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;moon-phase&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sx"&gt;url('moon.png')&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="n"&gt;cover&lt;/span&gt; &lt;span class="nb"&gt;no-repeat&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;inset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we get:&lt;/p&gt;

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

&lt;p&gt;Not very exciting! Let's add a &lt;code&gt;filter&lt;/code&gt; to spice it up a bit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;moon-phase&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sepia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;grayscale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;.25&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have:&lt;/p&gt;

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

&lt;p&gt;Much better! If you want to play around with CSS filters, &lt;a href="https://codepen.io/stoumann/pen/MWeNmyb" rel="noopener noreferrer"&gt;I've made a small editor&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now, we add an &lt;code&gt;::after&lt;/code&gt; pseudo-element:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;moon-phase&lt;/span&gt;&lt;span class="nd"&gt;::after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_btlr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_btrr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_bbrr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_bblr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;inset-inline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_ii&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Phew, let that sink in! We add four properties to control all &lt;code&gt;border-radius&lt;/code&gt;-sides, and one for &lt;code&gt;width&lt;/code&gt;. Let's start with that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;moon-phase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--_w&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100%&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1%&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;illumination&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="m"&gt;0%&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;So what's going on?&lt;/strong&gt; We read the &lt;code&gt;illumination&lt;/code&gt;-attribute as a number, convert it to a percentage by multiplying with &lt;code&gt;1%&lt;/code&gt;, and deduct that from the full width.&lt;/p&gt;

&lt;p&gt;So, if &lt;code&gt;illumination&lt;/code&gt; is set to &lt;code&gt;6%&lt;/code&gt;, the width will be &lt;code&gt;94%&lt;/code&gt; etc.&lt;/p&gt;

&lt;p&gt;Next, we need to adjust the &lt;code&gt;border-radius&lt;/code&gt; properties and &lt;code&gt;inset&lt;/code&gt;, depending on which &lt;code&gt;phase&lt;/code&gt; the moon has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;phase&lt;/span&gt;&lt;span class="o"&gt;*=&lt;/span&gt;&lt;span class="s1"&gt;"first-quarter"&lt;/span&gt;&lt;span class="o"&gt;],&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;phase&lt;/span&gt;&lt;span class="o"&gt;*=&lt;/span&gt;&lt;span class="s1"&gt;"waxing"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--_ii&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;phase&lt;/span&gt;&lt;span class="o"&gt;*=&lt;/span&gt;&lt;span class="s1"&gt;"crescent"&lt;/span&gt;&lt;span class="o"&gt;],&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;phase&lt;/span&gt;&lt;span class="o"&gt;*=&lt;/span&gt;&lt;span class="s1"&gt;"first-quarter"&lt;/span&gt;&lt;span class="o"&gt;],&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;phase&lt;/span&gt;&lt;span class="o"&gt;*=&lt;/span&gt;&lt;span class="s1"&gt;"waxing"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--_bblr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--_btlr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;phase&lt;/span&gt;&lt;span class="o"&gt;*=&lt;/span&gt;&lt;span class="s1"&gt;"crescent"&lt;/span&gt;&lt;span class="o"&gt;],&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;phase&lt;/span&gt;&lt;span class="o"&gt;*=&lt;/span&gt;&lt;span class="s1"&gt;"last-quarter"&lt;/span&gt;&lt;span class="o"&gt;],&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;phase&lt;/span&gt;&lt;span class="o"&gt;*=&lt;/span&gt;&lt;span class="s1"&gt;"waning"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--_btrr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--_bbrr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;phase&lt;/span&gt;&lt;span class="o"&gt;*=&lt;/span&gt;&lt;span class="s1"&gt;"gibbous"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="nd"&gt;::after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's see how we're doing, with &lt;strong&gt;6%  illumination&lt;/strong&gt;:&lt;/p&gt;

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

&lt;p&gt;Yay, a thin slice of moon! &lt;/p&gt;

&lt;p&gt;Now, for the "gibbous" phases, the shape is inwards, and we cannot use &lt;code&gt;border-radius&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Instead, the &lt;code&gt;::after&lt;/code&gt;-element takes up &lt;code&gt;100%&lt;/code&gt;, and is cut with a &lt;code&gt;mask&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;phase&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"waxing gibbous"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="nd"&gt;::after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;radial-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;circle&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="m"&gt;#0000&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100%&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_w&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="m"&gt;#000&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100%&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;phase&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"waning gibbous"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="nd"&gt;::after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;radial-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;circle&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="m"&gt;0%&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="m"&gt;#0000&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100%&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_w&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="m"&gt;#000&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100%&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;strong&gt;58% illumination&lt;/strong&gt; we get:&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Latitude and time
&lt;/h2&gt;

&lt;p&gt;Now, the moon looks different depending on where on Earth you reside, so let's add two new attributes to our component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;moon-phase&lt;/span&gt;
  &lt;span class="na"&gt;illumination=&lt;/span&gt;&lt;span class="s"&gt;"25"&lt;/span&gt;
  &lt;span class="na"&gt;phase=&lt;/span&gt;&lt;span class="s"&gt;"waxing crescent"&lt;/span&gt;
  &lt;span class="na"&gt;lat=&lt;/span&gt;&lt;span class="s"&gt;"-33.86"&lt;/span&gt;
  &lt;span class="na"&gt;hour=&lt;/span&gt;&lt;span class="s"&gt;"22"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/moon-phase&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As before, we read these directly in CSS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;moon-phase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--_lat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lat&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--_hour&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="m"&gt;12&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The calculations needed for the rotation angle, are a bit complex:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;moon-phase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--_l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_lat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;1.5deg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--_a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(((&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_hour&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="m"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;1deg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--_r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_a&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break it down:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Latitude Tilt (&lt;code&gt;--_l&lt;/code&gt;)&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
We multiply the latitude by &lt;code&gt;1.5deg&lt;/code&gt; to simulate how the moon’s tilt changes as you move north or south. This creates:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Upward tilt&lt;/strong&gt; in the Southern Hemisphere (negative latitudes).
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Downward tilt&lt;/strong&gt; in the Northern Hemisphere.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Hour Rotation (&lt;code&gt;--_a&lt;/code&gt;)&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The hour calculation &lt;code&gt;calc(((var(--_hour) - 12) * 15 * 0.7) * 1deg)&lt;/code&gt; works like this:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;(var(--_hour) - 12)&lt;/code&gt;: Centers rotation at solar noon.
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;* 15&lt;/code&gt;: Earth rotates 15° per hour (solar motion).
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;* 0.7&lt;/code&gt;: Dampens the effect to match the moon’s slower apparent speed (~14.5°/hour).
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;* 1deg&lt;/code&gt;: Converts to degrees.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Combined Rotation (&lt;code&gt;--_r&lt;/code&gt;)&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Adding &lt;code&gt;--_l&lt;/code&gt; and &lt;code&gt;--_a&lt;/code&gt; gives a realistic orientation. Examples:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Equator (lat=0)&lt;/strong&gt;: Vertical terminator (🌒).
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sydney (lat=-34)&lt;/strong&gt;: Tilted upward (/).
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;London (lat=51.5)&lt;/strong&gt;: Tilted downward (\).
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;North Pole (lat=90)&lt;/strong&gt;: Horizontal terminator (⊐).
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;moon-phase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0deg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c"&gt;/* Applies the final rotation */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's see an example: Same day, but different latitudes:&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;A small disclaimer: At extreme latitudes (&amp;gt;80°), the tilt calculation becomes approximate (the moon doesn’t quite lie flat at the poles with this formula). Also — while I’ve done my best to match real-world behavior — I am by no means an astrophysicist. If you spot errors in the calculations, you know who to blame! &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Here's a Codepen with all the phases of the moon; it's pure CSS, but only working in Chrome for now:&lt;/p&gt;

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

</description>
      <category>css</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>showdev</category>
    </item>
    <item>
      <title>How To Create Gauges in CSS</title>
      <dc:creator>Mads Stoumann</dc:creator>
      <pubDate>Sat, 05 Apr 2025 11:21:33 +0000</pubDate>
      <link>https://forem.com/madsstoumann/how-to-create-gauges-in-css-3581</link>
      <guid>https://forem.com/madsstoumann/how-to-create-gauges-in-css-3581</guid>
      <description>&lt;p&gt;With modern CSS we can create beautiful gauges with ease. It involves a bunch of techniques, so let's break it down and get started!&lt;/p&gt;

&lt;p&gt;First, we create a 3x3 grid. We then add an element that covers the entire grid and is a full circle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;host&lt;/span&gt;&lt;span class="nd"&gt;::part&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;gauge&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;conic-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="c"&gt;/* colors */&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;grid-area&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Next, we add a &lt;em&gt;starting degree&lt;/em&gt; for the &lt;code&gt;min&lt;/code&gt; position of the gauge, and how many degrees until the &lt;code&gt;max&lt;/code&gt; position:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:host&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--analog-gauge-start-angle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;235deg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--analog-gauge-range&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;250deg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adding these to our &lt;code&gt;gauge&lt;/code&gt;-part:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:host::part&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;gauge&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--analog-gauge-bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="m"&gt;#009&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#69f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#ff0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#f90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#f00&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--analog-gauge-range&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="m"&gt;#0000&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--analog-gauge-range&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;conic-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--analog-gauge-start-angle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;235deg&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
  &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--analog-gauge-bg&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we get:&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;What just happened?&lt;/strong&gt; We changed the starting point of the gradient to where we want to place the &lt;code&gt;min&lt;/code&gt;-label. We then added the length of the range using &lt;code&gt;--analog-gauge-range)&lt;/code&gt;, and after that point, we simply insert a transparent (&lt;code&gt;#0000&lt;/code&gt;) color.&lt;/p&gt;

&lt;p&gt;Next, let's add a circular mask to cut off the inner part of the circle — we control the width with a custom property, &lt;code&gt;--analog-gauge-bdw&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:host&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--analog-gauge-mask-circle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;radial-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;circle&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="m"&gt;#0000&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;50&lt;/span&gt;&lt;span class="n"&gt;cqi&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--analog-gauge-bdw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="n"&gt;cqi&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="m"&gt;#000&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's add this to our &lt;code&gt;gauge&lt;/code&gt;-part:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:host::part&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;gauge&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--analog-gauge-mask-circle&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--analog-gauge-mask-segment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;mask-composite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--analog-gauge-mask-composite&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subtract&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll get back to the segment-mask later! Now we have:&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Needle in a haystack
&lt;/h2&gt;

&lt;p&gt;Next up is the gauge needle. It's two grid cells wide:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:host::part&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;needle&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;align-self&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;grid-area&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--analog-gauge-needle-h&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Let's add a circular mask to the point in the needle matching the absolute middle of our grid, and adjust the &lt;code&gt;transform-origin&lt;/code&gt; of the needle to match that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:host&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;-_m&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="n"&gt;cqi&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nd"&gt;:host::part&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;needle&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;radial-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;circle&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100%&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_m&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="m"&gt;#0000&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;2.5&lt;/span&gt;&lt;span class="n"&gt;cqi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#FFF&lt;/span&gt; &lt;span class="m"&gt;2.5&lt;/span&gt;&lt;span class="n"&gt;cqi&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;transform-origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100%&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_m&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we get:&lt;/p&gt;

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

&lt;p&gt;The &lt;code&gt;--_m&lt;/code&gt; variable is 1/6th of the circle width (&lt;code&gt;100cqi&lt;/code&gt;), and thus the middle of a grid cell.&lt;/p&gt;

&lt;p&gt;Next, let's add a fancy clip-path to make it look like a gauge needle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:host::part&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;needle&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;clip-path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;polygon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;7.5%&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;78%&lt;/span&gt; &lt;span class="m"&gt;0%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;83%&lt;/span&gt; &lt;span class="m"&gt;35%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;83%&lt;/span&gt; &lt;span class="m"&gt;65%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;78%&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;I made a &lt;a href="https://codepen.io/stoumann/pen/abZxoOM" rel="noopener noreferrer"&gt;clip-path editor&lt;/a&gt; if you want to make your own needle!&lt;/p&gt;

&lt;h2&gt;
  
  
  Labels
&lt;/h2&gt;

&lt;p&gt;The labels are added in the last row of grid cells, and are again placed using &lt;code&gt;grid-area&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:host::part&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;label-min&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;grid-area&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nd"&gt;:host::part&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;label-max&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;grid-area&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h2&gt;
  
  
  Value Marks
&lt;/h2&gt;

&lt;p&gt;For the value marks, we add an inner circle. This circle is the full width of the main circle &lt;strong&gt;minus&lt;/strong&gt; the width of the gauge:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:host::part&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;value-marks&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;aspect-ratio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;grid-area&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;place-self&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="n"&gt;cqi&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; 
    &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--analog-gauge-bdw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="n"&gt;cqi&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The marks themselves are placed like on an &lt;a href="https://dev.to/madsstoumann/clocks-and-watches-in-css-36mm"&gt;analog clock&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here's what we've got now (I've added a grey background for clarity):&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Segments
&lt;/h2&gt;

&lt;p&gt;Remember the empty segment-mask we added earlier? Let's add that so we have an easy way to segmentize the gauge-gradient:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:host&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--analog-gauge-segments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--analog-gauge-mask-segment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;repeating-conic-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--analog-gauge-start-angle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;235deg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="m"&gt;#000&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--analog-gauge-segments-w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1deg&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="m"&gt;#0000&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--analog-gauge-range&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;250deg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt;
      &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--analog-gauge-segments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;))));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Phew! That requires some explanation! Let's break it down:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We create a mask at the same angle as the main gradient.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;--analog-gauge-segments&lt;/code&gt; variable sets how many segments to divide the gauge into (default is 10). Setting it to 1 gives us a single segment - returning to our original solid gradient.&lt;/li&gt;
&lt;li&gt;The repeating pattern creates thin black lines (&lt;code&gt;#000&lt;/code&gt;) with width of &lt;code&gt;--analog-gauge-segments-w&lt;/code&gt; (default is 1deg).&lt;/li&gt;
&lt;li&gt;Between these lines, we have transparent areas (&lt;code&gt;#0000&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;The size of each segment is calculated by dividing the total range (&lt;code&gt;--analog-gauge-range&lt;/code&gt;) by the number of segments.&lt;/li&gt;
&lt;li&gt;When combined with our previous mask using &lt;code&gt;mask-composite: subtract&lt;/code&gt;, these black lines create visible separations in our gauge.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;And ... we're done! Let's remove the grid-preview and see the final gauge:&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Variations
&lt;/h2&gt;

&lt;p&gt;Let's create a bunch of variations by simply modifying the CSS custom properties.&lt;/p&gt;

&lt;h3&gt;
  
  
  Humidity
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.humidity&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--analog-gauge-bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#8cf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#6bf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#46e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#24c&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--analog-gauge-range&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="m"&gt;#0000&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--analog-gauge-range&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--analog-gauge-start-angle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;270deg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--analog-gauge-range&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;220deg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--analog-gauge-segments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--analog-gauge-values-bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;linear-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="m"&gt;210deg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;light-dark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;#abd7f9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#1e1b40&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;light-dark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;#fff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#333&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="m"&gt;#0000&lt;/span&gt; &lt;span class="m"&gt;85%&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we change the starting degree, add a gradient background to the value-marks and segmentize it heavily:&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Download Speed
&lt;/h3&gt;

&lt;p&gt;Here, we create a variation of the needle, and add a different color after the current value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.download-speed&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--analog-gauge-bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#12c2fc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#6cffd4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="m"&gt;#78ff80&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--analog-gauge-value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0%&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;light-dark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;#ddd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#222&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--analog-gauge-range&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="m"&gt;#0000&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--analog-gauge-range&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--analog-gauge-needle-bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;light-dark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;#445&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#ccc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--analog-gauge-needle-cp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;polygon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="m"&gt;20%&lt;/span&gt; &lt;span class="m"&gt;35%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="m"&gt;80%&lt;/span&gt; &lt;span class="m"&gt;0%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="m"&gt;83%&lt;/span&gt; &lt;span class="m"&gt;35%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="m"&gt;83%&lt;/span&gt; &lt;span class="m"&gt;65%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="m"&gt;80%&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="m"&gt;20%&lt;/span&gt; &lt;span class="m"&gt;65%&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--analog-gauge-segments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h3&gt;
  
  
  UV Index
&lt;/h3&gt;

&lt;p&gt;Next, let's create a gradient with &lt;strong&gt;solid stops&lt;/strong&gt; and a larger gauge-size:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.uv&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--_dg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--analog-gauge-range&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--analog-gauge-segments&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="py"&gt;--analog-gauge-bdw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;25&lt;/span&gt;&lt;span class="n"&gt;cqi&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--analog-gauge-bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; 
    &lt;span class="m"&gt;#55AF33&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_dg&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
    &lt;span class="m"&gt;#A0C61B&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_dg&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; 
    &lt;span class="m"&gt;#F7E98E&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_dg&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; 
    &lt;span class="m"&gt;#F6E301&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_dg&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; 
    &lt;span class="m"&gt;#FAB60D&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_dg&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; 
    &lt;span class="m"&gt;#F88D2F&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_dg&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; 
    &lt;span class="m"&gt;#F76D00&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_dg&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; 
    &lt;span class="m"&gt;#E53015&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_dg&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; 
    &lt;span class="m"&gt;#D90E21&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_dg&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; 
    &lt;span class="m"&gt;#D80010&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_dg&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; 
    &lt;span class="m"&gt;#8A4F9E&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--analog-gauge-range&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
    &lt;span class="m"&gt;#0000&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--analog-gauge-range&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--analog-gauge-segments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h2&gt;
  
  
  Web Component
&lt;/h2&gt;

&lt;p&gt;I've wrapped all the logic in an easy-to-use web component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i @browser.style/analog-gauge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Basic Usage
&lt;/h3&gt;

&lt;p&gt;Import the component in your JavaScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@browser.style/analog-gauge&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the component to your HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Basic gauge with value and range --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;analog-gauge&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"50"&lt;/span&gt; &lt;span class="na"&gt;min=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt; &lt;span class="na"&gt;max=&lt;/span&gt;&lt;span class="s"&gt;"100"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/analog-gauge&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- With label, min and max labels --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;analog-gauge&lt;/span&gt; 
  &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"1032"&lt;/span&gt; 
  &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"hPa"&lt;/span&gt; 
  &lt;span class="na"&gt;min=&lt;/span&gt;&lt;span class="s"&gt;"950"&lt;/span&gt; 
  &lt;span class="na"&gt;max=&lt;/span&gt;&lt;span class="s"&gt;"1050"&lt;/span&gt; 
  &lt;span class="na"&gt;min-label=&lt;/span&gt;&lt;span class="s"&gt;"Low"&lt;/span&gt; 
  &lt;span class="na"&gt;max-label=&lt;/span&gt;&lt;span class="s"&gt;"High"&lt;/span&gt;
  &lt;span class="na"&gt;values=&lt;/span&gt;&lt;span class="s"&gt;"11"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/analog-gauge&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Supported Attributes
&lt;/h3&gt;

&lt;p&gt;The component accepts these attributes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;value&lt;/code&gt;: Current value (number)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;min&lt;/code&gt;: Minimum value (default: 0)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;max&lt;/code&gt;: Maximum value (default: 100)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;suffix&lt;/code&gt;: Text to append after value (e.g., "%", "°")&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;label&lt;/code&gt;: Main label text&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;min-label&lt;/code&gt;: Label for minimum value&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;max-label&lt;/code&gt;: Label for maximum value&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;values&lt;/code&gt;: Specify value markers in two formats:

&lt;ul&gt;
&lt;li&gt;A single number (e.g., "11") to generate evenly spaced markers&lt;/li&gt;
&lt;li&gt;A comma-separated list (e.g., "Low,Mid,High") for custom labels&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;You can see a demo of the web component at &lt;a href="https://browser.style/ui/analog-gauge/" rel="noopener noreferrer"&gt;browser.style/ui/analog-gauge&lt;/a&gt; or at CodePen:&lt;/p&gt;

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

</description>
      <category>css</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Digital Clocks in CSS</title>
      <dc:creator>Mads Stoumann</dc:creator>
      <pubDate>Sat, 22 Mar 2025 13:44:35 +0000</pubDate>
      <link>https://forem.com/madsstoumann/digital-clocks-in-css-3dkg</link>
      <guid>https://forem.com/madsstoumann/digital-clocks-in-css-3dkg</guid>
      <description>&lt;p&gt;In my previous post, I looked into &lt;a href="https://dev.to/madsstoumann/clocks-and-watches-in-css-36mm"&gt;clocks and watches&lt;/a&gt;, mimicking classic, analog watches. Now, how about &lt;em&gt;digital&lt;/em&gt; watches in CSS? The JavaScript initialization is identical, but that’s where the similarities stop.&lt;/p&gt;

&lt;p&gt;Let’s look into how we can animate numbers, automatically switch between “am” and “pm” for 12-hour clocks — and much, much more, using only CSS!&lt;/p&gt;

&lt;h2&gt;
  
  
  Initial
&lt;/h2&gt;

&lt;p&gt;First, we’ll create a web component to keep all logic wrapped up in a single instance. Feel free to simplify it later!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;digital-clock&lt;/span&gt; &lt;span class="na"&gt;timezone=&lt;/span&gt;&lt;span class="s"&gt;"+1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/digital-clock&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The inner, shadow-structure is like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;part=&lt;/span&gt;&lt;span class="s"&gt;"label"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;part=&lt;/span&gt;&lt;span class="s"&gt;"date"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;ol&lt;/span&gt; &lt;span class="na"&gt;part=&lt;/span&gt;&lt;span class="s"&gt;"time"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;part=&lt;/span&gt;&lt;span class="s"&gt;"hours"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;part=&lt;/span&gt;&lt;span class="s"&gt;"minutes"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;part=&lt;/span&gt;&lt;span class="s"&gt;"seconds"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;part=&lt;/span&gt;&lt;span class="s"&gt;"ampm"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ol&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the web component, we’ll later add logic &lt;em&gt;not&lt;/em&gt; to render specific elements.&lt;/p&gt;

&lt;p&gt;For now, let’s focus on the CSS. First, we set 3 custom properties, set with &lt;code&gt;@property&lt;/code&gt;, allowing us to animate them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@property&lt;/span&gt; &lt;span class="n"&gt;--seconds&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;syntax&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"&amp;lt;integer&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;initial-value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;inherits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;@property&lt;/span&gt; &lt;span class="n"&gt;--minutes&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;syntax&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"&amp;lt;integer&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;initial-value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;inherits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;@property&lt;/span&gt; &lt;span class="n"&gt;--hours&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;syntax&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"&amp;lt;integer&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;initial-value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;inherits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we calculate — in JavaScript, from the current time and timezone — animation delays for these:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;updateClock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tzOffset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;roundTzOffset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;timezone&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;utc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getTimezoneOffset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tzTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;utc&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3600000&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;tzOffset&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hours&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tzTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getHours&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;minutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tzTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMinutes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;seconds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tzTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSeconds&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--delay-hours&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;hours&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;minutes&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;s`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--delay-minutes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;minutes&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;s`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--delay-seconds&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;s`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--number-system&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;number-system&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;decimal-leading-zero&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The animations are simple enough — we go from 0 to 24 hours, and 0 to 60 minutes (and seconds):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;hours&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nt"&gt;from&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;--hours&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;to&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;--hours&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;24&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;minutes&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="nt"&gt;from&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;--minutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;to&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;--minutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;seconds&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="nt"&gt;from&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;--seconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;}&lt;/span&gt;
  &lt;span class="nt"&gt;to&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;--seconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Animating numbers
&lt;/h2&gt;

&lt;p&gt;And now for the tricky part! To animate numbers we’re going to use CSS &lt;strong&gt;counters.&lt;/strong&gt; For &lt;code&gt;hours&lt;/code&gt;, &lt;code&gt;minutes&lt;/code&gt; and &lt;code&gt;seconds&lt;/code&gt; we create a custom counter, and then &lt;em&gt;re-set&lt;/em&gt; them with the values we get from the keyframe animations.&lt;/p&gt;

&lt;p&gt;For seconds, that’s:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:host::part&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;seconds&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;seconds&lt;/span&gt; &lt;span class="m"&gt;60s&lt;/span&gt; &lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;infinite&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;animation-delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--delay-seconds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;counter-reset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;seconds&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--seconds&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So what’s going on here? Let’s break it down:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The keyframe animation increments the &lt;code&gt;--seconds&lt;/code&gt; custom property from 0 to 60 over exactly 60 seconds&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;steps(60, end)&lt;/code&gt; timing function creates 60 discrete jumps (one per second) rather than a smooth transition&lt;/li&gt;
&lt;li&gt;Each time &lt;code&gt;--seconds&lt;/code&gt; changes, the &lt;code&gt;counter-reset&lt;/code&gt; property immediately updates the CSS counter named "seconds"&lt;/li&gt;
&lt;li&gt;The counter is displayed using a pseudo-element:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:host::part&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;seconds&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nd"&gt;::after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--number-system&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;decimal-leading-zero&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="s2"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;animation-delay&lt;/code&gt; with a negative value (calculated in JavaScript) synchronizes the animation with the current time, so when the animation shows “42”, it matches the actual 42nd second of the current minute.&lt;/p&gt;

&lt;p&gt;This approach ensures the clock runs entirely through the browser’s animation engine with no further JavaScript needed. This eliminates timing drift that can occur with &lt;code&gt;setInterval()&lt;/code&gt; or &lt;code&gt;setTimeout()&lt;/code&gt; and requires no ongoing DOM manipulation.&lt;/p&gt;

&lt;p&gt;The same principle applies to minutes and hours, each with appropriate durations and step counts. For hours, the animation runs for 24 hours (86,400 seconds) with 24 steps; for minutes, it’s 60 minutes (3,600 seconds) with 60 steps.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;counter()&lt;/code&gt; method’s second parameter defines the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/list-style-type" rel="noopener noreferrer"&gt;list-style-type&lt;/a&gt; or “number system” used to display the values. We default to &lt;code&gt;decimal-leading-zero&lt;/code&gt;, but this can be customized through the &lt;code&gt;number-system&lt;/code&gt; &lt;br&gt;
attribute.&lt;/p&gt;
&lt;h2&gt;
  
  
  12-hour clocks: am and pm
&lt;/h2&gt;

&lt;p&gt;To display time in 12-hour-format, we can use a few CSS tricks! To tell the clock we want 12-hour-format, we add the string “12hour” to the &lt;code&gt;time&lt;/code&gt;-attribute of the web component. &lt;/p&gt;

&lt;p&gt;Next, we use the existing &lt;code&gt;hours&lt;/code&gt; counter for the &lt;code&gt;counter-reset&lt;/code&gt;, as we only want to update this when the hour change.&lt;/p&gt;

&lt;p&gt;To convert 24-hour time to 12-hour format, we use modulus. Modern CSS makes this easy with the &lt;code&gt;mod()&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:host&lt;/span&gt;&lt;span class="o"&gt;([&lt;/span&gt;&lt;span class="nt"&gt;time&lt;/span&gt;&lt;span class="o"&gt;*=&lt;/span&gt;&lt;span class="s1"&gt;"12hour"&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt;&lt;span class="nd"&gt;::part&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;hours&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;counter-reset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hours&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--hours&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;We subtract 1 from the hour (shifting 1-24 to 0-23)&lt;/li&gt;
&lt;li&gt;Apply modulo 12 (giving us 0-11)&lt;/li&gt;
&lt;li&gt;Add 1 back (giving us 1-12)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This ensures that 0 becomes 12, 13 becomes 1, 23 becomes 11, etc.&lt;/p&gt;

&lt;p&gt;To show ”am” or ”pm” after the time, we create a custom counter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@counter-style&lt;/span&gt; &lt;span class="n"&gt;am-pm&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;system&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cyclic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;symbols&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"am"&lt;/span&gt; &lt;span class="s1"&gt;"am"&lt;/span&gt; &lt;span class="s1"&gt;"am"&lt;/span&gt; &lt;span class="s1"&gt;"am"&lt;/span&gt; &lt;span class="s1"&gt;"am"&lt;/span&gt; &lt;span class="s1"&gt;"am"&lt;/span&gt; &lt;span class="s1"&gt;"am"&lt;/span&gt; &lt;span class="s1"&gt;"am"&lt;/span&gt; &lt;span class="s1"&gt;"am"&lt;/span&gt; &lt;span class="s1"&gt;"am"&lt;/span&gt; &lt;span class="s1"&gt;"am"&lt;/span&gt; &lt;span class="s1"&gt;"pm"&lt;/span&gt; 
           &lt;span class="s1"&gt;"pm"&lt;/span&gt; &lt;span class="s1"&gt;"pm"&lt;/span&gt; &lt;span class="s1"&gt;"pm"&lt;/span&gt; &lt;span class="s1"&gt;"pm"&lt;/span&gt; &lt;span class="s1"&gt;"pm"&lt;/span&gt; &lt;span class="s1"&gt;"pm"&lt;/span&gt; &lt;span class="s1"&gt;"pm"&lt;/span&gt; &lt;span class="s1"&gt;"pm"&lt;/span&gt; &lt;span class="s1"&gt;"pm"&lt;/span&gt; &lt;span class="s1"&gt;"pm"&lt;/span&gt; &lt;span class="s1"&gt;"pm"&lt;/span&gt; &lt;span class="s1"&gt;"am"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;cyclic&lt;/code&gt; system cycles through these values as hours increment, ensuring the correct am/pm indicator is shown for each hour of the day. Note that the 12th string is &lt;code&gt;pm&lt;/code&gt; and 24th is &lt;code&gt;am&lt;/code&gt;, so 12 (noon) becomes &lt;code&gt;12pm&lt;/code&gt; and 12 (midnight) becomes &lt;code&gt;12am&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To show it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:host::part&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;ampm&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nd"&gt;::after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;am-pm&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Using the DigitalClock Web Component
&lt;/h2&gt;

&lt;p&gt;Wrapping the code in a &lt;code&gt;&amp;lt;digital-clock&amp;gt;&lt;/code&gt; web component offers a simple way to add a digital clock to your web projects. It’s customizable through various attributes and CSS custom properties.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation &amp;amp; Usage
&lt;/h3&gt;

&lt;p&gt;Install via npm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i @browser.style/digital-clock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or use directly via CDN:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://browser.style/ui/digital-clock/index.js"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, simply add the component to your HTML — here, a basic clock with UTC time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;digital-clock&amp;gt;&amp;lt;/digital-clock&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The DigitalClock component offers several attributes for customization:&lt;/p&gt;

&lt;h3&gt;
  
  
  Available Attributes
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;timezone&lt;/strong&gt;: Sets the timezone offset (e.g., "+1", "-4")&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;label&lt;/strong&gt;: Adds a text label before the clock (e.g., "Berlin,")&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;date&lt;/strong&gt;: Shows the date in different formats: "full", "short", or "narrow"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;lang&lt;/strong&gt;: Sets the language for date formatting (e.g., "en-US", "ja-JP")&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;time&lt;/strong&gt;: Configures time display format, options include "12hour" and "short" (no seconds)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;number-system&lt;/strong&gt;: Changes number display format (e.g., "decimal-leading-zero", "arabic-indic", "hiragana")&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Usage Examples
&lt;/h3&gt;

&lt;p&gt;Clock with timezone, label and full date:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;digital-clock&lt;/span&gt;
  &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Berlin"&lt;/span&gt;
  &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"de"&lt;/span&gt;
  &lt;span class="na"&gt;date=&lt;/span&gt;&lt;span class="s"&gt;"full"&lt;/span&gt;
  &lt;span class="na"&gt;timezone=&lt;/span&gt;&lt;span class="s"&gt;"+1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/digital-clock&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;12-hour format with short time (no seconds):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;digital-clock&lt;/span&gt;
  &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"New York"&lt;/span&gt;
  &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en-US"&lt;/span&gt;
  &lt;span class="na"&gt;date=&lt;/span&gt;&lt;span class="s"&gt;"short"&lt;/span&gt;
  &lt;span class="na"&gt;timezone=&lt;/span&gt;&lt;span class="s"&gt;"-4"&lt;/span&gt;
  &lt;span class="na"&gt;time=&lt;/span&gt;&lt;span class="s"&gt;"12hour short"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/digital-clock&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h2&gt;
  
  
  Different number systems
&lt;/h2&gt;

&lt;p&gt;Now the cool thing about CSS list-types are the &lt;em&gt;many&lt;/em&gt; different number systems that just work out-of-the-box.&lt;/p&gt;

&lt;p&gt;Let's add some color and create some cool examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;digital-clock&lt;/span&gt;
  &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"東京"&lt;/span&gt;
  &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ja-JP"&lt;/span&gt;
  &lt;span class="na"&gt;number-system=&lt;/span&gt;&lt;span class="s"&gt;"hiragana"&lt;/span&gt;
  &lt;span class="na"&gt;timezone=&lt;/span&gt;&lt;span class="s"&gt;"+9"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/digital-clock&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;digital-clock&lt;/span&gt;
  &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"الرياض"&lt;/span&gt;
  &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ar-SA"&lt;/span&gt;
  &lt;span class="na"&gt;date=&lt;/span&gt;&lt;span class="s"&gt;"full"&lt;/span&gt;
  &lt;span class="na"&gt;number-system=&lt;/span&gt;&lt;span class="s"&gt;"arabic-indic"&lt;/span&gt;
  &lt;span class="na"&gt;timezone=&lt;/span&gt;&lt;span class="s"&gt;"+3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/digital-clock&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Or how about a clock with roman numerals?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;digital-clock&lt;/span&gt;
  &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Roma"&lt;/span&gt;
  &lt;span class="na"&gt;number-system=&lt;/span&gt;&lt;span class="s"&gt;"upper-roman"&lt;/span&gt; 
  &lt;span class="na"&gt;timezone=&lt;/span&gt;&lt;span class="s"&gt;"+2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/digital-clock&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h3&gt;
  
  
  Styling
&lt;/h3&gt;

&lt;p&gt;The component exposes several CSS custom properties for styling — I might add more later (let me know if you need more control):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--digital-clock-bg&lt;/code&gt;: Background color&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--digital-clock-bdrs&lt;/code&gt;: Border radius&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--digital-clock-gap&lt;/code&gt;: Gap between elements&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--digital-clock-p&lt;/code&gt;: Padding&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--digital-clock-fs&lt;/code&gt;: Font size&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--digital-clock-fw&lt;/code&gt;: Font weight&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--digital-clock-date-ff/fs/fw&lt;/code&gt;: Font properties for the date&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--digital-clock-label-ff/fs/fw&lt;/code&gt;: Font properties for the label&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All the parts (pun intended) of the clock have &lt;code&gt;::part&lt;/code&gt;s, so you can easily style the shadow parts from outside:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.hiragana&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#080808&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#ff1a1a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ui-sans-serif&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;system-ui&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;300px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="err"&gt;&amp;amp;::part(time)&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt; 
    &lt;span class="nl"&gt;text-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;5px&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Here’s a CodePen with all the clocks we've been making (and a few extra!):&lt;/p&gt;

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

</description>
      <category>webdev</category>
      <category>css</category>
      <category>javascript</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Clocks and Watches in CSS</title>
      <dc:creator>Mads Stoumann</dc:creator>
      <pubDate>Tue, 25 Feb 2025 13:46:44 +0000</pubDate>
      <link>https://forem.com/madsstoumann/clocks-and-watches-in-css-36mm</link>
      <guid>https://forem.com/madsstoumann/clocks-and-watches-in-css-36mm</guid>
      <description>&lt;p&gt;A couple of years ago, when CSS trigonometry functions became baseline, I &lt;a href="https://css-tricks.com/creating-a-clock-with-the-new-css-sin-and-cos-trigonometry-functions/" rel="noopener noreferrer"&gt;wrote an article&lt;/a&gt; about them. One of the examples I did, was a CSS-only analog clock:&lt;/p&gt;

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

&lt;p&gt;Since then, CSS has introduced a &lt;em&gt;bunch&lt;/em&gt; of new features — one being &lt;code&gt;offset-path&lt;/code&gt;, which is perfect for creating indices on a clock (I sound like an horology expert, but I Googled that).&lt;/p&gt;

&lt;p&gt;So, without further ado, let's expand my old example with some more, cool features! We'll wrap it within a Web Component for easier customization, but you can stick with CSS-only, if you want.&lt;/p&gt;




&lt;p&gt;First, we set up a simple grid, divided into 3 rows:&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:host&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;aspect-ratio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#f2f2f2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;grid-template-rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The indices are a bunch of &lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt; elements within a &lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt;, using &lt;code&gt;offset-distance / path&lt;/code&gt; to place them around the circle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inline-block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;list-style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;offset-distance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_d&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;offset-path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;content-box&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;fit-content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each &lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt; has a degree (actually a percentage), defined  in the &lt;code&gt;--_d&lt;/code&gt; custom property:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"--_d:0%"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;|&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gets us:&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;By default, &lt;code&gt;offset-rotate&lt;/code&gt; automatically rotates elements to follow the path direction. This behavior is exactly what we need for the indices, so we don't need to set any additional rotation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now, for the numerals, we'll also use &lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt;, but this time within an &lt;em&gt;ordered&lt;/em&gt; list, &lt;code&gt;&amp;lt;ol&amp;gt;&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;ol&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"--_d:300deg"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;1&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ol&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll use &lt;code&gt;cos()&lt;/code&gt; and &lt;code&gt;sin()&lt;/code&gt; to place the numerals, like in my original example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--_r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="m"&gt;100%&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt;&lt;span class="n"&gt;cqi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--_x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_d&lt;/span&gt;&lt;span class="p"&gt;))));&lt;/span&gt;
  &lt;span class="py"&gt;--_y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="n"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_d&lt;/span&gt;&lt;span class="p"&gt;))));&lt;/span&gt;
  &lt;span class="py"&gt;aspect-ratio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;place-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt;&lt;span class="n"&gt;cqi&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we get:&lt;/p&gt;

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

&lt;p&gt;Now, let's create the markup for the hands and date. The cap will be added as a pseudo-element. I had a hard time trying to wrap my head around what good, semantic markup would be here? I gave up, and just used a bunch of &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;s 😄&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;nav&lt;/span&gt; &lt;span class="na"&gt;part=&lt;/span&gt;&lt;span class="s"&gt;"hands"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;part=&lt;/span&gt;&lt;span class="s"&gt;"seconds"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;part=&lt;/span&gt;&lt;span class="s"&gt;"minutes"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;part=&lt;/span&gt;&lt;span class="s"&gt;"hours"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;time&lt;/span&gt; &lt;span class="na"&gt;part=&lt;/span&gt;&lt;span class="s"&gt;"date"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/time&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/nav&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We position the &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt; in the middle row of the main grid, and create a 3-column grid:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:host::part&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;hands&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;grid-area&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Finally, we place the label at the top center of the last row of the main grid:&lt;/p&gt;

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


&lt;h2&gt;
  
  
  Animating the hands
&lt;/h2&gt;

&lt;p&gt;To animate the hands, we just need a single animation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;turn&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nt"&gt;to&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1turn&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, it needs to be &lt;strong&gt;called&lt;/strong&gt; in 3 very distinct ways:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:host::part&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;hours&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;turn&lt;/span&gt; &lt;span class="m"&gt;43200s&lt;/span&gt; &lt;span class="n"&gt;linear&lt;/span&gt; &lt;span class="n"&gt;infinite&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;animation-delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_dh&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0ms&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nd"&gt;:host::part&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;minutes&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;turn&lt;/span&gt; &lt;span class="m"&gt;3600s&lt;/span&gt; &lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;infinite&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;animation-delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_dm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0ms&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nd"&gt;:host::part&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;seconds&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;turn&lt;/span&gt; &lt;span class="m"&gt;60s&lt;/span&gt; &lt;span class="n"&gt;linear&lt;/span&gt; &lt;span class="n"&gt;infinite&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;animation-delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--_ds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0ms&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it! ... if you don't mind the clock always starting at noon!&lt;/p&gt;

&lt;p&gt;To initialize the clock with the &lt;em&gt;actual&lt;/em&gt; time, we need to update the delay properties: &lt;code&gt;--_dh&lt;/code&gt;, &lt;code&gt;--_dm&lt;/code&gt; and &lt;code&gt;--_ds&lt;/code&gt; — and for that, we need a small snippet of JavaScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;3600&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getHours&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mins&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMinutes&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--_dm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;mins&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;s`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--_dh&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${(&lt;/span&gt;&lt;span class="nx"&gt;hour&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;mins&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;s`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Variants
&lt;/h2&gt;

&lt;p&gt;Styling variants is dead simple (see the final demo at the end of the article).&lt;/p&gt;

&lt;p&gt;How about a SAIKO:&lt;/p&gt;

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

&lt;p&gt;Or a ROBEX (sorry for my unimaginative names!):&lt;/p&gt;

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

&lt;p&gt;... or how about some &lt;em&gt;really&lt;/em&gt; colorful examples:&lt;/p&gt;

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

&lt;p&gt;The latter can, of course, be done by adding the labels manually, but if we wrap it in a web component, it becomes a bit easier to maintain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;analog-clock&lt;/span&gt;
  &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"မြန်မာ"&lt;/span&gt;
  &lt;span class="na"&gt;system=&lt;/span&gt;&lt;span class="s"&gt;"mymr"&lt;/span&gt;
  &lt;span class="na"&gt;timezone=&lt;/span&gt;&lt;span class="s"&gt;"+6.5"&lt;/span&gt;
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"burmese"&lt;/span&gt;
  &lt;span class="na"&gt;indices&lt;/span&gt;
  &lt;span class="na"&gt;marker=&lt;/span&gt;&lt;span class="s"&gt;"•"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/analog-clock&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;analog-clock&lt;/span&gt;
  &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"ประเทศไทย"&lt;/span&gt;
  &lt;span class="na"&gt;system=&lt;/span&gt;&lt;span class="s"&gt;"thai"&lt;/span&gt;
  &lt;span class="na"&gt;timezone=&lt;/span&gt;&lt;span class="s"&gt;"+7"&lt;/span&gt;
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"thai"&lt;/span&gt;
  &lt;span class="na"&gt;indices&lt;/span&gt;
  &lt;span class="na"&gt;marker=&lt;/span&gt;&lt;span class="s"&gt;"·"&lt;/span&gt;
  &lt;span class="na"&gt;marker-hour=&lt;/span&gt;&lt;span class="s"&gt;"•"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/analog-clock&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;analog-clock&lt;/span&gt;
  &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"अरुणाचल"&lt;/span&gt;
  &lt;span class="na"&gt;system=&lt;/span&gt;&lt;span class="s"&gt;"wcho"&lt;/span&gt;
  &lt;span class="na"&gt;timezone=&lt;/span&gt;&lt;span class="s"&gt;"+5.5"&lt;/span&gt;
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"indian"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/analog-clock&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's look into that.&lt;/p&gt;




&lt;h2&gt;
  
  
  Web Component
&lt;/h2&gt;

&lt;p&gt;Wrapping the code in a &lt;code&gt;&amp;lt;analog-clock&amp;gt;&lt;/code&gt; web component offers a simple way to add an analog clock to your web projects. It's  customizable through various attributes and CSS custom properties.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation &amp;amp; Usage
&lt;/h3&gt;

&lt;p&gt;Install via npm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i @browser.style/analog-clock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or use directly via CDN:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://browser.style/ui/analog-clock/index.js"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, simply add the component to your HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;analog-clock&amp;gt;&amp;lt;/analog-clock&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Basic Examples
&lt;/h3&gt;

&lt;p&gt;Here are some common use cases:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Simple clock for New York time --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;analog-clock&lt;/span&gt; 
  &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"New York"&lt;/span&gt; 
  &lt;span class="na"&gt;timezone=&lt;/span&gt;&lt;span class="s"&gt;"-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/analog-clock&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Clock with date display and minute markers --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;analog-clock&lt;/span&gt; 
  &lt;span class="na"&gt;indices&lt;/span&gt; 
  &lt;span class="na"&gt;date=&lt;/span&gt;&lt;span class="s"&gt;"day month"&lt;/span&gt; 
  &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Current Time"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/analog-clock&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Clock with custom markers and Roman numerals --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;analog-clock&lt;/span&gt; 
  &lt;span class="na"&gt;indices=&lt;/span&gt;&lt;span class="s"&gt;"hours"&lt;/span&gt;
  &lt;span class="na"&gt;system=&lt;/span&gt;&lt;span class="s"&gt;"roman"&lt;/span&gt;
  &lt;span class="na"&gt;marker=&lt;/span&gt;&lt;span class="s"&gt;"•"&lt;/span&gt;
  &lt;span class="na"&gt;marker-hour=&lt;/span&gt;&lt;span class="s"&gt;"●"&lt;/span&gt;
  &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Roma"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/analog-clock&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Styling Examples
&lt;/h3&gt;

&lt;p&gt;The component can be styled using CSS custom properties:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* Gold luxury theme */&lt;/span&gt;
&lt;span class="nc"&gt;.luxury&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--analog-clock-bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;radial-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;circle&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="m"&gt;#f4e5c3&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="m"&gt;#e2ca7d&lt;/span&gt; &lt;span class="m"&gt;51%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="m"&gt;#5c4d28&lt;/span&gt; &lt;span class="m"&gt;95%&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--analog-clock-c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#2a2317&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--analog-clock-ff&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"Didot"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--analog-clock-second&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#8b0000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--analog-clock-cap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#403428&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* Minimalist theme */&lt;/span&gt;
&lt;span class="nc"&gt;.minimal&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--analog-clock-bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#fff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--analog-clock-c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#333&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--analog-clock-indices-c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#ddd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--analog-clock-second&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#ff4444&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--analog-clock-cap-sz&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="n"&gt;cqi&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Number Systems
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;system&lt;/code&gt; attribute supports various &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#numberingsystem" rel="noopener noreferrer"&gt;number systems&lt;/a&gt;, as we saw in the colorful examples earlier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;analog-clock&lt;/span&gt; &lt;span class="na"&gt;system=&lt;/span&gt;&lt;span class="s"&gt;"mymr"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/analog-clock&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;analog-clock&lt;/span&gt; &lt;span class="na"&gt;system=&lt;/span&gt;&lt;span class="s"&gt;"thai"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/analog-clock&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Timezone Support
&lt;/h3&gt;

&lt;p&gt;You can display different timezones using the &lt;code&gt;timezone&lt;/code&gt; attribute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;analog-clock&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"New York"&lt;/span&gt; &lt;span class="na"&gt;timezone=&lt;/span&gt;&lt;span class="s"&gt;"-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/analog-clock&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;analog-clock&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"London"&lt;/span&gt; &lt;span class="na"&gt;timezone=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/analog-clock&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;analog-clock&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Tokyo"&lt;/span&gt; &lt;span class="na"&gt;timezone=&lt;/span&gt;&lt;span class="s"&gt;"+9"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/analog-clock&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;analog-clock&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Mumbai"&lt;/span&gt; &lt;span class="na"&gt;timezone=&lt;/span&gt;&lt;span class="s"&gt;"+5.5"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/analog-clock&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Attributes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;date&lt;/code&gt;: Display date. Values: "day", "month", "year" or any combination&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;indices&lt;/code&gt;: Show tick marks. Values: empty (60 marks) or "hours" (12 marks)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;label&lt;/code&gt;: Text label below the clock&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;marker&lt;/code&gt;: Character used for indices (default: "|")&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;marker-hour&lt;/code&gt;: Character used for hour indices (defaults to marker value)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;numerals&lt;/code&gt;: Number of numerals to display (1-12, default: 12)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;steps&lt;/code&gt;: Use stepping animation for seconds hand&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;system&lt;/code&gt;: Number system. Values: "roman", "romanlow", or any valid &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#numberingsystem" rel="noopener noreferrer"&gt;Intl numberingSystem&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;timezone&lt;/code&gt;: UTC offset in hours (e.g., "-4", "+1", "+5.5")&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Here's a Codepen with all the clocks and watches, we've coded:&lt;/p&gt;

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

&lt;p&gt;Now go teach kids how to read an analog clock!&lt;/p&gt;

</description>
      <category>css</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>The Unofficial Dev.To Embed Web Component</title>
      <dc:creator>Mads Stoumann</dc:creator>
      <pubDate>Tue, 11 Feb 2025 09:38:37 +0000</pubDate>
      <link>https://forem.com/madsstoumann/the-unofficial-devto-embed-web-component-1hjk</link>
      <guid>https://forem.com/madsstoumann/the-unofficial-devto-embed-web-component-1hjk</guid>
      <description>&lt;p&gt;The other day, I re-discovered the dev.to API and found out that it can be used &lt;em&gt;without&lt;/em&gt; an API key. That’s really great if you want to embed your articles anywhere — maybe on a personal blog — without the hassle of protecting secret keys. You can fetch a list of articles by a given author or a specific article. &lt;/p&gt;

&lt;p&gt;So, I coded a web component that lets you grab either a list of articles &lt;em&gt;or&lt;/em&gt; a specific one. Let’s start with the “list mode”:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dev-to&lt;/span&gt;
  &lt;span class="na"&gt;author=&lt;/span&gt;&lt;span class="s"&gt;"madsstoumann"&lt;/span&gt;
  &lt;span class="na"&gt;theme=&lt;/span&gt;&lt;span class="s"&gt;"classic"&lt;/span&gt;
  &lt;span class="na"&gt;itemsperpage=&lt;/span&gt;&lt;span class="s"&gt;"10"&lt;/span&gt;
  &lt;span class="na"&gt;links=&lt;/span&gt;&lt;span class="s"&gt;"internal"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dev-to&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here’s what we get:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flu2gxbad1swk918ymg97.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flu2gxbad1swk918ymg97.png" alt="List" width="672" height="889"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice the &lt;code&gt;theme&lt;/code&gt; and &lt;code&gt;links&lt;/code&gt; attributes.&lt;/p&gt;

&lt;p&gt;I’ve added 3 themes to choose from (but you can easily add your own):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;brutalist&lt;/li&gt;
&lt;li&gt;classic&lt;/li&gt;
&lt;li&gt;modern&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;links&lt;/code&gt; can be either “internal” or “external”. The first opens the article directly within the web component, while the latter opens the article on dev.to in a new tab.&lt;/p&gt;

&lt;p&gt;Now, let’s click on “The Golden Ratio in CSS”.&lt;/p&gt;

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

&lt;p&gt;Very posh, classic theme, isn’t it?! &lt;/p&gt;

&lt;p&gt;If you just want to show the article directly, without showing the list first, you can use the component in “article mode”:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dev-to&lt;/span&gt; &lt;span class="na"&gt;article=&lt;/span&gt;&lt;span class="s"&gt;"2014104"&lt;/span&gt; &lt;span class="na"&gt;theme=&lt;/span&gt;&lt;span class="s"&gt;"classic"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/dev-to&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s change the theme to “modern”:&lt;/p&gt;

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

&lt;p&gt;... or how about “brutalist”, for that raw, industrial look?&lt;/p&gt;

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




&lt;h2&gt;
  
  
  Internationalization
&lt;/h2&gt;

&lt;p&gt;If you want to display dates and UI text in your local language, the component supports i18n this way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dev-to&lt;/span&gt; 
  &lt;span class="na"&gt;author=&lt;/span&gt;&lt;span class="s"&gt;"madsstoumann"&lt;/span&gt;
  &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"es"&lt;/span&gt;
  &lt;span class="na"&gt;i18n=&lt;/span&gt;&lt;span class="s"&gt;'{"es":{"more":"Más...","reactions":"reacciones"}}'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dev-to&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Customization
&lt;/h2&gt;

&lt;p&gt;The component exposes several parts that you can style using CSS &lt;code&gt;::part()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;dev-to&lt;/span&gt;&lt;span class="nd"&gt;::part&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;avatar&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="n"&gt;currentColor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;dev-to&lt;/span&gt;&lt;span class="nd"&gt;::part&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;cover&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sepia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;dev-to&lt;/span&gt;&lt;span class="nd"&gt;::part&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;more&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#ff00ff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Shadow DOM or Not?
&lt;/h2&gt;

&lt;p&gt;If you need to style the component directly (without shadowDOM and parts), just add the &lt;code&gt;noshadow&lt;/code&gt; attribute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dev-to&lt;/span&gt; &lt;span class="na"&gt;author=&lt;/span&gt;&lt;span class="s"&gt;"madsstoumann"&lt;/span&gt; &lt;span class="na"&gt;noshadow&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/dev-to&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;You can try out a demo, using &lt;em&gt;external&lt;/em&gt; links, here: &lt;a href="https://browser.style/ui/dev-to" rel="noopener noreferrer"&gt;browser.style/ui/dev-to&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;You can install the component directly from npm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i @browser.style/dev-to
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or import the script directly from browser.style:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script
  &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt;
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://browser.style/ui/dev-to/index.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let me know what you think!&lt;/p&gt;




&lt;p&gt;Cover Photo by Markus Spiske: &lt;a href="https://www.pexels.com/photo/mork-computer-gron-software-225769/" rel="noopener noreferrer"&gt;https://www.pexels.com/photo/mork-computer-gron-software-225769/&lt;/a&gt;&lt;/p&gt;

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