<?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 - a11y topic archive</title>
      <link>https://piccalil.li/</link>
      <atom:link href="https://piccalil.li/category/a11y.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 - a11y topic archive 2026</copyright>
      <docs>https://www.rssboard.org/rss-specification</docs>
      <pubDate>Tue, 07 Apr 2026 22:02:08 GMT</pubDate>
      <lastBuildDate>Tue, 07 Apr 2026 22:02:08 GMT</lastBuildDate>

      
      <item>
        <title>Practical Accessibility Tips You Can Apply Today</title>
        <link>https://piccalil.li/blog/practical-accessibility-tips-you-can-apply-today/?ref=a11y-category-rss-feed</link>
        <dc:creator><![CDATA[Kevin Andrews]]></dc:creator>
        <pubDate>Thu, 03 Oct 2024 12:54:25 GMT</pubDate>
        <guid isPermaLink="true">https://piccalil.li/blog/practical-accessibility-tips-you-can-apply-today/?ref=a11y-category-rss-feed</guid>
        <description><![CDATA[<p>We’ve all been there—rushing to meet a deadline, we throw together a dropdown menu or modal without fully considering its accessibility. But what if making a few small changes could drastically improve the experience for a broader range of users? Accessibility doesn’t have to be a daunting task or a compliance box to tick. It’s about creating products that are usable by everyone, no matter their ability, technical literacy, operating system, or device.</p>
<p>In this article, I’ll share practical advice on marking up three common UI patterns the right way. Whether you’re new to accessibility or just need a refresher, these tips will help you build more inclusive interfaces. I also link out to implementations and external resources so you can experience firsthand how these changes affect usability. You’ll see how a few thoughtful adjustments can make all the difference.</p>
<h2>Dropdown menus</h2>
<p>Dropdown menus are one of the most commonly used UI elements across the web. From navigation bars to filtering systems, they make it easier to present multiple options in a clean and compact way. But while dropdowns may look simple, they’re often built in a way that leaves many users unable to interact with them effectively.</p>
<h3>The problem</h3>
<p>The issue with a lot of dropdowns is that they are constructed with generic elements like <code>&lt;div&gt;</code> or <code>&lt;span&gt;</code> with interaction typically handled through JavaScript. While this might work for a mouse user, someone using a keyboard, screen reader, or other assistive technology will struggle. If dropdowns aren’t properly marked up, users might not even know the options exist or may be unable to navigate them.</p>
<h3>The fix</h3>
<p>To make dropdown menus more accessible, the first step is to use semantic HTML. This means using elements that communicate their purpose to the browser and assistive technologies. In this case, using a <code>&lt;button&gt;</code> for the dropdown trigger and a <code>&lt;ul&gt;</code> for the list of options provides the structure assistive technologies need to interpret the menu correctly.</p>
<p>Next, you’ll want to add ARIA (Accessible Rich Internet Applications) attributes like aria-expanded and aria-controls. These attributes give screen readers additional context, such as whether the menu is open or closed, and which element controls the dropdown.</p>
<pre><code>&lt;button aria-haspopup="true" aria-expanded="false" aria-controls="menu1"&gt;Options&lt;/button&gt;
&lt;ul id="menu1" role="menu" aria-hidden="true"&gt;
  &lt;li role="menuitem"&gt;Option 1&lt;/li&gt;
  &lt;li role="menuitem"&gt;Option 2&lt;/li&gt;
&lt;/ul&gt;
</code></pre>
<p>Let’s break it down:</p>
<ul>
<li>The <code>&lt;button&gt;</code> element with <code>aria-haspopup="true"</code> signals to assistive technologies that this button will open a dropdown.</li>
<li><code>aria-expanded="false"</code> tells the user that the menu is currently closed. This value will change dynamically to true when the menu is open.</li>
<li>The <code>&lt;ul&gt;</code> tag, with its <code>role="menu"</code>, ensures that the list is recognized as a menu by screen readers.</li>
<li>Each <code>&lt;li&gt;</code> element has <code>role="menuitem"</code>, letting screen readers treat these list items as menu options.</li>
<li><code>aria-hidden="true"</code> keeps the menu hidden until the user activates it, when it should be removed.</li>
</ul>
<p>These small changes mean that not only can users interact with your dropdown via keyboard, but they can also receive feedback through their screen readers about the state of the dropdown.</p>
<div><h2>FYI</h2>
<p>It's worth also <a href="https://adrianroselli.com/2017/10/dont-use-aria-menu-roles-for-site-nav.html">reading this article by Adrian Roselli</a> to make sure you have all bases covered.</p>
</div>
<h2>Modal dialogs</h2>
<p>Modals are extremely useful for displaying important information or capturing user input without navigating away from the page. However, without the correct focus management, they can be incredibly frustrating for users who rely on screen readers or keyboards.</p>
<h3>The problem</h3>
<p>A common mistake with modals is not managing focus properly. When a modal is opened, users need to be focused on interacting <strong>within</strong> it, but in many cases, focus isn’t properly trapped inside the modal. This allows users to tab through the rest of the page content, which can be disorienting. Worse, users might not even realize they’re in a modal, making it difficult to know how to close it.</p>
<h3>The fix</h3>
<p>The key to making modals more accessible is to trap focus within the modal itself. This means that when a modal is opened, keyboard navigation should cycle only through the elements inside the modal until it is closed. This prevents users from accidentally navigating to content behind the modal and ensures they stay focused on the task at hand.</p>
<p>You can appropriately manage focus trapping with JavaScript, which detects when the modal is opened and ensures focus remains inside. Additionally, adding ARIA attributes like <code>role="dialog"</code> and <code>aria-modal="true"</code> provides screen readers with the necessary context about the modal's role and behavior. Here’s a <a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/dialog_role#focus_management">step-by-step guide to do that</a>.</p>
<pre><code>&lt;div role="dialog" aria-labelledby="modal-title"&gt;
    &lt;h1 id="modal-title"&gt;Sign Up&lt;/h1&gt;
    &lt;button&gt;Close&lt;/button&gt;
    &lt;!-- Modal content --&gt;
&lt;/div&gt;
</code></pre>
<p>Let’s break it down:</p>
<ul>
<li><code>role="dialog"</code> informs assistive technologies that this is a dialog window.</li>
<li><code>aria-labelledby="modal-title"</code> helps users know what the purpose of the dialog is by associating it with the modal’s title.</li>
</ul>
<div><h2>FYI</h2>
<p>The <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog">HTML <code>&lt;dialog&gt;</code> element</a> will give you <code>role="dialog"</code> for free.</p>
</div>
<p>Additionally, <a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/dialog_role#required_javascript_features">JavaScript should be used to manage the focus</a>. When users press the <kbd>tab</kbd> key, the focus should loop through the interactive elements within the modal without leaving it. Finally, pressing the <kbd>esc</kbd> key should close the modal.</p>
<p>The basic tenants of your JavaScript for focus management should ensure that when a modal opens, the first interactive element inside it (such as a button or input field) receives focus, guiding keyboard users directly to the content. As the user navigates through the modal using the <kbd>tab</kbd> key, focus is trapped within the modal, cycling between the first and last focusable elements. If the user tries to tab out of the modal, the script should prevent this by redirecting focus to the opposite end, depending on the direction of their tabbing. When the modal is closed, you need to return focus to the button that opened it, ensuring a smooth and logical user experience.</p>
<div><h2>FYI</h2>
<p>This CodePen Demo by Adrian Roselli does a good job of demonstrating the options.</p>
<p></p><p>See the Pen <a href="https://codepen.io/piccalilli/pen/MWPKVbd">Modal Dialog</a> by Andy Bell (<a href="https://codepen.io/piccalilli/">@piccalilli</a>) on <a href="https://codepen.io">CodePen</a>.</p><p></p>
</div>
<h2>Tabbed interfaces</h2>
<p>Tabbed interfaces are a great way to organize content without overwhelming users, but only if they’re structured correctly. When tab navigation is improperly built, users relying on keyboard navigation or screen readers can find it difficult to understand which tab is selected and how to navigate between different sections.</p>
<h3>The problem</h3>
<p>Many tabbed interfaces use basic <code>&lt;div&gt;</code> or <code>&lt;span&gt;</code> tags for tab buttons, which provide no context for assistive technologies. Without ARIA roles, screen readers can’t tell users that a tab is part of a larger tab set or which content corresponds to the selected tab. Additionally, users relying on keyboards may find it impossible to navigate between tabs without arrow key functionality.</p>
<h3>The fix</h3>
<p>To fix this, use a combination of ARIA roles and properties. Specifically, <code>role="tablist"</code> defines the container for all the tabs, <code>role="tab"</code> identifies each tab, and <code>role="tabpanel”</code> specifies the content for each tab. Additionally, implement keyboard navigation so users can switch tabs using the arrow keys, making the experience seamless.</p>
<pre><code>&lt;div role="tablist"&gt;
  &lt;button role="tab" aria-selected="true" aria-controls="panel1" id="tab1"&gt;Tab 1&lt;/button&gt;
  &lt;button role="tab" aria-selected="false" aria-controls="panel2" id="tab2"&gt;Tab 2&lt;/button&gt;
&lt;/div&gt;
&lt;div id="panel1" role="tabpanel" aria-labelledby="tab1"&gt;
  &lt;p&gt;Content for Tab 1.&lt;/p&gt;
&lt;/div&gt;
&lt;div id="panel2" role="tabpanel" aria-labelledby="tab2"&gt;
  &lt;p&gt;Content for Tab 2.&lt;/p&gt;
&lt;/div&gt;
</code></pre>
<p>Let’s break it down:</p>
<ul>
<li><code>role="tablist"</code> tells screen readers that the container holds a set of tabs.</li>
<li>Each <code>role="tab"</code> button is part of the tab list and is linked to the corresponding tab content using aria-controls.</li>
<li><code>aria-selected="true"</code> denotes which tab is currently active. Only one tab should have this attribute set to true at a time.</li>
<li><code>role="tabpanel"</code> ensures that screen readers can navigate the tab content. <code>aria-labelledby</code> links the tab panel back to its associated tab for clear context.</li>
</ul>
<p>To ensure keyboard accessibility, users should be able to switch between tabs using the arrow keys, which mimics the behavior they expect from other interfaces. Additionally, focus should move to the active tab’s content when a new tab is selected.</p>
<p>For more on how to create accessible tabbed interfaces, check out <a href="https://inclusive-components.design/tabbed-interfaces/">this excellent resource from Inclusive Components</a>.</p>
<h2>Wrapping up</h2>
<p>Building accessible UI patterns doesn’t have to be complicated, but it does require attention to detail and a commitment to empathy. As you’ve seen, a few small changes — like using semantic HTML, adding ARIA roles, and managing focus correctly — can make your interfaces far more inclusive. If we want to create a web that works for everyone, we need to prioritize accessibility in every decision we make.</p>
<p>I also recommend that you read <a href="https://www.w3.org/WAI/ARIA/apg/patterns/">ARIA Authoring Practices Guide Patterns</a>, which provides a ton of examples to a whole bunch of component types beyond what we discussed and, if you're going to/need to use ARIA, how to implement them correctly.</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=a11y-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=a11y-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>Make a button element look like a link</title>
        <link>https://piccalil.li/blog/link-button/?ref=a11y-category-rss-feed</link>
        <dc:creator><![CDATA[Andy Bell]]></dc:creator>
        <pubDate>Fri, 10 Jul 2020 00:00:00 GMT</pubDate>
        <guid isPermaLink="true">https://piccalil.li/blog/link-button/?ref=a11y-category-rss-feed</guid>
        <description><![CDATA[<p>Sometimes you need interactivity within flow content, such as a paragraph of text.</p>
<p>To help with this, you can make a proper <code>&lt;button&gt;</code> element look like a link and retain all of that accessibility and semantic goodness with only a tiny bit of CSS:</p>
<pre><code>.link-button {
  display: inline;
  padding: 0;
  border: 0;
  font: inherit;
  text-decoration: underline;
  cursor: pointer;
  background: transparent;
  color: currentColor;

  -webkit-appearance: none;
}
</code></pre>
<p>In this context, it’s probably second nature to reach for an <code>&lt;a&gt;</code>, which is pretty suboptimal.</p>
<p>The benefit of using a <code>&lt;button&gt;</code> instead, is that you get all of the other interactive events for free, such as keyboard events, but also, some assistive technology allows users to loop through all of the buttons on the page.</p>
<div><h2>FYI</h2>
<p>Remember: if your user is supposed to go somewhere, use an <code>&lt;a&gt;</code> element. If something with JavaScript needs to happen, use a <code>&lt;button&gt;</code> element.</p>
</div>
<p>With all that in mind, this approach will be endlessly more helpful than using an <code>&lt;a&gt;</code> element.</p>
        
        ]]></description>
        
      </item>
    
    </channel>
  </rss>
