<?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, 07 Apr 2026 01:02:02 GMT</pubDate>
      <lastBuildDate>Tue, 07 Apr 2026 01:02:02 GMT</lastBuildDate>

      
      <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>
    
      <item>
        <title>Create a semantic breakout button to make an entire element clickable</title>
        <link>https://piccalil.li/blog/create-a-semantic-break-out-button-to-make-an-entire-element-clickable/?ref=html-category-rss-feed</link>
        <dc:creator><![CDATA[Andy Bell]]></dc:creator>
        <pubDate>Fri, 27 Sep 2019 00:00:00 GMT</pubDate>
        <guid isPermaLink="true">https://piccalil.li/blog/create-a-semantic-break-out-button-to-make-an-entire-element-clickable/?ref=html-category-rss-feed</guid>
        <description><![CDATA[<p>A common design pattern is to have something like a “card” element that has to be fully clickable. This is usually because it links to another page or triggers a JavaScript action.</p>
<p>The problem though, is that often, you end up with stuff that looks like this:</p>
<pre><code>&lt;div onClick="alert('Nope')"&gt;Please don’t ever do this.&lt;/div&gt;
</code></pre>
<p>You should absolutely never attach a click event to a <code>&lt;div&gt;</code> element, though, even if you sprinkle it with <a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques">aria roles</a> to “fake” a real button. <a href="https://piccalil.li/wrote/context-and-caveats/">Although this is technically possible</a>, if assistive technology doesn’t support the aria roles in question, the user will just get <code>&lt;div&gt;</code>s and nothing else. <strong>Not cool</strong>.</p>
<p>In this article, we’re going to remedy this common crime by instead using the magic of CSS to give us the desired fully clickable element effect, while also using proper semantic elements and JavaScript and as an enhancement.</p>
<h2>What we’re making</h2>
<p>We’re going to use the context of an <code>&lt;article&gt;</code> that when a button is clicked, a JavaScript alert fires. Here’s a CodePen demo:</p>
<p></p><p>See the Pen <a href="https://codepen.io/piccalilli/pen/3b85e3dbeaa97221d06a9d055541f680">Semantic “break-out” button</a> by Andy Bell (<a href="https://codepen.io/piccalilli/">@piccalilli</a>) on <a href="https://codepen.io">CodePen</a>.</p><p></p>
<p>To code along, you’re going to need a HTML file, a CSS file and a JavaScript file. I recommend that you use a service like <a href="https://codepen.io/">CodePen</a> that does all of this for you.</p>
<p>For the rest of this section, we’re going to be laying the foundations. If you’re in a rush, you can <a href="#heading-creating-the-breakout-button-component">skip this section</a> and use this <a href="https://codepen.io/andybelldesign/pen/43dd6339a10e9830264524bbdef3fcfc">starter CodePen</a> instead.</p>
<h3>Getting started with markup</h3>
<p>We need some HTML to work with, so add this:</p>
<pre><code>&lt;article class="box"&gt;
  &lt;h1&gt;A semantic, breakout button&lt;/h1&gt;
  &lt;p&gt;This whole box is clickable, but still uses a button element, correctly.&lt;/p&gt;
  &lt;button class="breakout-button" type="button"&gt;Say Hi 👋&lt;/button&gt;
&lt;/article&gt;
</code></pre>
<p>We’ve got an article, a heading, a paragraph and a button. Job done for HTML for now.</p>
<h3>Base CSS styles</h3>
<p>Before we get into the proper CSS stuff, let’s set some base styles. Add this CSS:</p>
<pre><code>body {
  background: #efefef;
  padding: 3rem 2rem;
  line-height: 1.4;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial,
    sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
  font-size: 1.2rem;
}

h1 {
  font-size: 2rem;
  line-height: 1.1;
  font-weight: 600;
}

.box {
  color: #fff;
  padding: 2rem;
  max-width: 30rem;
  background: #252525;
  position: relative;
  box-shadow: none;
  transition: transform 300ms ease-in-out, box-shadow 400ms ease, background 100ms ease;
}

.box:hover,
.box:focus-within {
  background: #111111;
  box-shadow: 0 1rem 1rem rgba(0, 0, 0, 0.3);
  transform: translateY(-0.5rem);
}

.box &gt; * + * {
  margin-top: 1em;
}
</code></pre>
<p>Looking pretty decent, right? That’s it for our base styles.</p>
<h2>Creating the breakout button component</h2>
<p>We’ve got base styles, so let’s focus all of our attention on the <code>.breakout-button</code> component.</p>
<h3>Core component styles</h3>
<p>In your CSS, add the following:</p>
<pre><code>.breakout-button {
  font: inherit;
  font-weight: 600;
  padding: 0.6rem 2rem;
  background: transparent;
  color: currentColor;
  border: 1px solid;
  transition: background 100ms ease;
  position: static;
}
</code></pre>
<p>Here, we have some simple styles for our button which make it looks nicer. A handy takeaway trick here is that because we are using <code>font: inherit</code> and <code>color: currentColor</code>, we get all of our text styles for free, using the cascade, which is by far my favourite aspect of CSS. Also notice that we are setting <code>.breakout-button</code> to be <code>position: static</code>. This is because we want our “breakout element” (coming up) to literally <em>break out</em> of the button!</p>
<p>Related to this is that the <code>.box</code> element has <code>position: relative</code>, which means that any child elements without a relative parent that have <code>position: absolute</code> will be contained in this <code>.box</code>. Because that’s what our breakout button will do as its sole purpose, you should always remember to make its containing parent behave like this. The <code>position: static</code> on the <code>.breakout-button</code> itself is a failsafe to make sure the “breakout element” isn’t ever contained to the <code>.breakout-button</code> element.</p>
<p>Right, let’s add some more code. Under the <code>.breakout-button</code> component style, add this CSS:</p>
<pre><code>.breakout-button,
.breakout-button::before {
  cursor: pointer;
}
</code></pre>
<p>We’ve set both the button and its <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-elements"><code>::before</code> pseudo-element</a> to have a <code>pointer</code> cursor. There’s some contention around wether a <code>&lt;button&gt;</code> should have a <code>pointer</code> cursor and I very much sit in the camp that by proxy of exposure, it is now an expected style and shouldn’t really have a negative user experience impact.</p>
<p>Anyway, we digress… Add the following CSS to create the “breakout element”:</p>
<pre><code>.breakout-button::before {
  content: '';
  display: block;
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}
</code></pre>
<p>It’s pretty straightforward. We make this pseudo-element an absolutely positioned block element that will “break out” until it hits the bounds of a <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/position">relative, absolute or fixed</a> parent. In our context, this parent is the <code>.box</code> element because it has <code>position: relative</code> set.</p>
<p>The “breakout element” is visually hidden, so to help you understand how it behaves, check out this demo where I’ve added some colour to it:</p>
<p></p><p>See the Pen <a href="https://codepen.io/piccalilli/pen/87e1796e1212d6759faf9bca250f4e1f">Semantic “break-out” button (pseudo element exposed)</a> by Andy Bell (<a href="https://codepen.io/piccalilli/">@piccalilli</a>) on <a href="https://codepen.io">CodePen</a>.</p><p></p>
<h3>Interactive styles</h3>
<p>Now that we’ve got the core component setup, let’s style up the interactivity. Add this to your CSS:</p>
<pre><code>.breakout-button:focus {
  outline: none;
}

.breakout-button:hover {
  background: #333333;
}
</code></pre>
<p>The first thing we do is remove focus outline from the button. I can’t stress this enough, though: you <strong>must</strong> have visible focus styles for interactive elements, so keyboard users can actually see where their focus currently is. If you remove the default <code>outline</code> CSS rule, you <strong>must</strong> replace it with something effective and obvious.</p>
<p>This is exactly what we’re going to do now, by adding a solid outline to our “breakout element” when the parent button is focused. Add this CSS:</p>
<pre><code>.breakout-button:focus::before {
  outline: 1px solid #ffffff;
  outline-offset: -0.8rem;
}
</code></pre>
<p>Now that we’ve sorted our button out, let’s add some hover styles to our <code>.box</code> element. Add this to your CSS, with all of the other <code>.box</code> styles:</p>
<pre><code>.box:hover,
.box:focus-within {
  background: #111111;
  box-shadow: 0 1rem 1rem rgba(0, 0, 0, 0.3);
  transform: translateY(-0.5rem);
}
</code></pre>
<p>This makes our box shift up either on hover, or if there’s focus inside it, which means when our button element is focused, our box’s appearance will change.</p>
<p>That’s it! Our “breakout button” finished and this is what it should look like:</p>
<p></p><p>See the Pen <a href="https://codepen.io/piccalilli/pen/3b85e3dbeaa97221d06a9d055541f680">Semantic “break-out” button</a> by Andy Bell (<a href="https://codepen.io/piccalilli/">@piccalilli</a>) on <a href="https://codepen.io">CodePen</a>.</p><p></p>
<h2>Improving our breakout button with progressive enhancement</h2>
<p>As usual, I make you think that you’re done, but then slip in some progressive enhancement when you least expect it, because as it stands, our project is just <em>ok</em>. Because we’re using a <code>&lt;button&gt;</code>, it’ll be about as useful as a chocolate teapot when JavaScript isn’t available. What we’re going to do to fix this is hide the button by default with a <code>hidden</code> attribute, and when JavaScript is available, we’ll show it by removing the <code>hidden</code> attribute.</p>
<p>We’re also going to add a bonus bit of attention to detail and only display the interactive states when JavaScript is available too, using a <a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes">data attribute</a> as a style hook.</p>
<p>Open up your HTML and add a <code>hidden</code> attribute to the <code>&lt;button&gt;</code> like this:</p>
<pre><code>&lt;button class="breakout-button" type="button" hidden&gt;Say Hi 👋&lt;/button&gt;
</code></pre>
<p>Now we need to add some JavaScript to show the <code>&lt;button&gt;</code> by removing that <code>hidden</code> element:</p>
<pre><code>const button = document.querySelector('.breakout-button');

if (button) {
  button.removeAttribute('hidden');
}
</code></pre>
<p>Like I said above, though, we should also only show interactive styles such as <code>:hover</code> and <code>:focus</code> states when the button is available too. Let’s replace your existing JavaScript with this JavaScript:</p>
<pre><code>const button = document.querySelector('.breakout-button');

if (button) {
  button.parentElement.setAttribute('data-interactive', '');
  button.removeAttribute('hidden');

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

    alert('Oh hi there 👋');
  });
}
</code></pre>
<p>We’ve added an event handler for the button’s click event, but most importantly, we’ve added a <code>data-interactive</code> attribute to the button’s parent, which means we can now use this as a style hook.</p>
<p>Amend your CSS by deleting this block:</p>
<pre><code>.box:hover,
.box:focus-within {
  background: #111111;
  box-shadow: 0 1rem 1rem rgba(0, 0, 0, 0.3);
  transform: translateY(-0.5rem);
}
</code></pre>
<p>And now, replace it with this block:</p>
<pre><code>[data-interactive]:hover,
[data-interactive]:focus-within {
  background: #111111;
  box-shadow: 0 1rem 1rem rgba(0, 0, 0, 0.3);
  transform: translateY(-0.5rem);
}
</code></pre>
<p>Now, the <code>.box</code> will behave like a normal element when JavaScript isn’t available, like so:</p>
<p></p><p>See the Pen <a href="https://codepen.io/piccalilli/pen/b853fb1a71529dfb1e4cc97d154fd0dc">Semantic “break-out” button - progressive enhancement (JS disabled)</a> by Andy Bell (<a href="https://codepen.io/piccalilli/">@piccalilli</a>) on <a href="https://codepen.io">CodePen</a>.</p><p></p>
<h2>Wrapping up</h2>
<p>That’s it, we’re done. Yours should now look like this:</p>
<p></p><p>See the Pen <a href="https://codepen.io/piccalilli/pen/NWKmqRm">Semantic, progressively enhanced “break-out” button</a> by Andy Bell (<a href="https://codepen.io/piccalilli/">@piccalilli</a>) on <a href="https://codepen.io">CodePen</a>.</p><p></p>
<p>Hopefully this article shows that when you think outside the box (or inside it in this context), CSS, semantic HTML and a sprinkle of JavaScript can give you solid, progressive components that work for everyone.</p>
        
        ]]></description>
        
      </item>
    
    </channel>
  </rss>
