<?xml version="1.0" encoding="utf-8"?>
  <rss version="2.0"
    xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:wfw="http://wellformedweb.org/CommentAPI/"
    xmlns:dc="http://purl.org/dc/elements/1.1/"
    xmlns:atom="http://www.w3.org/2005/Atom"
    xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
    xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
    xmlns:georss="http://www.georss.org/georss"
    xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"
  >
    <channel>
      <title>Piccalilli - HTML topic archive</title>
      <link>https://piccalil.li/</link>
      <atom:link href="https://piccalil.li/category/html.xml" rel="self" type="application/rss+xml" />
      <description>We are Piccalilli. A publication dedicated to providing high quality educational content to level up your front-end skills.</description>
      <language>en-GB</language>
      <copyright>Piccalilli - HTML topic archive 2026</copyright>
      <docs>https://www.rssboard.org/rss-specification</docs>
      <pubDate>Tue, 12 May 2026 00:06:55 GMT</pubDate>
      <lastBuildDate>Tue, 12 May 2026 00:06:55 GMT</lastBuildDate>

      
      <item>
        <title>The end of responsive images</title>
        <link>https://piccalil.li/blog/the-end-of-responsive-images/?ref=html-category-rss-feed</link>
        <dc:creator><![CDATA[Mat Marquis]]></dc:creator>
        <pubDate>Thu, 23 Apr 2026 11:45:00 GMT</pubDate>
        <guid isPermaLink="true">https://piccalil.li/blog/the-end-of-responsive-images/?ref=html-category-rss-feed</guid>
        <description><![CDATA[<p>I’ve been waiting for fourteen years to write this article. <em>Fourteen years</em> to tell you about one relatively new addition to the way images work on the web. For you, just a handful of characters will mean improvements to the fundamental ergonomics of working with images. For users, it will mean invisible, seamless, and potentially <em>massive</em> improvements to front-end performance, forever stitched into the fabric of the web. For me, it means the time has finally come to confess to my sinister machinations — a confession almost a decade and a half in the making.</p>
<p>Back then, I was the esteemed Chair of <a href="https://www.w3.org/community/respimg/">the RICG</a> — the "pirate radio" web standards body responsible for bringing responsive image markup to the web platform. Some of you remember. Some of you were there at the advent of responsive web design, helping to find brand new use cases where the web platform fell short — as a scrappy band of front-end specialists rallied, organized, and crashed headlong into a web standards process that did not welcome them. We demanded a seat at the table alongside browser vendors, representing the needs of web designers and developers and the users we served. Our numbers swelled to the hundreds, and after years of iteration, countless scrapped draft specifications and prototypes, and endless arguments-turned-consensus across antique mailing lists and IRC channels, we finally arrived at a workable syntax hand-in-hand with browser vendors. Then we made it <em>real</em> — raised money from the community to fund independently-developed implementations in browsers, built the polyfills that would drive adoption, wired these new features up major CMSs, wrote articles and gave talks, and distributed — if I may say so — some of the best t-shirts the web standards game has ever seen.</p>
<p>I imagine just as many of you weren't there for any of that, as ancient as that history is in web development terms. For you, responsive image markup has been around as long as you've been making websites — a dense, opaque, inexorable, <em>inescapable</em> aspect of the web platform, an arcane syntax and a constant source of frustration.</p>
<p>If you're in the latter group, well, please allow me to introduce myself: <em>I did that</em>. Right here; eyes front — <em>me</em>.</p>
<p></p>
<p>Every time you tried and failed to figure out why the browser was selecting a certain source from <code>srcset</code>? You didn’t know it, but I was the one putting you through it. Every time you had to pull in some enormous third-party library to deal with a syntax very clearly not designed to be parsed by any human? Not only was I the cause, hell, I might have helped <em>write</em> it. When you ran some workflow-obliterating bookmarklet in hopes of generating a <code>sizes</code> value that mostly, <em>kind of</em> matched the reality of your layouts? When it was all too much; when you threw up your hands — gave up — and instead found yourself foisting huge source files upon countless users who might never see any practical benefit, but would bear all the performance costs? None of that was your fault. That was all me. Not only did I not <em>stop</em> these syntaxes from being standardized, I was the <em>flag-bearer</em> for responsive images — I fought <em>tooth-and-nail</em> for the markup you’ve cursed.</p>
<p>Oh-ho, and as if that wasn't enough, here's the part that will really make you mad: <em>I hate it all too</em>.</p>
<p>Every talk I gave and article I wrote on the subject — <a href="https://web.dev/learn/images">the <em>course</em></a> I wrote about images, the <a href="https://abookapart.com/products/image-performance"><em>entire book</em></a> I wrote about images — all done through gritted teeth. There are parts of this syntax that I've hated since the moment I first set eyes on them — which, again, was the very same moment that I became their most vocal champion. I'm not sorry. I'd do it again.</p>
<h2>The Beast</h2>
<p>Don’t get me wrong: I don't hate <em>responsive images</em>. The problem needed solving, there are no two ways about that. Then, as now, <a href="https://httparchive.org/reports/state-of-the-web?start=latest#bytesperpage">the vast majority of a website's transfer size</a> is in images. A flexible image requires an image source large enough to cover the largest size it will occupy in a layout — without responsive images, an image designed to occupy a space in a layout that's, say, two thousand pixels wide at its largest layout sizes would mean serving <em>every</em> user an image source at least two thousand pixels wide. Scaling that image down to suit a smaller display is trivial in CSS, but the <em>request</em> remains the same — the user bears all the transfer costs, but sees no benefit from an enormous image source.</p>
<p>Remember, too, that this problem stems from an era where sub-3G connections were still common. There was no reliable way to tailor those requests to a user's browsing context in a way that maintained browser-level performance optimizations — and ultimately, the solutions we got were effective, performant, and have saved <em>unfathomable</em> amounts of bandwidth for users. Responsive images, as a concept, are an <em>incredible</em> addition to the web platform. I'm proud to have been able to play a small part in it.</p>
<p>Hell, it's not even that I wholesale don't like the responsive image <em>syntaxes</em>. Not all of them, anyway. <code>picture</code> I liked from the very beginning. Granted, that's a <strong>prescriptive syntax</strong>, and it represents a very different set of use cases from "I just want fast images." The <code>picture</code> element is for <em>control</em> — the siren song that has called out to designers and developers of all stripes since time immemorial, and I’m no exception. Control over sources, control over the conditions used to determine whether they're requested, even control over whether the browser should bail out of the source selection algorithm entirely to the tune of "nevermind, don't load any source" — it took me a while to come around on that last one, but I got there.</p>
<p>What's not to like? Who wouldn't want that level of fine-grained control? Not only that, but <code>picture</code> made it possible to responsibly serve brand new image formats with fast, reliable fallbacks across browsers, opening the door for incredible advances in encoding and compression without the need for a single scrap of JavaScript. The syntax makes perfect, readable sense, it provides us with a template <a href="https://scottjehl.com/posts/responsive-video/">for standardizing smarter decisions around <em>all</em> media requests</a>, and it grows ever more powerful as more and more media queries are added to the platform. <code>picture</code> is great. I like <code>picture</code>; everyone likes <code>picture</code>. We're not here to talk about <code>picture</code>.</p>
<p></p>
<p><code>picture</code> is something altogether different from <code>srcset</code> and <code>sizes</code>, which represent a <strong>descriptive syntax</strong>. You use <code>srcset</code> to provide the browser with information about a set of image sources, identical apart from their dimensions, and <code>sizes</code> to provide the browser with information about how the image will be rendered, and at no point do you use either to tell the browser what to <em>do</em> with any of it. Once given this information, the browser can then use it to do exactly one (1) very complicated thing: determine the image source most appropriate for that user's browsing context. Visually, the source selected from the list of candidates in <code>srcset</code> doesn't matter to the user — the sources will all <em>look</em> the same — but the chosen candidate will best fit the user’s browsing context. You don't get any control over how that decision is made. In fact, you don't even get to <em>know</em> how that decision is made, by design — right down to an "explicitly vague" step in the source selection algorithm, carved into the HTML specification itself:</p>
<blockquote>
<p>In an implementation-defined manner, choose one image source from <em>sourceSet</em>.</p>
<p>— <a href="https://html.spec.whatwg.org/multipage/images.html#selecting-an-image-source">Source</a></p>
</blockquote>
<blockquote>
<p>If something is said to be implementation-defined, the particulars of what is said to be implementation-defined are up to the implementation. In the absence of such language, the reverse holds: implementations have to follow the rules laid out in documents using this standard.</p>
<p>— <a href="https://infra.spec.whatwg.org/#implementation-defined">Source</a></p>
</blockquote>
<p>Unsettling, isn’t it? “Then the browser,” in strict technical terms, “just does <em>whatever</em>.” That formally codified lack of control didn't just <em>happen</em>; that buck could have stopped with me, but no. Instead, I personally thumbs-upped the decision that you should not have any say in how <code>srcset</code>/<code>sizes</code> work — that you can’t even <em>know</em> how they work. Now, after all these years — with this, the reveal that I’ve been the villain of the story all along — I can finally tell you why. You’re not gonna like it one bit, either. It’s because I know you would have done it wrong.</p>
<h2>A human work</h2>
<p>Don't take it too personally, I would've done it wrong too. Hell, I <em>did</em> do it wrong, through countless proposals and prototypes, in search of a solution that could be standardized — everybody did. In the end, all that iteration only proved that <em>nobody</em> could have gotten this part right. That "one thing" that <code>srcset</code>/<code>sizes</code> does — determining the image source best tailored to a user's browsing context, including viewport size, display density, user preferences, bandwidth, and countless other potentially unknowable factors? Those factors include things we <em>can't</em> know, and just as many things we <em>shouldn’t</em> know.</p>
<p>For example, we can't tailor asset delivery to a user's connection speed, which seems like a shame. For a moment, though, let's imagine we could — imagine we were able to say "use <em>that</em> source above this speed, and <em>that</em> source below it." Now that those decisions are yours to control: what connection speed thresholds would <em>you</em> set for your image sources, and what would I set for mine? They're different, I bet. That means that for a given connection speed, a user might get beautiful but bandwidth-obliterating image sources on one site, and highly compressed but wonderfully efficient ones on the next one. Which of those does that user actually <em>want</em>? Well, trick question, they'd all want something different, wouldn't they? What would your <em>organization</em> want? Uh oh. Everyone is looking to you now — you, with the open tickets, and a meeting in half an hour, and all this <em>control</em> foisted upon you by the specification. Why does the website feel so slow? Why do our images look worse than our competitors' now? <em>Why does the website feel so slow again?</em> Even when we're <em>only</em> considering connection speed, the cost of our having more control is the user giving up <em>theirs</em>, and that's before we've considered every other factor <em>besides</em> connection speed.</p>
<p>I didn't want that; I didn't want that for the people who build the web, I didn't want that for people using the web, and I sure as hell didn't want to see the web itself buckle under the strain of a million massive image files backed by a hundred thousand <code>figure out our responsive images policy in excruciating detail when we have time</code> issues buried in trackers forever.</p>
<p></p>
<p>The browser has access to a lot more information than we do — certainly more than we should reasonably <em>want</em> access to — so it can make decisions about screen size and display density and bandwidth and user preferences and any number of future factors we can't even imagine, without making any of it our problem. The browser can decide how to finesse details, like avoiding wasted requests by retaining larger sources rather than requesting functionally identical smaller ones if the larger sources already exist in the cache — I wouldn't want to own that logic. The browser can poll preferences set by a user, to give <em>them</em> control over these decisions and ensure a consistent experience from one site to the next.</p>
<p>Ultimately, we don't need control when it comes to optimizing an image request. We just want <em>faster images</em>, and <code>srcset</code> and <code>sizes</code> cover that use case handily — better than you or I ever could, if we had to. It would be miserable if we <em>had to</em>. A descriptive syntax avoids this whole nightmare for us, and allows the browser to do what it does best: use the information it has at hand to make a single, efficient request for an image source — something <em>only</em> the browser can do. We just have to provide it with what little information it <em>doesn't</em> have.</p>
<p>Honestly, <code>srcset</code> isn't even that bad, all things considered! Every CMS, static site generator, and build tool in the world can churn out a quick comma-separated list of generated image sources and their widths. Then the more of those values you put in the attribute, the more efficient and tailored the image requests can be; no fuss, no muss, no user-facing costs beyond a few extra bytes of markup. Pretty tidy little syntax, all things considered. I like <code>srcset</code> fine. It’s <em>fine</em>. We're not really here to talk about <code>srcset</code> either.</p>
<p>Responsive images aren't a problem. <code>picture</code> isn't a problem; <code>srcset</code> isn't even the problem.</p>
<p>We both know what the problem is.</p>
<h2>The <code>sizes</code> dilemma</h2>
<p>A browser can't know about the space an image will occupy in a layout because it makes decisions about image requests long before it has the information it needs to <em>render</em> that layout — there's nothing there for it to measure. The viewport size is available to the browser at that point, sure, but that's a terrible proxy for the size of a rendered image in a real-world layout. The web isn't made out of full-bleed "hero" images, it's made up of columns and grids and sidebars and "cards" and smatterings of little round user avatars. Assuming that an image source should never be larger than the user's viewport is a good start, sure, which is why an omitted <code>sizes</code> attribute (invalid, per the specification) behaves as though it were <code>sizes="100vw"</code> . That’s better than nothing, but not by much. So, instead, you and I are left describing the all of the sizes that an element will be, across every breakpoint and container query, as a single string, in an <em>HTML attribute</em>. How disgusting.</p>
<p>Precisely because it requires information about the surrounding layout, <code>sizes</code> resists automation in any meaningful way. A build process can't know the space an image will occupy across layouts without introducing a <em>tremendous</em> amount of overhead to that process — to the tune of "build everything, render the whole site, take measurements for every image on every page, generate <code>sizes</code> values for them all, and <em>then</em> continue the build." So instead we're left to generate that description <em>manually</em> — but except in very, very simple cases, we <em>can't</em> calculate a <code>sizes</code> attribute without tooling. Describing the sizes of a flexible image will require far too much calculation across breakpoints. <code>(min-width: 1340px) 257px, (min-width: 1040px) calc(24.64vw - 68px), (min-width: 360px) calc(28.64vw - 17px), 80px</code> is an example from a <em>relatively simple layout</em>, and there's <em>no way</em> anyone could be expected to write this. I mean, <em>how</em> — from, what, resizing your browser and squinting? <em>Guessing</em>? <code>sizes</code> is one of the few markup patterns that all but <em>require</em> the use of tooling, which the furthest possibly cry from the web's "open any text editor and you can build a website" ethos — something I value <em>tremendously</em>. Hell, even if you <em>did</em> manage to factor it all out, to describe it with <em>media queries</em> — to use a <em>prescriptive</em> syntax as a <em>descriptive</em> syntax, by using them to say "above this size, <em>this is what happens</em>" rather than "above this size, <em>do this</em>" — I feel sick. I hate <code>sizes</code>. I have <em>always</em> hated <code>sizes</code>.</p>
<p>That's why I'm here. That's why I'm writing this, finally, after all this time. I’m not here to apologize for <code>sizes</code>. I’m here to help <em>bury it</em>.</p>
<h2>The beginning and the end</h2>
<p>A few weeks ago, two patches landed in <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1819581">Gecko</a> and <a href="https://bugs.webkit.org/show_bug.cgi?id=310025">WebKit</a> — championed by Simon Pieters and Yoav Weiss, respectively, two of the RICG's finest. These patches landed to little fanfare, quietly aligning Gecko and WebKit with <a href="https://issues.chromium.org/issues/40862170#comment27">Blink</a> in supporting a relatively recent addition to the HTML specification: support for an <code>auto</code> value in <code>sizes</code> attributes. Automatic <code>sizes</code> — the potential sizes of the rendered image, left up to the browser to determine alongside all those other factors. Fully automatic responsive images. Supply the browser with a list of candidates using <code>srcset</code>, bolt on <code>sizes="auto"</code>, and let the browser do the rest.</p>
<p>How? Well, the central issue with <code>srcset</code>/<code>sizes</code> was one of timing, remember: "a browser makes decisions about image requests long before it has any information about the page's layout, so we had to provide it with that layout information." That assumption is no longer strictly true. That's still the <em>default</em> behavior, yes: if there's an <code>img</code> in your markup, the request it triggers will be fired off long before any information about the layout can be known — that is, unless that image uses the <code>loading="lazy"</code> attribute, an <em>exceptionally</em> common best practice for all but the images most likely to appear in the user's viewport at the time the page is first loaded. Adding <code>loading="lazy"</code> to an <code>img</code> changes that entire equation — now those images are requested at the point of user interaction, long after the browser has all the information it needs about the sizes of the rendered image. The browser doesn't need us anymore, and all's right in the world.</p>
<p>I bet you’re waiting for a catch. Well, if you're worried about browser support, don't be — upon encountering the string "auto" at the start of a <code>sizes</code> attribute, any browser with support for it will say "figure it out myself; got it," ditch the rest of the <code>sizes</code> attribute, and move on — browsers without support will throw the meaningless-to-them <code>auto</code> value out and continue on to the rest of attribute as usual. That means you can start using this right now, at absolutely zero cost and with no more overhead than typing <code>auto,</code> at the start of a <code>sizes</code> attribute:</p>
<pre><code>&lt;img 
  loading="lazy"
  src="TrIZjHKy9-650.jpeg" 
  srcset="GTrIZjHKy9-650.jpeg 650w, GTrIZjHKy9-960.jpeg 960w, GTrIZjHKy9-1400.jpeg 1400w"
  sizes="auto, (min-width: 1040px) 650px, calc(94.44vw - 15px)"
  alt="…"&gt;
</code></pre>
<p>Not the first four-letter word <code>sizes</code> has inspired me to use, but certainly the one with the most positive result. </p>
<p>This approach is exactly what WordPress is now using <a href="https://core.trac.wordpress.org/changeset/59008">thanks to a patch from Joe McGill</a>, another RICG alum still fighting the good fight.</p>
<h2>You do (not) need <code>sizes</code></h2>
<p>Granted, it’s not <em>over</em> — you’ll still need descriptive <code>sizes</code> values now and then. An image likely to appear in the user's viewport when a page first loads is a situation where you wouldn't want to use <code>loading="lazy"</code> (again, <code>sizes="auto"</code> will <em>only</em> work with lazyloaded images), but these images are the exceptions, not the default.</p>
<p>Those few exceptions — the images all but certain to appear in the user's viewport way up at the top of the page, your most likely <a href="https://web.dev/articles/lcp">Largest Contentful Paint</a> elements and thus poor candidates for <code>loading="lazy"</code>? Well, you saw one in your mind just now, didn't you? You imagined a big "hero" image; the kind of images that, say, occupy the full viewport width, or close to it? Relatively easy to describe across breakpoints? Maybe even somewhere in the ballpark of — I dunno, just to pull a value out of thin air — <code>sizes="100vw"</code>. Every other image — all those images scattered throughout columns and grids and sidebars and "cards" and smatterings of little round user avatars that the web is really <em>made</em> out of? <code>loading="lazy" sizes="auto"</code>. Job done. Congratulations.</p>
<p>I won't miss all those hand-hewn <code>sizes</code> attributes; I never had any love for them to begin with. I will never experience a shred of nostalgia for a thing that I helped make real and inexorably bound to my name. A syntax was never the goal; the goal was always a <em>mechanism</em>. At the time, the web platform lacked a way for browsers to make smarter decisions about what image asset to request and when, and no amount of clever scripting or markup trickery would ever result in an asset request as fast or efficient as one the browser itself could make. We got that mechanism — and I made all of us pay the cost of it, for the sake of our users and for the health of the web.</p>
<p>So, to any of you designers and developers who’ve wrestled with <code>sizes</code> attributes in the past: go ahead and render an image of me — <em>any size you want</em> — print it out, and stick it to your nearest dartboard. I hold my head high and I offer you no apology. I was right about this; <em>we</em> were right about this. I stand by the need for a declarative syntax. I stand by it every bit as much as I wish it could've been something better, and every bit as much as I know it <em>couldn't</em> have been, at the time. Sure, I bristle at the idea of giving up control as much as the next developer, but when it comes to high-performance images we could never have had any in the first place — not really. It would've been hubris to even try. As frustrating as it can be to give up control, <em>owning</em> responsive images would be a burden; a <em>curse</em>.</p>
<p>Ask me how I know.</p>
        
        ]]></description>
        
      </item>
    
      <item>
        <title>Using the step and pattern attributes to make number inputs more useful</title>
        <link>https://piccalil.li/blog/using-the-step-and-pattern-attributes-to-make-number-inputs-more-useful/?ref=html-category-rss-feed</link>
        <dc:creator><![CDATA[Cassidy Williams]]></dc:creator>
        <pubDate>Thu, 13 Feb 2025 11:55:00 GMT</pubDate>
        <guid isPermaLink="true">https://piccalil.li/blog/using-the-step-and-pattern-attributes-to-make-number-inputs-more-useful/?ref=html-category-rss-feed</guid>
        <description><![CDATA[<p>If you ever have an HTML <code>&lt;input&gt;</code> element for numbers — <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/number"><code>&lt;input type="number" /&gt;</code></a> — you might notice that it defaults to accepting integers as values, and increments and decrements by one. There's ways to make it accept, increment by, and decrement by decimal values too.</p>
<h2>The <code>step</code> attribute</h2>
<p>When you add the <code>step</code> attribute to your <code>&lt;input&gt;</code> element, it specifies how granular the numbers can be in the input.</p>
<p>If you truly don't care what the value is, you can use <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/step#:~:text=The%20value%20must%20be%20a%20positive%20number%20%2D%20integer%20or%20float%20%E2%80%94%20or%20the%20special%20value%20any%2C%20which%20means%20no%20stepping%20is%20implied%20and%20any%20value%20is%20allowed%20(barring%20other%20constraints%2C%20such%20as%20min%20and%20max)."><code>step="any"</code></a> like so:</p>
<pre><code>&lt;input id="number" type="number" value="42" step="any" /&gt;
</code></pre>
<p></p><p>See the Pen <a href="https://codepen.io/piccalilli/pen/bNGGbZx">HTML Number Input with step='any'</a> by Andy Bell (<a href="https://codepen.io/piccalilli/">@piccalilli</a>) on <a href="https://codepen.io">CodePen</a>.</p><p></p>
<p>Your users can then input any number their heart desires, even if it's several decimal places deep.</p>
<p>But, let's say you want it to be a monetary value, for example, only to two decimal places. Then, you make <code>step="0.01"</code>.</p>
<pre><code>&lt;input id="number" type="number" value="3.14" step="0.01" /&gt;
&lt;!-- 3.15 valid, 3.14159 invalid --&gt;
</code></pre>
<p>Try setting a value of three decimal places in this demo to see how it fails validation.</p>
<p></p><p>See the Pen <a href="https://codepen.io/piccalilli/pen/jEOONjJ">HTML Number Input with step='0.01'</a> by Andy Bell (<a href="https://codepen.io/piccalilli/">@piccalilli</a>) on <a href="https://codepen.io">CodePen</a>.</p><p></p>
<p>Not too bad, right? You can even get funky with it and give it a really specific number value, and if the user clicks up and down with the input box controls, it'll go up by that amount:</p>
<pre><code>&lt;input id="number" type="number" value="3.14" step="3.14" /&gt;
&lt;!-- 9.42 valid, 9.43 invalid --&gt;
</code></pre>
<p></p><p>See the Pen <a href="https://codepen.io/piccalilli/pen/RNwwbXx">HTML Number Input with step='3.14'</a> by Andy Bell (<a href="https://codepen.io/piccalilli/">@piccalilli</a>) on <a href="https://codepen.io">CodePen</a>.</p><p></p>
<h2>Can I use <code>pattern</code> to do the same thing?</h2>
<p>Theoretically yes. If you haven't seen the <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern"><code>pattern</code> attribute</a>, you can use it in your HTML to do regex pattern matching. It <em>can</em> be used for decimal places too. Should you use it for that? Probably not, especially when <code>step</code> is right there, waiting to be used. But you <em>can</em> use it.</p>
<p>For example, you can use <code>pattern</code> to match a one or two-digit number with two decimal places after it:</p>
<pre><code>&lt;input type="number" pattern="\d{1,2}(\.\d{2})?" /&gt;
&lt;!-- 12.34 valid, 123.4 invalid --&gt;
</code></pre>
<p>Try typing in a number with more than two decimal places or three numbers before the decimal point to see how it fails validation.</p>
<p></p><p>See the Pen <a href="https://codepen.io/piccalilli/pen/gbOOYVz">HTML Number Input with pattern='\d{1,2}(\.\d{2})?'</a> by Andy Bell (<a href="https://codepen.io/piccalilli/">@piccalilli</a>) on <a href="https://codepen.io">CodePen</a>.</p><p></p>
<p>...it's not cute, but it <em>does</em> get the job done if you really want this kind of behavior. The <code>pattern</code> attribute is definitely more suitable for text-based inputs — like an email address — rather than numerical ones. The <code>pattern</code> attribute doesn't provide the same UI controls that <code>step</code> gives you either.</p>
<p>This approach of using <code>pattern</code> <em>is</em> particularly good for adhering to specific patterns, like "a number that is exactly 4 digits" (<code>pattern="[0-9]{4}"</code>), rather than limiting input data to a certain number of decimal places. But, <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/max">the <code>max</code> attribute</a> set to <code>9999</code> will do the same thing, <em>and</em> it's more semantic.</p>
<h2>What happens when I break the <code>step</code> or <code>pattern</code>?</h2>
<p>With both <code>step</code> and <code>pattern</code>, your <code>&lt;input&gt;</code> element will match the <code>:invalid</code> pseudo-class in CSS, so you can style it, and there's often a browser-native validation message as well when a user tries to submit the form.</p>
<div><h2>FYI</h2>
<p>There's also a <a href="https://html-css-tip-of-the-week.netlify.app/tip/user-valid/"><code>:user-valid</code> pseudo-class</a> that only applies <em>after</em> a user has interacted with your input. This gives you more control over when your validation-based styles apply.</p>
</div>
<p>If your users type in an invalid value anyway, you can still run JavaScript on it and see what the <code>value</code> is. <strong>Sometimes</strong> the browser rounds the number to the closest one that will fit. I say "sometimes" because... It Depends™.</p>
<p></p><p>See the Pen <a href="https://codepen.io/piccalilli/pen/bNGGGbQ">HTML Number Inputs, with JavaScript</a> by Andy Bell (<a href="https://codepen.io/piccalilli/">@piccalilli</a>) on <a href="https://codepen.io">CodePen</a>.</p><p></p>
<p>In this demo, you can see that some of the initial values in the number inputs are valid, with suggestions on how to change them to see how the browser rounds things. The "raw" value in the input is what you see in the input, but the "valid" value is what the browser might be accepting instead.</p>
<h2>Wrapping up</h2>
<p>You should use <code>step</code> for controlling numeric increments because it's designed specifically to do that <em>and</em> the browser provides better UI controls for it natively. The <code>step</code> attribute is more mathematically based, too, instead of text-pattern based. It also works for other numeric input types like <code>range</code> and works really well when paired with the <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/min"><code>min</code></a> and <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/max"><code>max</code></a> attributes.</p>
<p>Use <code>pattern</code> for more complex pattern validation beyond the number of decimal places you want! But, remember that it doesn't have built-in incrementing and decrementing controls, and the validation is done based on text-pattern matching.</p>
        
        ]]></description>
        
      </item>
    
      <item>
        <title>A highly configurable switch component using modern CSS techniques</title>
        <link>https://piccalil.li/blog/a-highly-configurable-switch-component-using-modern-css/?ref=html-category-rss-feed</link>
        <dc:creator><![CDATA[Andy Bell]]></dc:creator>
        <pubDate>Tue, 30 Jan 2024 07:55:00 GMT</pubDate>
        <guid isPermaLink="true">https://piccalil.li/blog/a-highly-configurable-switch-component-using-modern-css/?ref=html-category-rss-feed</guid>
        <description><![CDATA[<p>Safari Technology preview has <a href="https://alvaromontoro.com/blog/68049/new-toggle-switch-lands-in-safari">recently added a native switch component with version 185 and 186</a>, which is great! It’s going to be a long while before this is ready to rock on a production website though.</p>
<p>Still, <a href="https://nt1m.github.io/html-switch-demos/">this collection of demos is worth enjoying</a>. Here’s a video for those who don’t have the latest version of Safari Technology Preview.</p>
<figure>
<video></video>
<figcaption>Native switches that are seemingly highly customisable with CSS in Safari Technology preview.</figcaption>
</figure>
<div><h2>FYI</h2>
<p>You’re going to need to enable the HTML switch, <code>::thumb</code> and <code>::track</code> pseudo-element feature flags for this to work for you.</p>
</div> 
<p>While we wait for native switch support, I thought I would build a highly configurable switch component using <code>:has()</code>, container queries, Logical Properties and Custom Properties for fun <em>and</em> to show you how much goes into a truly flexible component. Let’s dig in.</p>
<h2>What we’re building</h2>
<p></p><p>See the Pen <a href="https://codepen.io/piccalilli/pen/MWxERjV/615c784c307feb96f5e7459c5d5aa456">Our final switch component</a> by Andy Bell (<a href="https://codepen.io/piccalilli/">@piccalilli</a>) on <a href="https://codepen.io">CodePen</a>.</p><p></p>
<h2>HTML first, always</h2>
<p>The HTML for this is pretty straightforward:</p>
<pre><code>&lt;label class="switch-input"&gt;
  &lt;span class="visually-hidden"&gt;Enable setting&lt;/span&gt;
  &lt;input type="checkbox" role="switch" class="visually-hidden" /&gt;
  &lt;span class="switch-input__decor" data-switch-input-state="on" aria-hidden="true"
    &gt;On&lt;/span
  &gt;
  &lt;span class="switch-input__decor" data-switch-input-state="off" aria-hidden="true"
    &gt;Off&lt;/span
  &gt;
  &lt;span class="switch-input__thumb" aria-hidden="true"&gt;&lt;/span&gt;
&lt;/label&gt;
</code></pre>
<p>The first thing to note is the root of this component is a <code>&lt;label&gt;</code> element. I like that pattern for checkbox and radio buttons because you get a nice increased tap area.</p>
<p>The HTML form control is a checkbox, but I’ve added a <code>role="switch"</code> attribute to it. This is so the component is announced as a switch by a screen reader and also each state change is announced as “on” or “off”, which is appropriate for a switch in my opinion.</p>
<p>I could have used an <code>aria-label</code> for the text label (always add a text label, pals), but I opted instead for a visually hidden <code>&lt;span&gt;</code>. The main reason is it’s easier for a user’s in-browser translation tool to translate the content.</p>
<div><h2>FYI</h2>
<p>If you’re going to use this in the wild, avoid using the word “switch” in your label because it’s already announced as a switch by screen readers.</p>
</div>
<p>Lastly, there’s 3 decorative only elements. The “on” and “off” text are hidden from assistive tech with <code>aria-hidden="true"</code> because that tech is already announcing those states. The visual thumb of the control is hidden in the same manner too because it only provides value for sighted users.</p>
<h2>Configuration settings</h2>
<p>As promised, this thing is <em>configurable</em>. There’s no better way in native CSS than Custom Properties.</p>
<pre><code>:root {
  --switch-input-thumb-size: 44px;
  --switch-input-thumb-bg: #ffffff;
  --switch-input-thumb-stroke: 1px solid grey;
  --switch-input-off-bg: #444444;
  --switch-input-off-text: #ffffff;
  --switch-input-on-bg: #00a878;
  --switch-input-on-text: #ffffff;
  --switch-input-gutter: 4px;
  --switch-input-decor-space: var(--switch-input-gutter) 1.25ch;
  --switch-input-focus-stroke: 2px solid #ff6978;
  --switch-input-font-weight: bold;
  --switch-input-font-family: sans-serif;
  --switch-input-font-size: 18cqw;
  --switch-input-transition: inset 50ms linear;
}
</code></pre>
<p>I’m not going to go into too much detail at this point. I’ll explain stuff as we come to it in the component’s CSS. One thing I will note though is that the pixel sizes are there for reasons like:</p>
<ol>
<li>I wanted to hit the <a href="https://www.smashingmagazine.com/2023/04/accessible-tap-target-sizes-rage-taps-clicks/#:~:text=It's%20worth%20noting%20that%20according,%2C%20the%20larger%2C%20the%20better.">WCAG minimum tap target size</a></li>
<li>The thumb size and thumb gutter are used in calculations, so <code>clamp()</code> is outa the window</li>
</ol>
<h2>Visually hidden utility</h2>
<pre><code>.visually-hidden {
  border: 0;
  clip: rect(0 0 0 0);
  height: auto;
  margin: 0;
  overflow: hidden;
  padding: 0;
  position: absolute;
  width: 1px;
  white-space: nowrap;
}
</code></pre>
<p>I roll this variant of screen reader only CSS out on pretty much every project and have done so <a href="https://piccalil.li/quick-tip/visually-hidden/">for quite some time</a>. This will allow a screen reader to read the label’s content, but will visually hide it and also prevent it from affecting layout etc.</p>
<h2>The <code>.switch-input</code> root</h2>
<p>It’s time to get stuck into the component now.</p>
<pre><code>.switch-input {
  width: calc((var(--switch-input-thumb-size) * 2) + (var(--switch-input-gutter) * 3));
  height: calc(var(--switch-input-thumb-size) + (var(--switch-input-gutter) * 2));
  border-radius: calc(var(--switch-input-thumb-size) + var(--switch-input-gutter));
  padding: var(--switch-input-gutter);
  background: var(--switch-input-off-bg);
  color: var(--switch-input-off-text);
  text-align: left;
  text-transform: uppercase;
  font-family: var(--switch-input-font-family);
  font-weight: var(--switch-input-font-weight);
  position: relative;
  cursor: pointer;
  container-type: inline-size;
}
</code></pre>
<p>The first calculation is the width of the component itself because it is also the switch “track”. The two configuration options used are <code>--switch-input-thumb-size</code> and <code>--switch-input-thumb-gutter</code>. The thumb size is self-explanatory, but the gutter is the space around the thumb.</p>
<p><img src="https://piccalil.b-cdn.net/images/blog/finished-switch.jpg" alt="The finished switch component in its off state" /></p>
<p>The gutter provides space around the thumb, so we need to account for each side of the overall <code>.switch-input</code> element and also added space for the middle. The formula for the calculation is <code>thumbSize x 2</code> added to <code>gutter x 3</code>.</p>
<p>The <code>border-radius</code> uses <a href="https://set.studio/relative-rounded-corners/">the following formula</a> for relative rounded corners: <code>radius</code> + <code>padding</code>. It’s not as visible in this context as say, a rounded rectangle, but if you have a large gutter it will make a difference.</p>
<p>There’s a lot of inheritable CSS in here such as font treatments, so we’ll skip that. The last bit I want to focus on is the <code>container-type: inline-size</code>. This will be used later to calculate the label’s size, so it’s critical that it is present here because that size will be relative to the track’s inline size. We also roll out <code>position: relative</code> because everything is absolutely positioned from here on in.</p>
<h2>The decorative text elements</h2>
<div><h2>FYI</h2>
<p>These are completely optional. You might also want to use icons for a theme switcher, for example.</p>
</div>
<pre><code>.switch-input__decor {
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  padding: var(--switch-input-decor-space);
  font-size: var(--switch-input-font-size);
  display: flex;
  width: 100%;
  align-items: center;
}
</code></pre>
<p>We’re using the logical versions of <code>inset</code> here because it’ll be handy for this component to respond the HTML <code>dir</code> attribute. For example, if a parent HTML has <code>dir="rtl"</code>, it’ll automatically present as <code>rtl</code>.</p>
<div><h2>FYI</h2>
<p>You might not want this to be the behaviour of your version of this component. If you add <code>direction: ltr</code> to the <code>.switch-input</code> CSS, it’ll behave as you wish it too.</p>
</div> 
<p>It’s worth noting that the <code>inset</code> shorthand property is not logical. It is shorthand for <code>top</code>, <code>right</code>, <code>bottom</code> and <code>left</code>. That’s why we’re using the specific logical versions.</p>
<p>You can also see that we’re applying our <code>font-size</code> here, rather than inheriting from the parent. This is because the <code>--switch-input-font-size</code> Custom Property is using <code>cqw</code> units: a portion of the container’s computed width.</p>
<figure>
<video></video>
<figcaption>As the thumb sizes changes, the whole component, including the text changes with it. As the padding increases, the need for a relative border radius for the parent component is demonstrated too.</figcaption>
</figure>
<p>As that demo shows, if the component grows, the labels grow with it nicely. Please use this approach with caution though. You need to make sure that the text is large enough (ideally computed to at least <code>16px</code>) and that it also zooms appropriately. With the switch input’s overall size being pixel controlled in this example, and the fact we’re using <code>44px</code> — the minimum tap target size — that is the case.</p>
<h3>Future stuff: align-content</h3>
<p>A nice improvement to this decorative text’s rule would be the following (don’t add this to your code):</p>
<pre><code>align-content: center;
display: block;
block-size: 100%;
</code></pre>
<p>We’re only using flexbox to vertically align our text labels in the middle. This new alignment capability is <a href="https://rachelandrew.co.uk/archives/2023/12/19/align-content-in-block-layout/">arriving to block elements</a> in the future which should render those already completely out of date, vertical alignment CSS memes, useless, once and for all. I imagine this won’t change the social media clout chaser’s behaviour though, unfortunately.</p>
<pre><code>.switch-input__decor[data-switch-input-state='off'] {
  justify-content: flex-end;
}
</code></pre>
<p>Lastly for this element, we’re adding a <a href="https://cube.fyi/exception.html">CUBE Exception</a> to push the “off” label out to the inline end because the thumb will be at the inline start in the default off state.</p>
<h2>The thumb element</h2>
<p>Time to style up our little round thumb element.</p>
<pre><code>.switch-input__thumb {
  display: block;
  width: var(--switch-input-thumb-size);
  height: var(--switch-input-thumb-size);
  border-radius: var(--switch-input-thumb-size);
  background: var(--switch-input-thumb-bg);
  border: var(--switch-input-thumb-stroke);
  z-index: 1;
  position: absolute;
  inset-block-start: var(--switch-input-gutter);
  inset-inline-start: var(--switch-input-gutter);
  transition: var(--switch-input-transition);
}
</code></pre>
<p>We’re making a square by setting the <code>width</code> and <code>height</code>, then making that square a circle by using the same configuration value for <code>border-radius</code>. You could use percentages here, but I personally prefer this approach. I think it’s a symptom of the bad old days of browsers.</p>
<p>It’s important to set <code>z-index</code> because we want this to always be a layer up from our decorative labels and guarantees that the thumb won’t interfere with the visual text.</p>
<p>Finally, using absolute positioning, the thumb is set to the inline and block start, using the logical <code>inset</code> values. A <code>transition</code> (very quick one!) is added to smooth out the on and off state changes. Normally I would recommend that you don’t transition <code>inset</code> because <code>translate</code> is much smoother, but in this context, it should be fine because it’s not a large surface area and the transition is very quick (<code>100ms</code>).</p>
<h2>Focus states</h2>
<p>The important thing to get right here for me is that the focus ring should show for keyboard users only and not when the component is clicked or tapped.</p>
<pre><code>.switch-input:has(:focus-visible) .switch-input__thumb {
  outline: var(--switch-input-focus-stroke);
}
</code></pre>
<p>Luckily <code>:focus-visible</code> does this well and is <a href="https://caniuse.com/?search=focus-visible">very well supported</a>.</p>
<p>The party trick here is the usage of <code>:has()</code>. Historically with this sort of component you’d have to write a selector like this: <code>.switch-input input:focus-visible ~ .switch-input__thumb</code>.</p>
<p>This would require the source order to be just right in the component. Now that we have <code>:has()</code> — <a href="https://caniuse.com/?search=has">which is also very well supported</a> — that state can be determined at the root level of the component. In theory, the input could be the last child element now.</p>
<h2>The on/off states</h2>
<p>This is the last bit of CSS and we are done!</p>
<pre><code>.switch-input:has(:checked) {
  background: var(--switch-input-on-bg);
  color: var(--switch-input-on-text);
}

.switch-input:has(:checked) .switch-input__thumb {
  inset-inline-start: calc(
    var(--switch-input-thumb-size) + (var(--switch-input-gutter) * 2)
  );
}
</code></pre>
<p>Historically setting the background of the switch component would again, require some weird combination selectors and extra elements, <em>or</em> require a JavaScript dependency. Not anymore because <code>:has()</code> allows us to change the background and text colour of the component (if required) based on its child <code>:checked</code> state. Handy!</p>
<p>We also use that pattern to change the position of the thumb. It’s a calculation that similar to the one we added earlier to set the size of the overall component. This time, it’s pushing the thumb to the end when the switch is “on”. This is why the <code>--switch-input-gutter</code> is doubled, to account for the start space and also the middle space.</p>
<h2>Wrapping up</h2>
<p>After all that, we’ve got ourselves a lovely little component:</p>
<p></p><p>See the Pen <a href="https://codepen.io/piccalilli/pen/MWxERjV/615c784c307feb96f5e7459c5d5aa456">Our final switch component</a> by Andy Bell (<a href="https://codepen.io/piccalilli/">@piccalilli</a>) on <a href="https://codepen.io">CodePen</a>.</p><p></p>
<p>Would I use this in production? Probably, yeh. There has to be a damn good reason for a switch in the first place though.</p>
<p>Regardless, this is a handy little context to teach you about some of the super powers CSS gives us now.</p>
        
        ]]></description>
        
      </item>
    
      <item>
        <title>Build a fully-responsive, progressively enhanced burger menu</title>
        <link>https://piccalil.li/blog/build-a-fully-responsive-progressively-enhanced-burger-menu/?ref=html-category-rss-feed</link>
        <dc:creator><![CDATA[Andy Bell]]></dc:creator>
        <pubDate>Fri, 15 Jan 2021 08:18:51 GMT</pubDate>
        <guid isPermaLink="true">https://piccalil.li/blog/build-a-fully-responsive-progressively-enhanced-burger-menu/?ref=html-category-rss-feed</guid>
        <description><![CDATA[<p>This is a <em>long</em> tutorial, so make sure you have plenty of time to get though it. Let’s dive in.</p>
<h2>Getting started</h2>
<p>We’re keeping things on The Platform™ in this tutorial, so no need to worry about build processes. We’re going to have a single HTML page, a CSS file and a couple of JavaScript files.</p>
<p>First up, grab these starter files that will give you the right structure.</p>
<p><a href="https://piccalilli.s3.eu-west-2.amazonaws.com/530fbbc391989e513daa5e59d74c88b9.zip">Download starter files</a></p>
<p>Extract those into the folder you want to work out of. It should look like this:</p>
<pre><code>├── css
│   └── global.css
├── images
│   └── logo.svg
├── index.html
├── js
│   ├── burger-menu.js
│   └── get-focusable-elements.js
</code></pre>
<p>These files are all empty (apart from the logo), so let’s start filling them up.</p>
<h2>Adding our HTML</h2>
<p>We’ll start with HTML, so open up <code>index.html</code> and add the following to it:</p>
<pre><code>&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
  &lt;head&gt;
    &lt;meta charset="UTF-8" /&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&gt;
    &lt;meta http-equiv="X-UA-Compatible" content="ie=edge" /&gt;
    &lt;title&gt;Build a fully-responsive, progressively enhanced burger menu&lt;/title&gt;
    &lt;link rel="stylesheet" href="https://unpkg.com/modern-css-reset/dist/reset.min.css" /&gt;
    &lt;link rel="stylesheet" href="css/global.css" /&gt;
    &lt;link rel="preconnect" href="https://fonts.gstatic.com" /&gt;
    &lt;link
      href="https://fonts.googleapis.com/css2?family=Halant:wght@600&amp;family=Hind:wght@400;500;700&amp;display=swap"
      rel="stylesheet"
    /&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;script src="js/burger-menu.js" type="module" defer&gt;&lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>This is our HTML shell and features all the relevant CSS, fonts and JavaScript files, pre-linked and ready to go. We’re using <a href="https://piccalil.li/blog/a-modern-css-reset/">this modern CSS reset</a> to give us some sensible defaults that we’ll build on with our custom CSS.</p>
<p>Let’s add some more HTML, first. Open up <code>index.html</code> again, and after the opening <code>&lt;body&gt;</code> tag, add the following:</p>
<pre><code>&lt;header class="site-head" role="banner"&gt;
  &lt;a href="#main-content" class="skip-link"&gt;Skip to content&lt;/a&gt;
  &lt;div class="wrapper"&gt;
    &lt;div class="site-head__inner"&gt;
      &lt;a href="/" aria-label="ACME home" class="site-head__brand"&gt;
        &lt;img src="images/logo.svg" alt="ACME logo" /&gt;
      &lt;/a&gt;
      &lt;burger-menu max-width="600"&gt;
        &lt;nav class="navigation" aria-label="primary"&gt;
          &lt;ul role="list"&gt;
            &lt;li&gt;
              &lt;a href="#"&gt;Home&lt;/a&gt;
            &lt;/li&gt;
            &lt;li&gt;
              &lt;a href="#"&gt;About&lt;/a&gt;
            &lt;/li&gt;
            &lt;li&gt;
              &lt;a href="#"&gt;Our Work&lt;/a&gt;
            &lt;/li&gt;
            &lt;li&gt;
              &lt;a href="#"&gt;Contact Us&lt;/a&gt;
            &lt;/li&gt;
            &lt;li&gt;
              &lt;a href="#"&gt;Your account&lt;/a&gt;
            &lt;/li&gt;
          &lt;/ul&gt;
        &lt;/nav&gt;
      &lt;/burger-menu&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/header&gt;
</code></pre>
<p>This is our main site header and it’s got a few bits that we’ll break down. First up, we have a skip link. A skip link allows a user to skip past the header and navigation and jump straight to the <code>&lt;main&gt;</code> element—which in our case—contains a simple <code>&lt;article&gt;</code>. It’ll be visually invisible by default and show on focus, when we get around to writing some CSS.</p>
<p>Next up, we have the brand element, which contains our placeholder logo. We’re using the <code>aria-label</code> attribute to provide the text to mainly assist screen readers. Very importantly, the <code>alt</code> on the image describes the image as “ACME logo”, with “ACME” being the name of the company.</p>
<p>Lastly, we have our main navigation—a classic unordered list of links in a <code>&lt;nav&gt;</code> element. It’s wrapped in a <code>&lt;burger-menu&gt;</code> element which is a <a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements">Custom Element</a>. This is a great example of HTML being an incredibly smart programming language because even though we’ve not defined what this element is or does yet, but the browser doesn’t care—it just continues doing what it’s doing, without any fuss. This capability helps us build this project <em>progressively</em>, which we’ll get into more, shortly.</p>
<p>We have an <code>aria-label</code> on our <code>&lt;nav&gt;</code> element. It’s not required in our case, but if you have more than one <code>&lt;nav&gt;</code> on a page, you must label them to help assistive technology.</p>
<p>Let’s wrap up our HTML by adding the last bit. Still inside <code>index.html</code>, add the following <strong>after</strong> the closing <code>&lt;/header&gt;</code>:</p>
<pre><code>&lt;main id="main-content" tabindex="-1" class="wrapper"&gt;
  &lt;article class="post flow"&gt;
    &lt;h1&gt;A responsive, progressively enhanced burger menu&lt;/h1&gt;
    &lt;p&gt;
      Burger menus are a relic of responsive design that no matter what your opinion of
      them is, they continue to be a dominant design pattern. They’re very good at
      preserving often-limited horizontal space, but they also, more often than not, are
      built in a user-hostile, non-accessible fashion.
    &lt;/p&gt;
    &lt;p&gt;
      &lt;a
        href="https://piccalil.li/premium/build-a-fully-responsive-progressively-enhanced-burger-menu"
        &gt;In this premium tutorial&lt;/a
      &gt;, we’re going to build a burger menu from the ground up, using progressive
      enhancement, `ResizeObserver`, `Proxy` state and of course, super-solid HTML and CSS
      that pull from the CUBE CSS principles.
    &lt;/p&gt;
    &lt;p&gt;
      This whole page is what you’re building in the tutorial 👆.
      &lt;a
        href="https://piccalil.li/premium/build-a-fully-responsive-progressively-enhanced-burger-menu"
        &gt;Let’s dive in&lt;/a
      &gt;
    &lt;/p&gt;
  &lt;/article&gt;
&lt;/main&gt;
</code></pre>
<p>There’s not much to say about this as it’s a pretty straightforward <code>&lt;article&gt;</code>. The only bit to make you aware of is that the <code>&lt;main&gt;</code> element should only feature once on the page, being the <strong>main content</strong>. We’ve got an <code>id</code> of <code>main-content</code> on there, too, which is what our skip link links to.</p>
<p>We’ve got all of our HTML now and guess what: we’ve got a fully functional web page (if you ignore the <code>#</code> links). The key to progressive enhancement is building up with a <a href="https://adactio.com/journal/14327">principle of least power approach</a>. We know now that as long as this very small HTML page lands in the user’s browser, they can immediately and effectively use it.</p>
<h2>Minimum Viable Experience CSS</h2>
<p>We’re going to approach our CSS progressively too, so using principles of <a href="https://piccalil.li//cube.fyi">CUBE CSS</a>: we’re going to start right at the top with some global CSS.</p>
<div><h2>FYI</h2>
  If you haven’t read up on CUBE CSS yet, I would recommend reading [this high-level overview post](/blog/cube-css/) to give yourself some background knowledge.
</div>
<p>Open up <code>css/global.css</code> and add the following to it:</p>
<pre><code>:root {
  --color-light: #ffffff;
  --color-light-shade: #fafffd;
  --color-dark: #062726;
  --color-primary: #d81159;
  --color-primary-shade: #b90f4c;
}
</code></pre>
<p>These are our site colours, neatly organised as some root-level CSS Custom Properties. The <code>:root</code> pseudo-class is just a posh way of using the <code>&lt;html&gt;</code> element. Keep in mind, though, that a pseudo-class (not pseudo-elements) has a higher specificity than a HTML element (<code>&lt;html&gt;</code> in this case). They have the same specificity as a <code>class</code>.</p>
<p>Let’s add some core globals. Still in <code>global.css</code>, add the following:</p>
<pre><code>body {
  background: var(--color-light-shade);
  color: var(--color-dark);
  line-height: 1.5;
  font-family: 'Hind', 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  font-weight: 400;
}

h1,
h2 {
  font-family: 'Halant', Georgia, 'Times New Roman', Times, serif;
  font-weight: 600;
  line-height: 1.1;
  max-width: 30ch;
}

h1 {
  font-size: 2rem;
}

h2 {
  font-size: 1.8rem;
}

a {
  color: currentColor;
}

:focus {
  outline: 1px dotted currentColor;
  outline-offset: 0.2rem;
}

p,
li,
dl {
  max-width: 70ch;
}

article {
  margin-top: 2.5rem;
  font-size: 1.25rem;
}

main:focus {
  outline: none;
}

@media (min-width: 40em) {
  h1 {
    font-size: 3rem;
  }

  h2 {
    font-size: 2.5rem;
  }
}
</code></pre>
<p>These are high-level, global HTML-element styles. We’re setting the basics, mainly, but again, with progressive enhancement in mind, we’re keeping things as simple as possible.</p>
<p>Some key points:</p>
<ol>
<li>We set a <code>max-width</code> on headings, paragraphs, lists elements and description lists using a <code>ch</code> unit. This really helps with readability and a <code>ch</code> unit is equal to the width of a <code>0</code> character in your chosen font and size. You can <a href="https://piccalil.li/quick-tip/limit-line-lengths-to-increase-readability/">read more about why we do this here</a>.</li>
<li>We set <code>:focus</code> styles globally by modifying how the <code>outline</code> looks. This means that any element that can receive focus, such as <code>&lt;a&gt;</code> and <code>&lt;button&gt;</code> will have a consistent focus style. The <code>outline-offset</code> pulls the outline away from the content a bit, which in my opinion, makes it more user-friendly.</li>
<li>We remove focus styles from the <code>&lt;main&gt;</code> element because when someone activates the skip link from before, it programatically focuses the <code>&lt;main&gt;</code> because it’s the <code>:target</code>. The focus ring is unnecessary though, because making the <code>&lt;main&gt;</code> focusable, programatically, is purely for making tabbing on the keyboard more predictable for users who want to skip navigation. If we didn’t move focus, they could end up in a situation where hitting the tab key sends them back up the the navigation!</li>
</ol>
<div><h2>FYI</h2>
  Did you know that setting a `tabindex` HTML attribute value of `-1` allows you to focus it programatically? What this means is that you can—just like the skip link—pass focus when it’s targetted, but you can also use JavaScript to pass focus, using the `focus()` function.
<p>What’s handy with this approach is that using <code>tabindex="-1"</code> prevents the user from being able to tab to the element with their keyboard too, so it’s really helpful with focus management of interactive elements, which we’ll get on to later in this tutorial.
</p>
<h3>CSS Utilities</h3>
<p>We’ve got some sensible, global CSS, so let’s now add some utilities.</p>
<p>In the <a href="https://cube.fyi/utility/">CUBE-CSS documentation</a>, I describe utilities like so:</p>
<blockquote>
<p>A utility, in the context of CUBE CSS, is a CSS class that does one job and does that one job well.</p>
</blockquote>
<p>With that in mind, we’re going to add 3 utilities: a skip link, a wrapper and finally a <a href="https://piccalil.li/quick-tip/flow-utility">flow utility</a>.</p>
<p>Let’s add the skip link first. Open up <code>global.css</code> and add the following to it:</p>
<pre><code>.skip-link {
  display: inline-block;
  padding: 0.7rem 1rem 0.5rem 1rem;
  background: var(--color-light);
  color: var(--color-primary-shade);
  text-decoration: none;
  font-weight: 700;
  text-transform: uppercase;
  position: absolute;
  top: 1rem;
  left: 1rem;
}
</code></pre>
<p>The skip link is styled to look like a button. It’s good to give it plenty of contrast against it’s context—which in our case, is the site header—and making it look like a button really helps with that. Other than that, there’s not much to discuss about this, so let’s add the clever stuff.</p>
<div><h2>FYI</h2>
  You might be wondering why we’re lumping all of the CSS into `global.css`. It’s to keep this tutorial as simple as possible. I would recommend breaking global CSS, utilities, blocks and composition styles (both coming up, shortly) into their own areas, in a proper project, though.
</div>
<p>Still inside <code>global.css</code>, add the following:</p>
<pre><code>.skip-link:hover {
  background: var(--color-dark);
  color: var(--color-light-shade);
}

.skip-link:not(:focus) {
  border: 0;
  clip: rect(0 0 0 0);
  height: auto;
  margin: 0;
  overflow: hidden;
  padding: 0;
  position: absolute;
  width: 1px;
  white-space: nowrap;
}
</code></pre>
<p>The first part is some good ol’ <code>:hover</code> styles. The important part, though, is that when the skip link is <strong>not focused</strong>, we <strong>visually hide it</strong>. The CSS in the <code>.skip-link:not(:focus)</code> block is the same as <a href="https://piccalil.li/quick-tip/visually-hidden">this visually hidden utility</a>, which allows screen readers and parsers to “see” it, while visually, it isn’t present. Because we use the <code>:not(:focus)</code> pseudo-selector: when the skip link is focused, it shows, visually.</p>
<p>Right, that’s our main utility sorted. This one very much blurs the line between a CUBE CSS block and a utility, but I’m pretty happy putting it where we have it for this one.</p>
<p>Next up, let’s add those remaining utilities. We’ll add the <code>wrapper</code> which is a simple container that helps keep our sections aligned with each other.</p>
<p>Open up <code>global.css</code> and add the following to it:</p>
<pre><code>.wrapper {
  max-width: 65rem;
  margin-left: auto;
  margin-right: auto;
  padding-left: 1.25rem;
  padding-right: 1.25rem;
}
</code></pre>
<p>This does exactly what it says on the tin, really. The only part to mention is that we specifically add left/right padding/margin so <a href="https://cube.fyi/composition">other compositional CSS</a> can comfortably manage vertical space, if needed.</p>
<p>Let’s add the flow utility, too. Inside <code>global.css</code>, add the following:</p>
<pre><code>.flow &gt; * + * {
  margin-top: var(--flow-space, 1em);
}
</code></pre>
<p>This utility is <a href="https://piccalil.li/quick-tip/flow-utility">explained in this quick tip</a>, so go ahead and give it a read, but in short, it adds space to sibling elements automatically and I use it <strong>all the time</strong>.</p>
<p>With all of the HTML, global CSS and CSS utilities added, your page should look like this.</p>
<p><img src="https://piccalil.b-cdn.net/images/tutorials/bm-ss-1.jpg" alt="A very basic page, with mostly HTML with a touch of CSS" /></p>
<h3>CSS Blocks</h3>
<p>Let’s dig into the details now—but we’re only going to do our minimum viable experience CSS.</p>
<p>This is the default experience, for <strong>when</strong> JavaScript doesn’t load for a user. We want to make sure that the experience is solid and that the user doesn’t even notice that there is missing functionality.</p>
<p>We build this CSS first, but it’s also worth continually testing this use-case <em>even when</em> all your main functionality—in our case, a burger menu—is fully implemented.</p>
<p>Let’s start by styling up the site head. Open up <code>global.css</code> and add the following to it:</p>
<pre><code>.site-head {
  padding: 0.6rem 0;
  background: var(--color-primary);
  border-top: 5px solid var(--color-primary);
  border-bottom: 5px solid var(--color-primary-shade);
  color: var(--color-light);
  line-height: 1.1;
}

.site-head :focus {
  outline-color: var(--color-light);
}

.site-head__inner {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
  gap: 0 1rem;
}

.site-head__brand {
  display: block;
  width: 3rem;
}
</code></pre>
<p>To start with, we add the colour to site head and a nice bottom border. Having just a bottom border puts things out of kilter, visually, so we add an optical adjustment, in the form of the same border style—<em>but</em> the same colour as the background. This border is essentially invisible, but it levels things out. Brains are weird, right?</p>
<p>Next, some good ol’ flexible layout stuff. The <code>site-head__inner</code> element uses flexbox to <em>push</em> elements away from each other—importantly, only where there is space. We use <code>flex-wrap: wrap</code> to allow items to stack on top of each other where needed.</p>
<p>All mixed together, this means that because <code>justify-content</code> affects the <strong>horizontal axis</strong>—because we are using the default <code>flex-direction</code> value of <code>row</code>—it won’t affect items that are not on the same axis anymore. This means we get responsive layout with no media queries. Handy.</p>
<p>Let’s move on to a navigation block. Open up <code>global.css</code> again and add the following:</p>
<pre><code>.navigation ul {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.3rem 0.8rem;
  padding: 0;
}

.navigation li {
  margin: 0.1rem;
}

.navigation a {
  font-weight: 600;
  text-transform: uppercase;
  text-decoration: none;
  color: currentColor;
}

.navigation a:hover {
  color: var(--color-dark);
}
</code></pre>
<p>The first thing to note here is that we don’t have any specific styles for the <code>.navigation</code> block itself. This is because with CUBE CSS, <a href="https://cube.fyi/block/#heading-what-should-a-block-do">a block is more of a namespace</a>. Diving into the list element, though, we’re using the same sort of flexible layout approach as we did in the <code>site-head</code> block.</p>
<p>We’re using <code>gap</code> to space elements and again, allowing items to fall onto a new line if there’s no space. This gives us a handy, extremely acceptable, <a href="https://piccalil.li/blog/a-minimum-viable-experience-makes-for-a-resilient-inclusive-website-or-app">minimum viable experience</a>, regardless of viewport size.</p>
<p>We do provide a cheeky little fallback for if <code>gap</code> isn’t supported (<a href="https://caniuse.com/flexbox-gap">at the time of writing, Safari, mainly</a>). By adding a tiny, <code>0.1rem</code> margin on <strong>all sides</strong> of a list item. You could use <code>@supports</code> to remove this for <code>gap</code> support, but remember, <strong>principal of least power</strong>. Also, <a href="https://ishadeed.com/article/flexbox-gap/">it’s tricky to detect</a>.</p>
<p>The rest of this block is pretty darn self-explanatory and we still have <em>a lot</em> to cover, so let’s write some JavaScript.</p>
<p>First, take a look at your lush, Minimum Viable Experience:</p>
<p><img src="https://piccalil.b-cdn.net/images/tutorials/bm-ss-2.jpg" alt="A fully styled page with a bright reddish pink main site head" /></p>
<h2>JavaScript</h2>
<p>Now to move on to the main course of this tutorial. Have a quick stretch, get a drink, then get comfy, because this is where we’re going to really <em>dig in</em> to the details.</p>
<h2>Burger menu web component</h2>
<p>We’ll start with the main component itself. Open up <code>js/burger-menu.js</code> and add the following.</p>
<pre><code>class BurgerMenu extends HTMLElement {
  constructor() {
    super();

    const self = this;

    this.state = new Proxy(
      {
        status: 'open',
        enabled: false
      },
      {
        set(state, key, value) {
          const oldValue = state[key];

          state[key] = value;
          if (oldValue !== value) {
            self.processStateChange();
          }
          return state;
        }
      }
    );
  }

  get maxWidth() {
    return parseInt(this.getAttribute('max-width') || 9999, 10);
  }

  connectedCallback() {
    this.initialMarkup = this.innerHTML;
    this.render();
  }

  render() {
    this.innerHTML = `
      &lt;div class="burger-menu" data-element="burger-root"&gt;
        &lt;button class="burger-menu__trigger" data-element="burger-menu-trigger" type="button" aria-label="Open menu"&gt;
          &lt;span class="burger-menu__bar" aria-hidden="true"&gt;&lt;/span&gt;
        &lt;/button&gt;
        &lt;div class="burger-menu__panel" data-element="burger-menu-panel"&gt;
          ${this.initialMarkup} 
        &lt;/div&gt;
      &lt;/div&gt;
    `;

    this.postRender();
  }
}

if ('customElements' in window) {
  customElements.define('burger-menu', BurgerMenu);
}

export default BurgerMenu;
</code></pre>
<p>We’ve got a heck of a chunk of JavaScript here, but fear-not, we will go through what is happening.</p>
<p>First of all, we’re creating a <a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements">custom element—a web component</a>. We instantiate a new class which extends <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement"><code>HTMLElement</code></a>, which is the basis of <strong>all HTML elements</strong>.</p>
<div><h2>FYI</h2>
  A custom element uses JavaScript classes. If you’ve not done much with these already, I definitely recommend that you have a quick break from this (don’t worry, we’ll wait) to [brush up on how they work with this handy resource](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes).
</div>
<p>Inside our <code>BurgerMenu</code> class: we’ve got the constructor, which initialises everything when it’s loaded. The first thing we do is call <code>super()</code>, which tells our extended <code>HTMLElement</code> to do the same.</p>
<p>The next part is one of my favourite features of modern JavaScript: <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy">Proxies</a>. These are a handy type of object that let us do <em>loads</em> of cool stuff, but my favourite part is that we can <strong>observe and manipulate changes</strong>. <a href="https://piccalil.li/tutorial/build-a-light-and-global-state-system">I wrote up about them in this tutorial</a> and we’re using a similar setup to manage our component’s <code>state</code>.</p>
<p>In a nutshell, we create a <code>set</code> method in our <code>Proxy</code> which gets fired every time a value of our <code>state</code> is changed. For example, if elsewhere in our code, we write <code>state.enabled = false</code>, the <code>set</code> method of our <code>Proxy</code> is fired and it’s <em>in here</em> where we intercept and commit that change.</p>
<p>Alongside committing that change, we’re comparing the old value to the new value. If that value has changed, we fire of a yet-to-exist, <code>processStateChange()</code> method. We’ve got some light, reactive state going on here and it’s all baked into vanilla JavaScript. Cool, right?</p>
<p>Next up, we use a handy modern JavaScript feature, a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get">getter</a> to grab the <code>max-width</code> property from our <code>&lt;burger-menu&gt;</code> instance. This gives the component a <code>maxWidth</code> value, which we’ll use later to determine wether or not to enable our burger menu.</p>
<p>After this getter, we come across a <a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements#using_the_lifecycle_callbacks">lifecycle callbacks</a>. These—if defined in your component—fire off at various points in a component’s lifecycle. In this instance, the <code>connectedCallback</code> fires when our <code>&lt;burger-menu&gt;</code> is appended to the document. Think of it as a “ready” state. Now we know our component is connected, we’re going to tell our component to <code>render()</code>.</p>
<p>Before we do that, though, we store the markup that’s inside the <code>&lt;burger-menu&gt;</code>, which in our case is the <code>&lt;nav&gt;</code> element and it’s content. We store it for two reasons:</p>
<ol>
<li>We’re going to render that same markup inside our component markup</li>
<li>If all fails in this component, we can re-render the markup as if there was no burger menu whatsoever</li>
</ol>
<p>Moving swiftly onto the <code>render()</code> method: we’re using a template literal to write out some HTML. You’ll notice inside that HTML, we call on our <code>initialMarkup</code>, which we just stored. The markup is pretty straightforward. It’s a trigger button and an associated panel—similar to a <a href="https://piccalil.li/tutorial/a-progressive-disclosure-component">disclosure element</a>.</p>
<p>Lastly, we apply this component using the <code>customElements.define('burger-menu', BurgerMenu);</code> line <em>only</em> if <code>customElements</code> is available—another bit of progressive enhancement. We also <code>export</code> the class as a <code>default</code> export for if someone was to <code>import</code> this component.</p>
<p>Finally, after all this is set, we move on to <code>this.postRender()</code> which we will add now. Still in <code>burger-menu.js</code>, <strong>under the <code>render()</code> method</strong>, add the following:</p>
<pre><code>postRender() {
  this.trigger = this.querySelector('[data-element="burger-menu-trigger"]');
  this.panel = this.querySelector('[data-element="burger-menu-panel"]');
  this.root = this.querySelector('[data-element="burger-root"]');
  this.focusableElements = getFocusableElements(this);

  if (this.trigger &amp;&amp; this.panel) {
    this.toggle();

    this.trigger.addEventListener('click', evt =&gt; {
      evt.preventDefault();

      this.toggle();
    });

    document.addEventListener('focusin', () =&gt; {
      if (!this.contains(document.activeElement)) {
        this.toggle('closed');
      }
    });

    return;
  }

  this.innerHTML = this.initialMarkup;
}
</code></pre>
<p>There’s quite a bit going on in this method, whose role is to handle the fresh new HTML that’s just been rendered.</p>
<p>The first thing we do is grab the elements we want. I personally like to use <code>[data-element]</code> attributes for selecting elements with JavaScript, but really, do whatever works for you and your team. I certainly don’t do it for any good reason other than it makes it more obvious what elements have JavaScript attached to them.</p>
<p>The next thing we do is test to see if the <code>trigger</code> and <code>panel</code> are <strong>both</strong> present. Without both of these, our burger menu is redundant. If they are both there, we fire off the yet-to-be-defined <code>toggle()</code> method and wire up a click event to our <code>trigger</code> element, which again, fires off the <code>toggle()</code> method.</p>
<p>The next part is an accessibility pro tip. The burger menu—when we finish applying all of the CSS—covers the entire viewport. This means that if the user is shifting focus with their tab key and focus escapes the burger menu itself: they will lose focus <em>visually</em>. This is a poor user experience, so this <code>focusin</code> event listener on the <code>document</code>, <em>outside</em> of this component tests to see if the currently focused element—<code>document.activeElement</code>—is inside our component. If it isn’t: we force the menu closed, immediately.</p>
<p>Lastly, as a last-ditch fallback, we re-render the original markup. This is to make sure that if all fails, the user still gets the minimum viable experience. Don’t you just <em>love</em> the smell of progressive enhancement?</p>
<p>Let’s define that <code>toggle()</code> method. Still inside the <code>burger-menu.js</code> file, add the following <strong>after</strong> the <code>postRender()</code> method:</p>
<pre><code>toggle(forcedStatus) {
  if (forcedStatus) {
    if (this.state.status === forcedStatus) {
      return;
    }

    this.state.status = forcedStatus;
  } else {
    this.state.status = this.state.status === 'closed' ? 'open' : 'closed';
  }
}
</code></pre>
<p>In <code>toggle()</code>, we can pass an optional <code>forcedStatus</code> parameter which—just like in the above focus management—let’s us force the component into a specific, finite state: <code>'open'</code> or <code>'closed'</code>. If that isn’t defined, we set the current <code>state.status</code> to be open or closed, depending on what the current status is, using a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator">ternary operator</a>.</p>
<p>Now that we’ve got a state toggle, let’s process that state. We’ll add the method that is called in our <code>Proxy</code>: <code>processStateChange()</code>. Still in <code>burger-menu.js</code>, add the following <strong>after</strong> the <code>toggle()</code> method:</p>
<pre><code>processStateChange() {
  this.root.setAttribute('status', this.state.status);
  this.root.setAttribute('enabled', this.state.enabled ? 'true' : 'false');

  this.manageFocus();

  switch (this.state.status) {
    case 'closed':
      this.trigger.setAttribute('aria-expanded', 'false');
      this.trigger.setAttribute('aria-label', 'Open menu');
      break;
    case 'open':
    case 'initial':
      this.trigger.setAttribute('aria-expanded', 'true');
      this.trigger.setAttribute('aria-label', 'Close menu');
      break;
  }
}
</code></pre>
<p>This method is fired every time state changes, so its only job is to grab the current state of our component and reflect it where necessary. The first part of that is setting our root element’s attributes. We’re going to use this as style hooks later. Then, we set the <code>aria-expanded</code> attribute and the <code>aria-label</code> attribute on our trigger. We’ll do the actual visual toggling of the panel with CSS.</p>
<div><h2>FYI</h2>
  We’re using a disclosure pattern to do the visual toggling of this component. I wrote a tutorial on that a while ago that explains how the `aria-expanded` attribute works. [Check it out](https://piccalil.li/tutorial/a-progressive-disclosure-component)!
</div>
<p>We’re getting close now, pals, hang in there. This is a <em>long</em> tutorial, but heck, we are creating something pretty darn resilient. Let’s wrap up the JavaScript with some focus management, then get back to the comfort and warmth of CSS.</p>
<p>We referenced a <code>manageFocus()</code> method earlier that we need to write, so still in <code>burger-menu.js</code>, <strong>after</strong> the <code>processStateChange</code> method, add the following:</p>
<pre><code>manageFocus() {
  if (!this.state.enabled) {
    this.focusableElements.forEach(element =&gt; element.removeAttribute('tabindex'));
    return;
  }

  switch (this.state.status) {
    case 'open':
      this.focusableElements.forEach(element =&gt; element.removeAttribute('tabindex'));
      break;
    case 'closed':
      [...this.focusableElements]
        .filter(
          element =&gt; element.getAttribute('data-element') !== 'burger-menu-trigger'
        )
        .forEach(element =&gt; element.setAttribute('tabindex', '-1'));
      break;
  }
}
</code></pre>
<p>Here, we look grab our focusable elements (we’re doing that bit next) and then depending on wether or not we’re in an open or closed, state, we add <code>tabindex="-1"</code> or remove it. We add it when we in a closed state because if you remember rightly, this prevents keyboard focus. For the same reason we automatically closed the menu when focus escaped in the open state, earlier, we are now preventing focus from leaking in if it is closed.</p>
<div><h2>FYI</h2>
  You might be thinking, “what the heck is Andy doing with `[...`??”. This is called the [spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) and what we are doing here is converting our `NodeList` into an `Array` so we can use the `filter()` method to filter out the `trigger` element.
</div>
<p>We now need to add a mechanism that enables or disables the burger menu UI, based on the <code>maxWidth</code> property. To do that, we’re going to use a <a href="https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver"><code>ResizeObserver</code></a> which does exactly what it says on the tin: observes resizing.</p>
<p>Go back to the <code>connectedCallback()</code> method and <strong>inside it</strong>, add the following:</p>
<pre><code>const observer = new ResizeObserver(observedItems =&gt; {
  const {contentRect} = observedItems[0];
  this.state.enabled = contentRect.width &lt;= this.maxWidth;
});

// We want to watch the parent like a hawk
observer.observe(this.parentNode);
</code></pre>
<p>The <code>ResizeObserver</code> gives us a callback, every time the observed element changes size—in our case, the <code>&lt;burger-menu&gt;</code> parent, which happens to be <code>.site-head__inner</code>—we can monitor it and if needed, react to it.</p>
<p>We <a href="https://css-tricks.com/learning-gutenberg-4-modern-javascript-syntax/#destructuring-assignment">destructure</a> the <code>contentRect</code> out of the first item in <code>observedItems</code> (our <code>.site-head__inner</code> element) which gives us its dimensions. We then set our <code>state.enabled</code> flag based on wether or not is is less than, or equal to, our <code>maxWidth</code> property.</p>
<p>You might think that doing this in JavaScript is daft, but here me out: this is a low-level Container Query! It means this burger menu could be put <em>anywhere</em> in a UI. The <code>ResizeObserver</code> is <em>super</em> performant too, so there’s not much to worry about on that front.</p>
<p>Right, let’s add the final piece of the JavaScript puzzle: the helper method that finds all focusable elements for us. You can <a href="https://piccalil.li/quick-tip/load-all-focusable-elements-with-javascript">read up on how it works, here</a>.</p>
<p>First up, still inside the <code>buger-menu.js</code> file, add the following <strong>right at the top of the file</strong>:</p>
<pre><code>import getFocusableElements from './get-focusable-elements.js';
</code></pre>
<p>All we’re doing here is importing our helper, so let’s go ahead and write that method. Open up <code>get-focusable-elements.js</code> and add the following to it:</p>
<pre><code>/**
 * Returns back a NodeList of focusable elements
 * that exist within the passed parnt HTMLElement
 *
 * @param {HTMLElement} parent HTML element
 * @returns {NodeList} The focusable elements that we can find
 */
export default parent =&gt; {
  if (!parent) {
    console.warn('You need to pass a parent HTMLElement');
    return [];
  }

  return parent.querySelectorAll(
    'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"]):not([disabled]), details:not([disabled]), summary:not(:disabled)'
  );
};
</code></pre>
<p>This is just like a CUBE CSS utility, really. It does one job really well for us!</p>
<h2>Burger menu CSS</h2>
<p>We’ve got the burger menu interactivity written now and you might be happy to know that we <em>are done</em> with JavaScript. If you refresh your browser, it will be a <em>mess</em>, so let’s make it look good.</p>
<p>Open up <code>css/global.css</code> and add the following:</p>
<pre><code>.burger-menu__trigger {
  display: none;
}
</code></pre>
<p>Why the heck are we hiding the trigger? Well, think back to our <code>state.enabled</code> flag. If the component is disabled—which is our default state—we don’t want to present a trigger. Hiding it with <code>display: none</code> will hide it from screen readers, too.</p>
<p>Let’s build the actual hamburger icon. We’ll do it all with CSS, so still in <code>global.css</code>, add the following:</p>
<pre><code>.burger-menu__bar,
.burger-menu__bar::before,
.burger-menu__bar::after {
  display: block;
  width: 24px;
  height: 3px;
  background: var(--color-light);
  border: 1px solid var(--color-light);
  position: absolute;
  border-radius: 3px;
  left: 50%;
  margin-left: -12px;
  transition: transform 350ms ease-in-out;
}

.burger-menu__bar {
  top: 50%;
  transform: translateY(-50%);
}

.burger-menu__bar::before,
.burger-menu__bar::after {
  content: '';
}

.burger-menu__bar::before {
  top: -8px;
}

.burger-menu__bar::after {
  bottom: -8px;
}
</code></pre>
<p>The first thing to note here is that we’re using good ol’ pixel sizes because we really want some control of how this thing is sized. The first thing we do is target the <code>.burger-menu__bar</code> (which lives inside the trigger) and both its <code>::before</code> and <code>::after</code> pseudo-elements and make them <em>all</em> look the same as each other: a bar.</p>
<p>After this, we break off and target specific parts—so positioning the <code>.burger-menu__bar</code> dead-center with absolute positioning, which allows us to comfortably animate it, knowing it won’t affect layout. We then add <code>content: ''</code> to both the pseudo-elements so they render and push one up and one down. This gives us our hamburger!</p>
<p>We’ll leave this hamburger for now and deal with our <code>enabled</code> state in CSS.</p>
<h3>Handling the <code>enabled</code> state</h3>
<p>Our <code>BurgerMenu</code> enables and disables itself based on its parent’s width and its own <code>maxWidth</code> property. We need to handle this state with CSS.</p>
<p>We’re going to use the <a href="https://cube.fyi/exception/">CUBE CSS Exception principle</a> to do this, which means hooking into data attribute values in our CSS. The <code>BurgerMenu</code> sets an <code>[enabled="true|false"]</code> attribute on our <code>.burger-menu</code> component, so let’s deal with that.</p>
<p>Still in <code>global.css</code>, add the following:</p>
<pre><code>.burger-menu[enabled='true'] .burger-menu__trigger {
  display: block;
  width: 2rem;
  height: 2rem; /* Nice big tap target */
  position: relative;
  z-index: 1;
  background: transparent;
  border: none;
  cursor: pointer;
}

.burger-menu[enabled='true'] .burger-menu__panel {
  position: absolute;
  top: 0;
  left: 0;
  padding: 5rem 1.5rem 2rem 1.5rem;
  width: 100%;
  height: 100%;
  visibility: hidden;
  opacity: 0;
  background: var(--color-primary-shade);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
</code></pre>
<p>Because the burger menu is enabled, we can style up the trigger and the panel. The trigger is a transparent button, because it houses the burger bars we just created. We do make the button considerably bigger than them, though, so there’s a decent sized tap target.</p>
<p>For the panel, we make it fill the screen. We set the vertical overflow to be <code>auto</code> so long menus can be scrolled. Lastly, we make it hidden by using <code>opacity</code> and <code>visibility</code>.</p>
<div><h2>FYI</h2>
  Pro tip: if you set `visibility: hidden`, it will hide the element from a screen reader, so just be aware of that!
</div>
<p>Let’s add some more CSS. Inside <code>global.css</code>, add the following:</p>
<pre><code>.burger-menu[enabled='true'] .navigation ul {
  display: block;
}

.burger-menu[enabled='true'] .navigation ul &gt; * + * {
  margin-top: 2rem;
}

.burger-menu[enabled='true'] .navigation li {
  font-size: 1.5rem;
}
</code></pre>
<p>What we are doing here is converting our navigation into a stacked menu when the burger menu is enabled. This is where the enabled flag is super handy because we don’t need to rely on viewport-wide media queries and instead, we have full control over our specific context, instead. Ah, just leave me to dream about Container Queries for a second, will you?</p>
<p>Right, back from my dreaming, let’s wrap up with our interactive states! Add the following to <code>global.css</code>:</p>
<pre><code>.burger-menu[enabled='true'][status='open'] .burger-menu__panel {
  visibility: visible;
  opacity: 1;
  transition: opacity 400ms ease;
}

.burger-menu[enabled='true'][status='closed'] .burger-menu__panel &gt; * {
  opacity: 0;
  transform: translateY(5rem);
}

.burger-menu[enabled='true'][status='open'] .burger-menu__panel &gt; * {
  transform: translateY(0);
  opacity: 1;
  transition: transform 500ms cubic-bezier(0.17, 0.67, 0, 0.87) 700ms, opacity 500ms ease
      800ms;
}
</code></pre>
<p>The panel’s visibility can only be changed <strong>if</strong> the burger menu is enabled <strong>and</strong> the status is open. This is a great example of finite state giving our UI some real resilience and importantly, reducing the risk of presenting a broken or confusing state to our users.</p>
<p>When the panel is “open”, we transition the <code>opacity</code> to <code>1</code> and set <code>visibility</code> to <code>visible</code> to show it. I like to only add transitions in the changed state like this. It makes the UI much snappier when elements revert <em>immediately</em>.</p>
<p>The last section is pretty cool, too. In the “closed” state, we visually hide the navigation items with <code>opacity</code> and push them down with <code>transform</code>. When the panel is in the “open” state, we transition them back to being visible with full opacity. It gives us a lovely transition effect.</p>
<p>Right, let’s add the <em>last bit of code</em>. In <code>global.css</code>, add the following:</p>
<pre><code>.burger-menu[enabled='true'][status='open'] .burger-menu__bar::before {
  top: 0;
  transform: rotate(45deg);
}

.burger-menu[enabled='true'][status='open'] .burger-menu__bar::after {
  top: 0;
  transform: rotate(-45deg);
}

.burger-menu[enabled='true'][status='open'] .burger-menu__bar {
  background: transparent;
  border-color: transparent;
  transform: rotate(180deg);
}
</code></pre>
<p>This is our burger bars converting themselves into a close icon when the menu is open. We achieve this by first, setting the background and border of the main (central) bar to be transparent, then we rotate the pseudo-elements in opposite directions to create a cross. Lastly, we spin it all around by transitioning the whole thing 180 degrees.</p>
<p>Again, we’re using our state to determine this, which admittedly, creates some gnarly selectors, but it also provides resilience and solidity.</p>
<p>With that, <strong>we are done</strong>. If you refresh your browser, resize it and toggle the menu, it should all look like this.</p>
<p><video></video></p>
<h2>Wrapping up</h2>
<p>That was a hell of a long tutorial. Fair play to you for sticking it out! If your version is broken, you can go ahead and <a href="https://piccalilli.s3.eu-west-2.amazonaws.com/4d9533344b6ed90d0e81b5ce0421a31f.zip">download a complete copy, here</a>. You can also <a href="https://piccalilli-burger-menu-demo.netlify.app/">see a live version, here</a>.</p>
<p><a href="https://piccalilli.s3.eu-west-2.amazonaws.com/4d9533344b6ed90d0e81b5ce0421a31f.zip">Download final version</a></p>
<p>I hope you’ve learned a lot in this tutorial, but the main takeaway I’d love you to go away with is that even seemingly simple interactive elements—like burger menus—get really complex when you make them fully inclusive. Luckily, progressive enhancement makes that inclusivity a little bit easier.</p>
<p>Until next time, take it easy 👋</p></div>
        
        ]]></description>
        
      </item>
    
      <item>
        <title>Picture element as a progressive enhancement</title>
        <link>https://piccalil.li/blog/picture-as-a-progressive-enhancement/?ref=html-category-rss-feed</link>
        <dc:creator><![CDATA[Andy Bell]]></dc:creator>
        <pubDate>Fri, 18 Sep 2020 00:00:00 GMT</pubDate>
        <guid isPermaLink="true">https://piccalil.li/blog/picture-as-a-progressive-enhancement/?ref=html-category-rss-feed</guid>
        <description><![CDATA[<p>The <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture">HTML Picture element</a> is useful not just for responsive images, but it’s also great for progressively displaying images in a suitable, supported format.</p>
<p>You can use <a href="https://caniuse.com/avif">AVIF</a> and <a href="https://caniuse.com/webp">WebP</a> formats right now and still support old browsers by using a <code>&lt;picture&gt;</code> element like this:</p>
<pre><code>&lt;picture&gt;
  &lt;source type="image/avif" srcset="https://assets.codepen.io/174183/hankchizlbork.avif" /&gt;
  &lt;source type="image/webp" srcset="https://assets.codepen.io/174183/hankchizlbork.webp" /&gt;
  &lt;img src="https://assets.codepen.io/174183/hankchizlbork.jpg" alt="Me looking like a complete bork" width="1500 " height="1585" /&gt;
&lt;/picture&gt;
</code></pre>
<p>This is a great example of how progressive enhancement can help you to provide the best experience for anyone that visits your site, regardless of how they access it.</p>
<p>This snippet works in <strong>every browser</strong> because HTML is an incredibly forgiving and intelligent programming language. If a browser doesn’t support a <code>&lt;picture&gt;</code> element, the browser will ignore it and render the <code>&lt;img&gt;</code> as usual. If a browser does support <code>&lt;picture&gt;</code>, but doesn’t for example, support AVIF images: it will pick the format it can understand, including the default jpeg. Handy!</p>
<p>Go ahead and try the following demo is various different browsers 👇</p>
        
        ]]></description>
        
      </item>
    
    </channel>
  </rss>
