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

      
      <item>
        <title>Fluid typography with CSS clamp</title>
        <link>https://piccalil.li/blog/fluid-typography-with-css-clamp/?ref=intermediate-category-rss-feed</link>
        <dc:creator><![CDATA[Andy Bell]]></dc:creator>
        <pubDate>Fri, 05 Feb 2021 00:00:00 GMT</pubDate>
        <guid isPermaLink="true">https://piccalil.li/blog/fluid-typography-with-css-clamp/?ref=intermediate-category-rss-feed</guid>
        <description><![CDATA[<p>I’m a big fan of <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/clamp()"><code>clamp()</code></a>—it’s decent at doing what I like to do the most with CSS: <a href="https://piccalil.li/blog/a-minimum-viable-experience-makes-for-a-resilient-inclusive-website-or-app">let the browser do its job with some hints at how to do it</a>. It also provides <em>just the right amount</em> of control, which is <a href="https://piccalil.li/quick-tip/use-css-clamp-to-create-a-more-flexible-wrapper-utility">handy for layout elements</a>, too.</p>
<p>In this tutorial, we’re going to use clamp to generate a little fluid type system that can be configured using <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/--*">CSS Custom Properties</a>.</p>
<h2>Getting started</h2>
<p>All we need for this tutorial is a little HTML page and a CSS file. Go ahead and create the following files:</p>
<ol>
<li><code>index.html</code></li>
<li><code>global.css</code></li>
</ol>
<p>Now, inside <code>index.html</code>, add the following:</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;Fluid type demo&lt;/title&gt;
    &lt;link rel="stylesheet" href="global.css" /&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;article class="[ post ] [ flow ]"&gt;
      &lt;h1&gt;Fusce dapibus, tellus ac cursus commodo&lt;/h1&gt;
      &lt;h2&gt;Donec ullamcorper nulla non&lt;/h2&gt;
      &lt;h3&gt;Morbi leo risus, porta ac consectetur&lt;/h3&gt;
      &lt;p&gt;Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Donec sed odio dui. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Vestibulum id ligula porta felis euismod semper.&lt;/p&gt;
    &lt;/article&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>That’s it for HTML. It’s some lipsum placeholder text to demonstrate our fluid type system. Job done!</p>
<h2>Digging in to the CSS</h2>
<p>Now for the fun part. Let’s add our basic global styles first. Open up <code>global.css</code> and add the following to it:</p>
<pre><code>body {
  background: #f3f3f3;
  color: #252525;
  line-height: 1.5;
  font-family: Georgia, serif;
  padding: 2rem;
}

h1,
h2,
h3 {
  font-family: -apple-system, BlinkMacSystemFont, avenir next, avenir, helvetica neue,
    helvetica, Ubuntu, roboto, noto, segoe ui, arial, sans-serif;
  line-height: 1.1;
  font-weight: 900;
}
</code></pre>
<p>That’ll make things look a little nicer. We’re using my favourite font, Georgia, as our base and the <a href="https://systemfontstack.com/">system font stack</a> for headings. This contrast will really help you see the fluid type in action.</p>
<p>Now we can add the smart stuff—the fluid type setup. Open up <code>global.css</code> and add the following to it:</p>
<pre><code>h1,
h2,
h3,
p {
  font-size: clamp(
    var(--fluid-type-min, 1rem),
    calc(1rem + var(--fluid-type-target, 3vw)),
    var(--fluid-type-max, 1.3rem)
  );
}
</code></pre>
<p>There’s a lot going on here, so let’s break it down.</p>
<p>The <code>clamp()</code> function takes a minimum value, an ideal value and a maximum value. This allows us to create <a href="https://blog.typekit.com/2016/08/17/flexible-typography-with-css-locks/">some locks</a>.</p>
<p>To power all of this, we’re using 3 custom properties:</p>
<ol>
<li><code>--fluid-type-min</code> is the smallest we will allow our text to go</li>
<li><code>--fluid-type-target</code> is our ideal, fluid setting. We use <code>calc()</code> because if you just use a viewport unit to size your type, it can cause problems in zooming, which in turn, <a href="https://www.w3.org/WAI/WCAG21/Techniques/failures/F94.html">creates a WCAG accessibility failure</a>.</li>
<li><code>--fluid-type-max</code> is the largest we will allow our text to go</li>
</ol>
<p>For all three custom properties, we are setting the default value as the second parameter. This means that you can drop this  fluid type system into any project, and even if <em>none of those properties</em> are defined: the system will still work off those default, sensible values.</p>
<div><h2>FYI</h2>
<p>It’s really important to apply fluid type <em>responsibly</em>. Luckily for us all, <a href="https://adrianroselli.com/2019/12/responsive-type-and-zoom.html">Adrian Roselli has written about this in depth</a>. <a href="https://www.w3.org/WAI/WCAG21/Techniques/failures/F94.html">The specific criterion is here</a>.</p>
<p>It’s <em>really important</em> to test that your text gets large enough when you zoom in and small enough when you zoom out—it should be very obviously larger or smaller. Because we’re using a <code>rem</code> as part of our fluid calculation in this tutorial, we’re helping that, considerably.</p>
</div>
<h2>Implementing our system</h2>
<p>We have applied our fluid type system to the following elements: <code>h1, h2, h3, p</code>. We could—if we wanted—turn this into a <a href="https://cube.fyi/utility/">utility class</a> for maximum portability. For this tutorial, we’ll keep it simple with type selectors though.</p>
<p>We want to add some specific settings for each of these, using custom properties, or all the text will be the same size.</p>
<p>Open up <code>global.css</code> and add the following to it:</p>
<pre><code>h1 {
  --fluid-type-min: 2.5rem;
  --fluid-type-max: 5rem;
  --fluid-type-target: 5vw;

  max-width: 15ch;
}

h2 {
  --fluid-type-min: 1.8rem;
  --fluid-type-max: 3rem;
}

h3 {
  --fluid-type-min: 1.5rem;
  --fluid-type-max: 2.5rem;
}

h2,
h3 {
  max-width: 30ch;
}

p {
  max-width: 60ch;
}
</code></pre>
<p>For the <code>&lt;h1&gt;</code>, we increase the <code>--fluid-type-target</code> to a larger, <code>5vw</code>. By increasing the viewport unit, we speed up the rate of growth, which will help to maintain its extra large size. To reduce the rate of growth and have less difference between your minimum and maximum sizes: <em>reduce</em> the size of <code>--fluid-type-target</code>.</p>
<p>For all of the other elements, the default growth rate is fine, so all we’re doing is setting sensible minimum and maximum sizes, using standard <code>rem</code> units.</p>
<div><h2>FYI</h2>
<p>We’re limiting the width of all of the elements, using a <code>ch</code> unit to improve readability. You can <a href="https://piccalil.li/quick-tip/line-length">read more about that here</a>.</p>
</div>
<p>We’re done! You can <a href="https://codepen.io/piccalilli/project/full/48cfe24e498546cefdcae45bddecff46">see a live demo of what we have built, here</a>. You can also <a href="https://assets.codepen.io/174183/fluid-type-final.zip">download the completed source files, here</a>.</p>
<h2>Wrapping up</h2>
<p>This is a very simple, bare-bones system and will comfortably support a lot of usecases. For more advanced, complex designs, I would recommend using something like <a href="https://typetura.com/">Typetura</a> which gives very fine control or <a href="https://utopia.fyi/">Utopia</a>, which is a level up from this approach that we’ve learned today.</p>
<p>For full disclosure, I’m not a <em>huge</em> fan of fluid type, personally. I’ve had such a mixed, checkered history with it over the years, and personally, I prefer to create a size scale, implemented via media queries. That’s what this site does, at the time of writing.</p>
<p>Fluid type is in demand, too, so what you’ve learned today will undoubtedly be useful at some point—if nothing else, to give you a useful context of how <code>clamp()</code> works.</p>
<p>Until next time, take it easy 👋</p>
<hr />
<p>Big thanks to <a href="https://piccalil.li//ericwbailey.website">Eric Bailey</a> for casting his expert eye on this for me.</p>
        
        ]]></description>
        
      </item>
    
      <item>
        <title>Build a responsive media browser with CSS</title>
        <link>https://piccalil.li/blog/build-a-responsive-media-browser-with-css/?ref=intermediate-category-rss-feed</link>
        <dc:creator><![CDATA[Andy Bell]]></dc:creator>
        <pubDate>Thu, 01 Oct 2020 00:00:00 GMT</pubDate>
        <guid isPermaLink="true">https://piccalil.li/blog/build-a-responsive-media-browser-with-css/?ref=intermediate-category-rss-feed</guid>
        <description><![CDATA[<p>Something that has been a bit of a brain teaser historically, is aspect ratio—specifically, maintaining it—especially over the last 10 or so years, since responsive design has been the industry standard way of building websites.</p>
<p>A context that has always been particularly problematic for this layout brain-teaser is video—especially a third-party embedded video, such as YouTube.</p>
<p>In this tutorial we are going to solve all of these problems using the magic of Flexbox. What we will end up with at the end is a water-tight layout that can be adapted for many use-cases where aspect ratio needs to be maintained, while other elements put that at risk.</p>
<p><a href="https://css-media-browser.demo.piccalil.li/videos/video-1/">See a completed demo</a></p>
<h2>Getting started</h2>
<p>Before we start, let’s get you up and running with some <a href="https://github.com/piccalil-li/css-media-browser-tutorial/archive/starter.zip">starter files</a>. I’ve set us up a project that uses <a href="https://piccalil.li//11ty.dev">Eleventy</a> to generate a nice collection of pages that all run off the same template. This will make the media browser fully interactive, using good ol’ HTML and URLs.</p>
<p>To get started:</p>
<ol>
<li><a href="https://github.com/piccalil-li/css-media-browser-tutorial/archive/starter.zip">Download these starter files</a></li>
<li>Extract the contents</li>
<li>Open your terminal <strong>inside the folder</strong> and run <code>npm install</code></li>
<li>Serve up and watch for file changes with <code>npm start</code></li>
</ol>
<div><h2>FYI</h2>
<p>You can skip this process and build a more static version of the site using a CodePen template if you’d prefer.</p>
<p>All you need to do is <a href="http://codepen.io/pen?template=NWNZRaY">click here</a> and a brand new pen will start up with all the stuff you need, ready to go.</p>
</div>
<p>When you run <code>npm start</code> and open your browser at <code>&lt;http://localhost:8080&gt;</code>, you should see something that looks a bit like this:</p>
<p><img src="https://piccalil.b-cdn.net/images/tutorials/media-browser-1.jpg" alt="A title, video and collection of links in un-styled HTML" /></p>
<h2>A quick look at the markup</h2>
<p>All the HTML for this tutorial is already in place to keep things focused on CSS, but let’s look at the main <code>.media-browser</code> element’s markup.</p>
<p><strong>You don’t need to copy this code</strong></p>
<pre><code>&lt;div class="media-browser"&gt;
  &lt;a class="skip-link" href="#video-nav"&gt;Skip to the video navigation&lt;/a&gt;
  &lt;div class="media-browser__video"&gt;
    &lt;div class="video-player"&gt;
      &lt;iframe
        src="https://iframe.mediadelivery.net/embed/468647/32628951-a545-42b9-a761-8e373ee45331?autoplay=true&amp;loop=false&amp;muted=false&amp;preload=true&amp;responsive=true"
        width="640"
        height="360"
        frameborder="0"
        allow="autoplay; fullscreen"
        allowfullscreen
      &gt;&lt;/iframe&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;nav class="media-browser__nav" aria-label="Videos" id="video-nav" tabindex="-1"&gt;
    &lt;ol class="media-browser__links"&gt;
      &lt;li&gt;&lt;a href="#"&gt;Albatross Soup&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href="#"&gt;All Cats Are Gray in the Dark&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href="#" aria-current="page"&gt;Thrasher - Nick Mullins Sponsor Me&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href="#"&gt;Thrasher Bust or Bail 2008&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href="#"&gt;‘Aliens’ (2020)&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href="#"&gt;A Normal Day of Sassi (쎄씨의 하루)&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href="#"&gt;Battle Patrol 2084&lt;/a&gt;&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/nav&gt;
&lt;/div&gt;
</code></pre>
<p>What we have here is an element that features a Vimeo embed and a collection of links which link to other pages, which contain the same media browser, with a different video loaded. At the start of the <code>.media-browser</code> element, we have a skip link that allows the user to skip straight to the <code>&lt;nav&gt;</code> element to move to other videos.</p>
<div><h2>FYI</h2>
<p>You might be thinking, “but in the completed demo, the links are at the start, on the left”, which is true, but because we will be <em>visually changing the source order</em>, we need to make sure we are not creating a problem for keyboard users.</p>
</div>
<p>We make the <code>&lt;nav&gt;</code> element <em>programatically focusable</em> by setting <code>tabindex="-1"</code>, which helps to ensure that when a user activates the skip link, the next focusable element will be the <strong>first link to another video</strong>.</p>
<p>Another useful addition to this <code>&lt;nav&gt;</code> is that we have an <code>aria-label</code>. <a href="https://www.scottohara.me/blog/2018/03/03/landmarks.html">This is actually best practice</a> if you have more than one <code>&lt;nav&gt;</code> landmark so assistive technology, such as a screen reader, will more helpfully announce them.</p>
<div><h2>FYI</h2>
<p>When you add an <code>aria-label</code> to a <code>&lt;nav&gt;</code> element, you <strong>don’t need to add the word, “nav” or ”navigation”</strong> because the screen reader already knows this and will already announce it.</p>
<p>It’s the same type of rule as not adding the word “image” to an <code>&lt;img&gt;</code> <code>alt</code> attribute, because screen readers will announce them as images.</p>
</div>
<p>That’ll do for HTML programming for now. We’ll cover stuff in more detail as we style it.</p>
<h2>Global styles</h2>
<p>The CSS in this tutorial uses some of the principles from <a href="https://piccalil.li/cube-css/principles/">CUBE CSS</a>.</p>
<p>With that in mind, the first thing we will do is add some global CSS. In your starter files, open <code>src/css/global.css</code> and add the following to it:</p>
<pre><code>body {
  padding: 2.5rem 1.5rem;
  font-family: 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  line-height: 1.5;
  background: #ffffff;
  color: #333333;
}

main {
  max-width: 50rem;
  margin: 0 auto;
}

main &gt; * + * {
  margin-top: 2em;
}

h1 {
  font-weight: 500;
}

p {
  max-width: 60ch;
}

a {
  color: currentColor;
}

:focus {
  outline: 1px dashed;
  outline-offset: 0.25rem;
}
</code></pre>
<p>This is all fairly straightforward, global styling. We’re setting some sensible defaults, such as a system font stack, link styles and importantly, global focus styles.</p>
<p>We’re also adding some flow and rhythm by telling all direct siblings of the <code>&lt;main&gt;</code> element to add <code>2em</code> of top margin. We do this using a <a href="https://alistapart.com/article/axiomatic-css-and-lobotomized-owls/">lobotomised owl selector</a>.</p>
<div><h2>FYI</h2>
<p>This approach of managing stacking with flow and rhythm is something I’ve written extensively about for a while, but check out <a href="https://24ways.org/2018/managing-flow-and-rhythm-with-css-custom-properties/">this</a> and most importantly, <a href="https://every-layout.dev/layouts/stack/">this</a> for a high-level overview.</p>
</div>
<p>Your version of the project should look a bit like this now:</p>
<p><img src="https://piccalil.b-cdn.net/images/tutorials/media-browser-2.jpg" alt="The same view as before, but looking much smarter with sans serif fonts and limited widths" /></p>
<h2>Responsive video player</h2>
<p>Now we have our global styles, let’s focus on some specific elements. Before we tackle the <code>.media-browser</code>, let’s build out the <code>.video-player</code>. Still inside <code>global.css</code>, add the following:</p>
<pre><code>/* Video player */
.video-player {
  position: relative;
  padding-bottom: 56.25%;
}

.video-player &gt; * {
  width: 100%;
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;
}
</code></pre>
<p>What we do here is create a container with a <code>16:9</code> aspect ratio. The <code>56.25%</code> might look like a magic number, but it’s actually a result of the following calculation:</p>
<ol>
<li>Divide 9 by 16: <code>0.5625</code></li>
<li>Multiply that by 100 (or move decimal 2 places): <code>56.25</code></li>
</ol>
<p>This works with any aspect ratio!</p>
<p>We add padding to the bottom of the <code>.video-player</code> because <strong>vertical padding percentages work from the element’s width</strong>. Let’s say that our video player happens to find itself in a 1000px wide container: it would have a bottom padding value of <code>562.5px</code>.</p>
<p>We then target the direct child of the video player and make it <strong>absolutely positioned</strong>. Because we pull the video itself out of the document flow, it fits all sides by setting width and height, then sticking it in the top left with positioning. This means that the video element will always maintain the same proportions as its responsive parent, thanks to its padding. Handy, right?</p>
<h2>Styling the skip link</h2>
<p>Our skip link needs to be visually hidden unless it is focused. We also want to make that absolutely positioned, so it doesn’t affect our <code>.media-browser</code> layout.</p>
<p>While you’re still in <code>global.css</code>, add the following:</p>
<pre><code>.skip-link {
  display: inline-block;
  padding: 0.5rem 1.5rem 0.6rem 1.5rem;
  background: #efd6da;
  border: 1px solid #cccccc;
  border-radius: 0.25rem;
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  z-index: 99;
  text-decoration: none;
}

.skip-link:not(:focus) {
  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 skip link is styled to look like a button and uses pretty straightforward styles to do that. The real interesting stuff is what follows after that.</p>
<p>Firstly, we’re using the <code>:not</code> pseudo-selector to add styles to the button <strong>when it doesn’t have focus</strong>. Inside that rule, we are using the same CSS as this <a href="https://piccalil.li/quick-tip/visually-hide-an-element-with-css/">visually hidden quick tip</a>. This visually hides the element, but allows assistive technology, such as screen readers, to still access it.</p>
<p>When you refresh and hit your <kbd>tab</kbd> key, it should look like this:</p>
<p><img src="https://piccalil.b-cdn.net/images/tutorials/media-browser-3.jpg" alt="A skip link visible in the top right of the main view" /></p>
<h2>The main course: styling the media browser</h2>
<p>We’ve danced around the edges for long enough now: let’s get into the main course and get the media browser layout styled up.</p>
<p>While you’re still in <code>global.css</code>, add the following to it:</p>
<pre><code>.media-browser {
  display: flex;
  flex-direction: row-reverse;
  flex-wrap: wrap;
  position: relative;
  border-radius: 0.5rem;
  overflow: hidden;
  border: 1px solid #cccccc;
  box-shadow: 0 0 2.5rem 1rem rgba(0, 0, 0, 0.1);
}
</code></pre>
<p>This is the main layout that sits the elements side-by-side. Like we covered in the skip link section, we have flipped the video and the nav <strong>visually</strong> by using <code>flex-direction: row-reverse</code>.</p>
<div><h2>FYI</h2>
<p>Be very careful changing the source order of content—especially when there are focusable elements at play (<code>&lt;a&gt;</code>, <code>&lt;button&gt;</code>). Changing the visual order of elements with CSS doesn’t change their source order, so <strong>tabbing can be very unpredictable for keyboard users</strong></p>
<p>I’d say we are right on the edge of being a nuisance for keyboard users with this example and the skip link mitigates some of the problems that we have introduced.</p>
</div>
<p>Right now, this layout isn’t very helpful, so let’s fine-tune it. Add the following to your <code>global.css</code> file:</p>
<pre><code>.media-browser__nav {
  display: flex;
  flex-direction: column;
  flex-basis: 16rem;
  flex-grow: 1;
  position: relative;
  z-index: 1;
}

.media-browser__video {
  flex-basis: 0;
  flex-grow: 999;
  min-width: 60%;
  background: black;
}
</code></pre>
<p>This here is how you get a responsive media player without using a single media query. We’re using the same principles that we covered in the <a href="https://every-layout.dev/layouts/sidebar/">Sidebar in Every Layout</a>.</p>
<p>By setting the nav to have a <code>flex-basis</code> of <code>16rem</code> and the video to have a <code>flex-basis</code> of <code>0</code>, we are assisting the browser’s flexbox calculation of available space. Then, because the video has a <code>flex-grow</code> value of <code>999</code>, we’re <em>hinting</em> to the browser that it should let the video fill all available space as a top priority. We want our sidebar to grow too, so by adding <code>flex-grow: 1</code> to that, we send another clear message where the spacing-filling priority lies.</p>
<p>We further assist this layout by adding a min-width of <code>60%</code> to the video element. This is to make sure we don’t end up with a situation where the nav and video are both equal widths, which in this context, would be pretty rubbish.</p>
<p>Our main <code>.media-browser</code> element has <code>flex-wrap: wrap</code> set, so when flexbox can’t fit these two elements—using basis rules we set—in line with each other, they will stack on top of each other. Because they both have <code>flex-grow</code>, they will be 100% wide.</p>
<p>Lastly, even though we reversed the elements visually: this reversal does not apply when they are stacked, which means that on small viewports, the video is at the top and the nav is at the bottom.</p>
<p>Now <em>that</em> is some proper CSS layout!</p>
<h2>Making the nav shrinkable</h2>
<p>If you look at your project now, it should look like this:</p>
<p><img src="https://piccalil.b-cdn.net/images/tutorials/media-browser-4.jpg" alt="The main layout of the responsive media browser is now complete, just with mostly un-styled links" /></p>
<p>That nav is at risk of pushing the aspect ratio of the video out if it gets longer than the height of it. To mitigate this in the old days, we’d get our friend absolute positioning out for this sort of problem, but today, we have modern CSS to help us, so we don’t need to bother with archaic hacks.</p>
<p>In your <code>global.css</code>, add the following:</p>
<pre><code>.media-browser__nav &gt; * {
  flex-basis: 0;
  flex-grow: 1;
  overflow-y: auto;
  position: relative;
  min-height: 10rem;
}
</code></pre>
<p>This selector targets the <code>&lt;nav&gt;</code>’s direct children, which in this case, is an <code>&lt;ol&gt;</code>. If I just quickly refer back to the <code>.media-browser__nav</code> further up, we already added flex rules like so:</p>
<pre><code>.media-browser__nav {
  display: flex;
  flex-direction: column;
}
</code></pre>
<p>Because we have that CSS set on the nav, the flex rules on our <code>&lt;ol&gt;</code> apply. On that <code>&lt;ol&gt;</code>, we have <code>flex-basis</code> set to <code>0</code>, which means effectively, we’re telling the browser that we don’t expect it to be anything but <code>0</code> high by default. What we do though, is set <code>flex-grow</code> to be <code>1</code>, which means it should take the available space that the nav leaves for it.</p>
<p>Also on the <code>&lt;ol&gt;</code>, we set the vertical overflow to be <code>auto</code> which then allows the nav items to scroll, which in turn, gives us the desired maintained aspect ratio. We add a min height of <code>10rem</code> to make sure that when the <code>.media-browser</code> stacks at smaller viewports, the <code>flex-basis: 0</code> doesn’t make the <code>&lt;ol&gt;</code> vanish.</p>
<div><h2>FYI</h2>
<p>Because the <code>.media-browser__nav</code> is a child of <code>.media-browser</code>, which has <code>display: flex</code> set, it takes advantage of flex’s default alignment of <code>stretch</code>, which does what it says on the tin: makes its children stretch, vertically.</p>
<p>This means that the space for the <code>&lt;ol&gt;</code> is automatically made available, as long as the media browser isn’t stacked.</p>
</div>
<h2>Adding some style</h2>
<p>The layout is done and working great. You’ve got a handy layout primitive to work with now! Let’s add a bit of style though.</p>
<p>In your <code>global.css</code>, add the following:</p>
<pre><code>.media-browser__links &gt; * + * {
  border-top: 1px solid #cccccc;
}

.media-browser__links a {
  display: block;
  padding: 1rem;
  text-decoration: none;
  line-height: 1.2;
  background: #f3f3f3;
}

.media-browser__links :hover {
  background: #e5bec4;
}

.media-browser__links :focus {
  outline-offset: -0.25rem;
}
</code></pre>
<p>That’ll turn the links into nice, tap-friendly blocks. Let’s give the active link—which because we’re using links, allows us to treat it as an active page—a bit of treatment. Because it’s an active page, we can use an <a href="https://tink.uk/using-the-aria-current-attribute/"><code>aria-current="page"</code> attribute</a>, which tells assistive technology like screen readers that the <code>&lt;a&gt;</code>’s <code>href</code> attribute is in fact, the page we are currently on.</p>
<p>This also gives us a handy style hook, using the <a href="https://piccalil.li/cube-css/exception/">Exception Principle from CUBE CSS</a>.</p>
<p>Add the following to <code>global.css</code>:</p>
<pre><code>.media-browser__links a[aria-current='page'] {
  border-left: 0.4rem solid #444444;
}
</code></pre>
<h2>Adding a scrollbar</h2>
<p>Because CSS is a hell of a powerful programming language: we don’t need to mess around with JavaScript to get a nice, custom scrollbar for those auto-scrolling nav links.</p>
<p>Add the following to your <code>global.css</code>:</p>
<pre><code>::-webkit-scrollbar {
  height: 0.5rem;
  width: 0.5rem;
}

::-webkit-scrollbar-track {
  background: #f8eef0;
}

::-webkit-scrollbar-thumb {
  background: #444444;
}

/* Color order: thumb, then track */
* {
  scrollbar-color: #444444 #f8eef0;
}
</code></pre>
<h2>Wrapping up</h2>
<p>There we have it, a flexible, responsive and progressive media browser that is achieved with not much CSS at all.</p>
<p>I hope this has shown you how powerful modern CSS layout is—especially when you approach it in a flexible manner, rather than pixel-pushing and forcing the layout to work.</p>
<p>You can <a href="https://github.com/piccalil-li/css-media-browser-tutorial/archive/master.zip">download a zip of the final version of this tutorial</a> or go ahead and <a href="https://github.com/piccalil-li/css-media-browser-tutorial">check out a git repository of it</a>.</p>
<p>Until next time, take it easy 👋</p>
        
        ]]></description>
        
      </item>
    
      <item>
        <title>Build a dashboard with CUBE CSS</title>
        <link>https://piccalil.li/blog/build-a-dashboard-with-cube-css/?ref=intermediate-category-rss-feed</link>
        <dc:creator><![CDATA[Andy Bell]]></dc:creator>
        <pubDate>Wed, 29 Jul 2020 00:00:00 GMT</pubDate>
        <guid isPermaLink="true">https://piccalil.li/blog/build-a-dashboard-with-cube-css/?ref=intermediate-category-rss-feed</guid>
        <description><![CDATA[<p>CUBE CSS allows us to build seemingly complex front-ends with relative ease and to demonstrate that, in this tutorial, we’re going to build a banking dashboard which covers <strong>the whole methodology</strong>.</p>
<p><a href="https://dash.example.cube.fyi/">See what you are building</a></p>
<p>Before we start, it’s recommended that you <a href="https://piccalil.li/cube-css/">check out the CUBE CSS docs</a> or read the <a href="https://piccalil.li/blog/cube-css/">introduction post</a> to get a feel for what we are working.</p>
<h2>Starter files</h2>
<p>To focus this tutorial on the CSS only, we’re going to use a HTML boilerplate that I built earlier, with all of the necessary folders and assets, so the first thing you need to do is download those and set them up wherever you need to.</p>
<p><a href="https://github.com/piccalil-li/cube-css-dashboard/archive/d15c5e3ed92a0508e2cc9026b7b3c50d3c7334b7.zip">Download starter files</a></p>
<p>Now they are downloaded, your folder structure should look a bit like this:</p>
<pre><code>index.html
fonts
Images
scss
├── blocks
└── utilities
</code></pre>
<h2>Setting up Sass</h2>
<p>We’re going to use Sass for this tutorial along with a project I maintain called <a href="https://github.com/hankchizljaw/gorko">Gorko</a>, which allows us to generate utility classes.</p>
<p>The first thing we’ll do is install Sass, so in your terminal, run the following:</p>
<pre><code>npm install sass
</code></pre>
<p>This installs <a href="https://sass-lang.com/">canonical Sass</a>, which is the version of Sass you should be using, if possible, because you get all of the latest features quickest. It’s also currently the only way to use the most modern CSS features within Sass.</p>
<p>You should have a <code>package.json</code> file in your starter files, so open that up and <strong>replace the <code>"scripts"</code> section with the following</strong>:</p>
<pre><code>"scripts": {
  "start": "npx sass scss/global.scss css/global.css --watch",
  "build": "npx sass scss/global.scss css/global.css --style=compressed"
},
</code></pre>
<p>You should now be able to run <code>npm start</code> which will run Sass and then watch for any file changes.</p>
<div><h2>FYI</h2>
<p>You can also run this project locally by opening another terminal window/tab, moving into this project folder, then running <code>npx serve</code>. You can <a href="https://piccalil.li/quick-tip/quick-and-easy-local-server/">read more about that, here</a>.</p>
</div>
<p>Now that we have Sass installed, let’s install <a href="https://github.com/hankchizljaw/gorko">Gorko</a>. In your terminal, run the following:</p>
<pre><code>npm install gorko
</code></pre>
<p>Let’s now add some config for Gorko. In your working folder, in the <code>scss</code> folder, create a new file called <code>_config.scss</code> and add the following to it:</p>
<pre><code>/**
 * BASE SIZE
 * All calculations are based on this. It’s recommended that
 * you keep it at 1rem because that is the root font size. You
 * can set it to whatever you like and whatever unit you like.
 */
$gorko-base-size: 1rem;

/**
 * SIZE SCALE
 * This is a Major Third scale that powers all the utilities that
 * it is relevant for (font-size, margin, padding). All items are
 * calcuated off the base size, so change that and cascade across
 * your whole project.
 */
$gorko-size-scale: (
  '300': $gorko-base-size * 0.8,
  '400': $gorko-base-size,
  '500': $gorko-base-size * 1.33,
  '600': $gorko-base-size * 1.77,
  '700': $gorko-base-size * 2.4
);

/**
 * COLORS
 * Colors are shared between backgrounds and text by default. 
 * You can also use them to power borders, fills or shadows, for example.
 */
$gorko-colors: (
  'primary': #231651,
  'secondary': #ff8484,
  'secondary-shade': #ff5151,
  'tertiary': #2c988c,
  'tertiary-glare': #d6fff6,
  'quaternary': #2374ab,
  'light': #fafafa,
  'light-shade': #eeeeee,
  'grey': #c4c4c4
);

/**
 * CORE CONFIG
 * This powers everything from utility class generation to breakpoints
 * to enabling/disabling pre-built components/utilities.
 */
$gorko-config: (
  'bg': (
    'items': $gorko-colors,
    'output': 'standard',
    'property': 'background'
  ),
  'color': (
    'items': $gorko-colors,
    'output': 'standard',
    'property': 'color'
  ),
  'font': (
    'items': (
      'base': '"IBM Plex Sans", Helvetica, Arial, sans-serif',
      'mono': '"IBM Plex Mono", Courier New, Courier, monospace'
    ),
    'output': 'standard',
    'property': 'font-family'
  ),
  'gap-top': (
    'items': $gorko-size-scale,
    'output': 'standard',
    'property': 'margin-top'
  ),
  'pad-top': (
    'items': $gorko-size-scale,
    'output': 'standard',
    'property': 'padding-top'
  ),
  'text': (
    'items': $gorko-size-scale,
    'output': 'responsive',
    'property': 'font-size'
  ),
  'weight': (
    'items': (
      'medium': '500',
      'bold': '700'
    ),
    'output': 'standard',
    'property': 'font-weight'
  ),
  'breakpoints': (
    'md': '(min-width: 48em)'
  )
);
</code></pre>
<p>I won’t go into too much detail with this because <a href="https://github.com/hankchizljaw/gorko#configuration">the Gorko documentation does that already</a>, but essentially, what we have here is some colour, fonts and a size scale (perfect fourth) which then informs various low-level utilities. This is the <strong>U</strong> in <strong>CUBE</strong> that we are working with.</p>
<p>Now that we have the config, let’s add a reset. We’re going to use <a href="https://github.com/hankchizljaw/modern-css-reset">this modern reset here</a>. In your working folder, in the <code>scss</code> folder, create a new file called <code>_reset.scss</code> and add the following to it:</p>
<pre><code>// Modern CSS reset: https://github.com/hankchizljaw/modern-css-reset

/* Box sizing rules */
*,
*::before,
*::after {
  box-sizing: border-box;
}

/* Remove default padding */
ul[class],
ol[class] {
  padding: 0;
}

/* Remove default margin */
body,
h1,
h2,
h3,
h4,
p,
ul[class],
ol[class],
figure,
blockquote,
dl,
dd {
  margin: 0;
}

/* Set core root defaults */
html {
  scroll-behavior: smooth;
}

/* Set core body defaults */
body {
  min-height: 100vh;
  text-rendering: optimizeSpeed;
  line-height: 1.5;
}

/* Remove list styles on ul, ol elements with a class attribute */
ul[class],
ol[class] {
  list-style: none;
}

/* A elements that don't have a class get default styles */
a:not([class]) {
  text-decoration-skip-ink: auto;
}

/* Make images easier to work with */
img,
picture {
  max-width: 100%;
  display: block;
}

/* Natural flow and rhythm in articles by default */
article &gt; * + * {
  margin-top: 1em;
}

/* Inherit fonts for inputs and buttons */
input,
button,
textarea,
select {
  font: inherit;
}

/* Blur images when they have no alt attribute */
img:not([alt]) {
  filter: blur(10px);
}

/* Remove all animations and transitions for people that prefer not to see them */
@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
</code></pre>
<p>We’ve got our base setup now, so let’s start building out our global styles. In your working folder, in the <code>scss</code> folder, create a new file called <code>global.scss</code> and add the following to it:</p>
<pre><code>// First up: config
@import 'config';

// Next: pull in gorko for design tokens
@import '../node_modules/gorko/gorko.scss';

// Pull in modern reset
@import 'reset';
</code></pre>
<p>We’re pulling in our config, which informs Gorko. Then, we pull in our reset. In your output (<code>css/global.css</code>), you should now see some reset styles and utility classes.</p>
<h2>Global styles</h2>
<p>The base of CUBE CSS is global styles, so let’s add some of those now. Open up <code>scss/global.scss</code> and add the following to it:</p>
<pre><code>// Global CSS starts
body {
  line-height: 1.5;
  overflow-x: hidden;
  padding-bottom: get-size('600');

  @include apply-utility('weight', 'medium');
}

h1,
h2,
h3 {
  line-height: 1.2;
}

h1 {
  font-size: get-size('700');
}

h2,
h3 {
  font-size: get-size('600');
}

a {
  color: currentColor;
}

table {
  border-collapse: collapse;
}

th {
  text-align: left;
}

:focus {
  outline: 2px dotted;
  outline-offset: 0.25rem;
}
</code></pre>
<p>The design of this UI is pretty simple, so there’s not much to do here, but as you can see, our focus is element selectors, where key elements, such as headings, get some rules set. We also cover global focus styles to make sure each focusable element gets a consistent treatment.</p>
<h2>Utilities and tokens</h2>
<p>With the globals in place, let’s start adding some core utilities and <a href="https://css-tricks.com/what-are-design-tokens">design tokens</a> to our HTML. Most of the tokens are already implemented in the HTML because this would be a hell of a boring tutorial if we added them all manually, but to give you a feel for how things work, we’ll add some key ones now.</p>
<p>First of all, run your site locally and take a look. It should look a bit like this:</p>
<p><img src="https://piccalil.b-cdn.net/images/tutorials/cube-dash-1.jpg" alt="The site with mostly default styles, but typography looks neater" /></p>
<p>As you can see, with global styles and most of the tokens implemented, it doesn’t look half-bad <em>at all</em>. This is the magic of CUBE CSS: we can do a lot with very little.</p>
<p>Let’s add some more tokens. Open up <code>index.html</code> and on <strong>line 11</strong> there should be the opening <code>&lt;body&gt;</code> tag. Replace it with the following:</p>
<pre><code>&lt;body class="bg-light color-primary font-base"&gt;&lt;/body&gt;
</code></pre>
<p>That’s globally setting some colour tokens and the base font as the main font. Let’s continue this process by moving to <strong>line 12</strong> with the <code>&lt;header role="banner"&gt;</code> element. Replace it with the following:</p>
<pre><code>&lt;header role="banner" class="[ site-head ] [ bg-tertiary-glare ]"&gt;&lt;/header&gt;
</code></pre>
<p>We’ve added a <code>site-head</code> block, which we will come back to soon, but the main thing here is we’ve set its background colour with a utility.</p>
<div><h2>FYI</h2>
<p>You might be thinking “what the heck are those square brackets for?”. If this is you, go ahead and <a href="https://piccalil.li/cube-css/grouping/">read up on them in the CUBE CSS documentation</a>.</p>
</div>
<p>Lastly, skip down to <strong>line 116</strong>. You should see the following: <code>&lt;section class="[ summary ] [ flow radius ]"&gt;</code>. Replace that with the following:</p>
<pre><code>&lt;section class="[ summary ] [ flow radius ] [ bg-primary color-light ]"&gt;&lt;/section&gt;
</code></pre>
<p>Now, when you reload your local version of this page, it should look like this:</p>
<p><img src="https://piccalil.b-cdn.net/images/tutorials/cube-dash-2.jpg" alt="The site now looks much better with design tokens implemented that affect colour and type" /></p>
<p>Pretty cool, right? <strong>So much of the design implementation is already done</strong>. Now we’ve got to do the other parts of CUBE: composition, blocks and exceptions.</p>
<h2>Composition styles</h2>
<p>Right, we’ve done quite a lot already. Now it’s time to <strong>look at the bigger picture with composition styles</strong>. We’ll start with some high-level layouts.</p>
<h3>Wrapper</h3>
<p>The wrapper gives us a consistent max-width container with a bit of gutter. This is destined to be a utility because remember: <strong>a utility does one job and does it well</strong>.</p>
<p>Create a new file called <code>scss/utilities/_wrapper.scss</code> and add the following to it:</p>
<pre><code>.wrapper {
  max-width: 75rem;
  margin-left: auto;
  margin-right: auto;
  padding: 0 get-size('500');
}
</code></pre>
<p><a href="https://github.com/hankchizljaw/gorko#sass-functions">The <code>get-size</code> function comes from Gorko</a> and allows us to grab a size ratio value based on the key.</p>
<h3>Flow</h3>
<p>Next up is flow. This is a <em>super tiny</em> utility that adds margin to child sibling elements, using a lobotomised owl selector. This means that we get <em>tonnes</em> of flexibility, because <strong>any element</strong> will be affected (unless they are <code>display: inline</code>) and we don’t have to add daft CSS that targets last children to remove spacing.</p>
<p>Create a new file called <code>scss/utilities/_flow.scss</code> and add the following to it:</p>
<pre><code>.flow &gt; * + * {
  margin-top: var(--flow-space, 1rem);
}
</code></pre>
<p>We first look for a <code>--flow-space</code> <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/--*">Custom Property</a> and if that’s defined, it gets used. By default, using a fallback, we tell each child element to space itself <code>1rem</code> from its sibling. The great thing about using a Custom Property is that we can control spacing <strong>in context</strong> without touching this utility again. Those Custom Properties are affected by the cascade too.</p>
<p>We use <a href="https://every-layout.dev/layouts/stack/">a more comprehensive version of this utility in Every Layout</a>, where it’s called The Stack.</p>
<h3>Splitter</h3>
<p>This utility gives us our main responsive layout, which is two elements pushed away from each other at larger viewports.</p>
<p>Create a new file called <code>scss/utilities/_splitter.scss</code> and add the following to it:</p>
<pre><code>.splitter {
  &gt; :last-child {
    margin-top: get-size('500');
  }

  @include media-query('md') {
    display: flex;

    &gt; * {
      flex-grow: 1;
    }

    &gt; :last-child {
      margin-top: 0;
      margin-left: get-size('500');
      min-width: 22rem;
    }
  }
}
</code></pre>
<p>Being called <code>splitter</code>, this utility assumes it is dealing with <strong>two elements</strong>, so it works that way. This is shown right at the start of the utility when the last child has margin added to it.</p>
<div><h2>FYI</h2>
<p>“Why not use flow?!” I hear you scream. This is good thinking, but what we try to do with CUBE CSS is make things predictable and especially in the case of utilities: <strong>single purpose</strong>.</p>
<p>If we introduced another utility into this one (splitter), it would immediately become more complex, so it makes more sense to be specific, predictable and most importantly, <strong>pragmatic</strong>.</p>
</div>
<p>The rest of this utility adds flex at larger viewports, then flips the spacing.</p>
<h3>Visually hidden</h3>
<p>Create a new file called <code>scss/utilities/_visually-hidden.scss</code> and add the following to it:</p>
<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>This utility does what it says on the tin: it visually hides stuff. Using this utility instead of using <code>display: none</code> means that screen reader users can still access the content. In our project, this mainly affects the table headers.</p>
<h3>Radius</h3>
<p>Last up in these utilities is radius. This one isn’t technically a compositional rule, but while we’re in here, we might as well add it.</p>
<p>Create a new file called <code>scss/utilities/_radius.scss</code> and add the following to it:</p>
<pre><code>.radius {
  border-radius: 0.5rem;
}
</code></pre>
<p>If there was ever a single-purpose utility, it would be this one!</p>
<h2>Wiring up the utilities and composition styles</h2>
<p>Open up <code>scss/global.scss</code> and add the following:</p>
<pre><code>// Utilities
@import 'utilities/flow';
@import 'utilities/radius';
@import 'utilities/splitter';
@import 'utilities/wrapper';
@import 'utilities/visually-hidden';
</code></pre>
<p>Here, we are importing all of the CSS we have just written. If you reload your local version now, it should look like this:</p>
<p><img src="https://piccalil.b-cdn.net/images/tutorials/cube-dash-3.jpg" alt="The site now looks much better with design tokens implemented that affect colour and type" /></p>
<h2>Adding our blocks</h2>
<p>Now we’ve handled global styles, design tokens, single purpose utilities <em>and</em> composition styles, we naturally move on to blocks. We’ll also get into exceptions at this point too.</p>
<h3>Site header</h3>
<p>This is the main header of the site, but it’s a super tiny block. Create a new file called <code>scss/blocks/_site-head.scss</code> and add the following to it:</p>
<pre><code>.site-head {
  padding: 0.8rem 0;

  &amp;__inner {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
  }

  // Right margin prevents any collisions with the title
  h1 {
    margin: 0.5rem 1rem 0.5rem 0;
  }
}
</code></pre>
<p>The main theme of this block is to create an auto-wrapping flex layout. We’ve only got two elements: a heading and a <code>user</code> block—which we’ll build next—that should never collide with each other. We guarantee this by building a force-field around our <code>&lt;h1&gt;</code> with margin.</p>
<div><h2>FYI</h2>
<p>Remember: inside of blocks is <strong>open season</strong>, because in CUBE CSS, blocks are more like namespaces. It’s very safe therefore to <strong>target HTML elements directly</strong>.</p>
<p>You can <a href="https://piccalil.li/cube-css/block/#heading-no-formal-element-syntax">read more about this in the documentation</a>.</p>
</div>
<h3>User</h3>
<p>While we’re in the site head, let’s build the user block. Create a new file called <code>scss/blocks/_user.scss</code> and add the following to it:</p>
<pre><code>.user {
  display: inline-grid;
  align-items: center;
  grid-template-columns: max-content 50px;
  grid-gap: get-size('300');

  img {
    border-radius: 100%;
  }
}
</code></pre>
<p>Firstly, we use <code>inline-grid</code> because we want this block to only be as big as the content inside it. For that same reason, we use <code>max-content</code> in the <code>grid-template-columns</code> declaration. Lastly, we again, directly target the HTML <code>&lt;img&gt;</code> element and apply our 100% radius.</p>
<h3>Key header</h3>
<p>This is the element that introduces our transactions and summary. It provides a label for that data and also some key actions. It’s already got the <code>splitter</code> on it, so in true CUBE CSS fashion, we are using this block to create more contextual specificity.</p>
<p>Create a new file called <code>scss/blocks/_key-header.scss</code> and add the following to it:</p>
<pre><code>.key-header {
  align-items: flex-end;

  &gt; :last-child {
    display: flex;
    flex-wrap: wrap;
    gap: get-size('300');

    &gt; * {
      flex-shrink: 0;
      margin: 0.2rem 0 0 0.2rem;
    }
  }

  @include media-query('md') {
    &gt; :last-child {
      justify-content: flex-end;
    }
  }
}
</code></pre>
<p>This is mostly self-explanatory because the main theme is specifying alignment and justification.</p>
<p>The key area to focus on, though, is the <code>:last-child</code>, which is a group of buttons. We use a wrapping flex container, like in the <code>site-head</code>, but we use <code>gap</code> to space the items. A progressive enhancement pro tip follows as we add a <em>tiny</em> bit of vertical and horizontal margin to the elements. This is our fallback for if <code>gap</code> isn’t supported. Because it’s such a tiny amount of margin, it barely makes a scratch on the overall layout too. Happy days.</p>
<h3>Button</h3>
<p>It wouldn’t be a UI without a button, so let’s get them out of the way. Create a new file called <code>scss/blocks/_button.scss</code> and add the following to it:</p>
<pre><code>.button {
  @extend .radius;

  font: inherit;
  display: inline-block;
  line-height: 1;
  text-decoration: none;
  border: 1px solid get-color('secondary');
  background: get-color('secondary');
  padding: 0.6rem 1.5rem;
  position: relative;

  @include apply-utility('weight', 'bold');

  &amp;[data-variant='ghost'] {
    border-color: currentColor;
    background: transparent;
  }

  &amp;:focus {
    outline-offset: -0.4rem;
    outline: 1px solid;
  }

  &amp;:hover {
    background: get-color('primary');
    border-color: get-color('primary');
    color: get-color('light');
  }

  &amp;:active {
    transform: scale(0.95);
  }
}
</code></pre>
<p>This is a pretty generic button. In our context, both buttons are links, but we should always style a button to be a real <code>&lt;button&gt;</code> too. This is why we use <code>font: inherit</code>, to level the playing field by inheriting the base font rules. This rule itself makes the concept of using a <code>&lt;div&gt;</code> for a button completely ridiculous. There’s a <a href="https://hankchizljaw.com/wrote/introducing-the-button-element/"><em>pile</em> of other reasons too</a>.</p>
<p>You’ll also spot that we’re using a couple of <a href="https://github.com/hankchizljaw/gorko#sass-functions">Gorko functions and mixins to apply some of our tokens</a>. The reason we do it in the block is because it’s common to have a lot of buttons. If each one is styled with utility classes, it gets out of hand <em>really quickly</em>. This is also why we <code>@extend</code> <code>.radius</code>.</p>
<p>Finally, top marks if you spotted an exception. This one: <code>&amp;[data-variant='ghost']</code>, sets specific styles for a “ghost button”, which has no background and instead, has a visible border.</p>
<p>Just before we leave this block, there’s a little trick to make it look “squishy” when pressed (<code>:active</code>), which you can <a href="https://piccalil.li/quick-tip/squishy-button-active-state/">read more about here</a>.</p>
<h3>Summary</h3>
<p>If you remember, we applied some tokens to this element early in the tutorial. Now it’s time to set up some specific block styles. Create a new file called <code>scss/blocks/_summary.scss</code> and add the following to it:</p>
<pre><code>.summary {
  padding: get-size('500') get-size('500') get-size('600') get-size('500');
  line-height: 1.1;

  dl,
  dt {
    --flow-space: #{get-size('700')};
  }

  dd {
    --flow-space: #{get-size('300')};
  }
}
</code></pre>
<p>The main thing we are doing here is controlling <code>--flow-space</code> by applying size ratio values. Because this Custom Property value is set in the block, it will only affect these elements. Magic.</p>
<h3>Table group</h3>
<p>These are our transaction tables. This block features several tables, headed with the date of the transactions. Create a new file called <code>scss/blocks/_table-group.scss</code> and add the following:</p>
<pre><code>.table-group {
  border: 1px solid get-color('grey');
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;

  h3 {
    --flow-space: #{get-size('600')};
  }

  table {
    --flow-space: 0.2rem;

    width: 100%;
    min-width: 30rem;
  }

  td,
  h3 {
    padding: 0.5rem 1rem;
  }

  tr:first-child {
    border-top: 1px solid get-color('grey');
  }

  tr:nth-child(odd) td {
    background: get-color('light-shade');
  }

  td:nth-child(3) {
    text-align: right;
  }
}
</code></pre>
<p>We apply the visual border styles at the highest level. Then, we hide overflow because we want our tables to have a minimum width. You can mess around with responsive tables, but I think that’s a lot of effort for not much return. Instead, if you hide overflow and set a <code>min-width</code>, the data in the tables remains as intended and the user can swipe to see it.</p>
<p>The rest of the block is setting a “striped” effect by targeting odd rows and aligning the last column to the right, because they are numeric values.</p>
<h3>Pill</h3>
<p>Ok, the last block is here and it’s a good ol’ pill (hello, Bootstrap). Create a new file called <code>scss/blocks/_pill.scss</code> and add the following to it:</p>
<pre><code>.pill {
  display: inline-block;
  padding: 0.3rem 0.35rem;
  font-size: get-size('400');
  text-decoration: none;
  line-height: 1;
  white-space: nowrap;
  text-align: center;

  // Apply a default background colour if no token set
  &amp;:not([class*='bg-']) {
    background: get-color('grey');
  }

  // Capitalize only in english
  [lang*='en'] &amp; {
    text-transform: capitalize;
  }
}
</code></pre>
<p>There’s two tricks in here. The first is if there’s no <code>bg-</code> utility on there, we set the grey colour by default. The second is that we want to capitalise the text, but <strong>only</strong> if this is English content. We do that by only adding the CSS if an element with an <code>en</code> language attribute is its parent. Handy, right?</p>
<h3>Wiring up the blocks</h3>
<p>We’ve got all of our blocks, so let’s completely transform the page by wiring them up. Open up <code>scss/global.css</code> and <strong>at the bottom of the file</strong>, add the following:</p>
<pre><code>// Blocks
@import 'blocks/button';
@import 'blocks/key-header';
@import 'blocks/pill';
@import 'blocks/site-head';
@import 'blocks/summary';
@import 'blocks/table-group';
@import 'blocks/user';
</code></pre>
<p>We are done! Now, when you reload your browser, it should look like this:</p>
<p><img src="https://piccalil.b-cdn.net/images/tutorials/cube-dash-4.jpg" alt="The final site looking nice and tidy" /></p>
<h2>Wrapping up</h2>
<p>I hope that this tutorial has helped certain aspects of CUBE CSS to make even more sense for you. It’s hard to visualise how a methodology works without getting stuck into something proper with it.</p>
<p>You can <a href="https://github.com/piccalil-li/cube-css-dashboard/archive/master.zip">download a zip of the final version here</a> or even go ahead and <a href="https://github.com/piccalil-li/cube-css-dashboard">check out a git repository of it</a>.</p>
<p>If this is your first intro to CUBE CSS, you should probably <a href="https://piccalil.li/cube-css/">head over to the documentation</a> or <a href="https://piccalil.li/blog/cube-css/">read this high-level overview of the methodology</a>.</p>
<p>Until next time, take it easy 👋</p>
        
        ]]></description>
        
      </item>
    
      <item>
        <title>Build a light and global state system</title>
        <link>https://piccalil.li/blog/build-a-light-and-global-state-system/?ref=intermediate-category-rss-feed</link>
        <dc:creator><![CDATA[Andy Bell]]></dc:creator>
        <pubDate>Tue, 10 Mar 2020 12:37:01 GMT</pubDate>
        <guid isPermaLink="true">https://piccalil.li/blog/build-a-light-and-global-state-system/?ref=intermediate-category-rss-feed</guid>
        <description><![CDATA[<p>Observability and reactivity have mostly been reserved for JavaScript frameworks in the past, but thanks to modern JavaScript, we can observe the state of an object and react to changes made to it.</p>
<p>In this tutorial, we’re going to use one of my favourite modern JavaScript features: <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy">Proxies</a>, to create a global, observable object that can be used as a lightweight, basic state management tool.</p>
<p>The end result is a meagre <strong>132 bytes</strong> minified (128 bytes gzipped), which is the sort of lightweight JavaScript that I love.</p>
<p>Here it is in its entirety:</p>
<pre><code>window.subscribers = [];

const state = new Proxy(typeof defaultState !== 'undefined' ? defaultState : {}, {
  set(state, key, value) {
    const oldState = {...state};

    state[key] = value;

    window.subscribers.forEach(callback =&gt; callback(state, oldState));

    return state;
  }
});
</code></pre>
<p>Let’s dig into how it all works, but first of all, we need to understand Proxies.</p>
<h2>What are Proxies and how do they work?</h2>
<p>A <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy">Proxy object</a> allows us to add custom behaviours to an object. We do this by defining a <strong>handler</strong>, which is where we set <a href="http://www.zsoltnagy.eu/es6-proxies-in-practice/"><strong>traps</strong></a>. This all sounds a bit dramatic, so I’ll create an analogy to hopefully make it a bit more understandable.</p>
<p>Consider a nightclub that can only have 100 people in it at any time. It has a queue of people outside which is 30 people long and inside the club there are 90 people. At the door, there is a security guard who has a count of the people currently in the club and for each person that tries to enter, they check that this number hasn’t reached 100 yet.</p>
<p>Because there are 90 people in the club, the security guard runs this process 10 times and waves the people through. This now means that there are 100 people in the club so no more can enter. Because of this, the next person that tries to enter gets stopped.</p>
<p>If someone leaves the club, the number goes down, so that means more can enter. The security guard <strong>observes</strong> the number and runs another process which calls people forward to enter as other people leave. All of this creates a nice, fluid system that keeps the maximum amount of people in the club where possible.</p>
<p>Let’s now translate this into JavaScript.</p>
<p>We define our object as usually would without Proxies, like this:</p>
<pre><code>const defaultState = {
  guests: 90
};
</code></pre>
<p>Now lets create our security guard: <abbr>AKA</abbr> the Proxy:</p>
<pre><code>const state = new Proxy(typeof defaultState !== 'undefined' ? defaultState : {}, {
  set(state, key, value) {
    if (key === 'guests' &amp;&amp; state.guests &gt;= 100) {
      throw new Error('No more guests allowed');
      return state;
    }

    state.guests = value;
    return state;
  }
});
</code></pre>
<p>This is where the security guard is doing their core job: making sure too many people can’t enter the club.</p>
<p>In the context of this Proxy, we are using what’s called a <a href="http://www.zsoltnagy.eu/es6-proxies-in-practice/"><strong>trap</strong></a>—specifically a <strong>set trap</strong>. This particular trap monitors each time outside code tries to modify a property of the object, so the JavaScript that triggers this trap should look very familiar:</p>
<pre><code>state.guests = 93;
</code></pre>
<p>We need to observe the guest count and we can do that within our Proxy <strong>set trap</strong>. We need to do some groundwork, first.</p>
<p>The first thing we’ll do is set an array of subscriber functions on the <code>window</code> like this:</p>
<pre><code>window.subscribers = [];
</code></pre>
<p>Then inside our <strong>set trap</strong>, we can publish the state changes.</p>
<pre><code>set(state, key, value) {
	if(key === 'guests' &amp;&amp; state.guests &gt;= 100) {
	  throw new Error('No more guests allowed');
	  return state;
	}

	state.guests = value;

	// Publish the state changes to the subscribers
  window.subscribers.forEach(callback =&gt; callback(state));

  return state;
}
</code></pre>
<p>This means we can do something like this now:</p>
<pre><code>const guestWatcher = state =&gt; {
  if (state.guests &lt;= 100) {
    alert('More guests can now enter');
  }
};
</code></pre>
<p>It’s at this point that our security guard would wave some more people in.</p>
<h2>Applying this process to create a global state system</h2>
<p>Now that you hopefully understand how Proxies work at a surface level, you can probably see how we apply an almost identical approach to create our global state system.</p>
<p>To save you scrolling back up, here it is again:</p>
<pre><code>window.subscribers = [];

const state = new Proxy(typeof defaultState !== 'undefined' ? defaultState : {}, {
  set(state, key, value) {
    const oldState = {...state};

    state[key] = value;

    window.subscribers.forEach(callback =&gt; callback(state, oldState));

    return state;
  }
});
</code></pre>
<p>Let’s break it down:</p>
<p>We start by creating an empty array to hold our subscriber functions. They could do <em>anything</em> and they will be called <strong>every time any object value changes</strong>, which is super handy.</p>
<div><h2>FYI</h2>
<p>To prevent your subscriber functions running unnecessarily, you can test the <code>oldState</code> value against the <code>state</code> that is passed in. Both of these are passed as parameters, so in the context of our nightclub, we can do this:</p>
<pre><code>window.subscribers.push((state, oldState) =&gt; {
  // No changes, so bail out
  if (state.guests === oldState.guests) {
    return;
  }
});
</code></pre>
<p>If you’re used to working with frameworks like React, this is probably a pretty recognisable approach to prevent your components re-rendering where they don’t need to.</p>
</div>
<p>After we define our <code>subscribers</code> array on the window object, we create the Proxy. The first parameter that’s passed to a Proxy is an object. In our sample, we pass this: <code>typeof defaultState !== 'undefined' ? defaultState : {}</code>.</p>
<p>What that does is use <code>defaultState</code> if it is defined or use an empty object by default. This is really handy for setting your sensible defaults <strong>before</strong> you create the Proxy.</p>
<p>The second parameter is a <strong>handler</strong>. We’re only setting one <strong>trap</strong> in this <strong>handler</strong>, which is the <strong>set</strong> trap, which runs whenever a value of the proxied-object is changed.</p>
<p>Like we saw in the nightclub example, let’s say you have this as your <code>defaultState</code>:</p>
<pre><code>const defaultState = {
  name: 'Rafaela'
};
</code></pre>
<p>When we do this: <code>state.name = 'Tlaytmas'</code>, our <strong>set trap</strong> will spring into action.</p>
<p>Inside the <strong>set trap</strong> we take a copy of our old state:</p>
<pre><code>const oldState = {...state};
</code></pre>
<p>The reason we use a spread operator is because if we did <code>const oldState = state</code>, it would create a <a href="https://codeburst.io/explaining-value-vs-reference-in-javascript-647a975e12a0">reference</a>. This means that when <code>state</code> changes: so would <code>oldState</code>. Using a spread operator prevents that from happening.</p>
<pre><code>state[key] = value;
</code></pre>
<p>After we take a copy of our old state, we need to apply the changes to our object. Remember: this is a trap that we’ve created, so if we don’t apply the changes that were requested, they won’t change. This is why developers often use Proxies to perform validation.</p>
<pre><code>window.subscribers.forEach(callback =&gt; callback(state, oldState));

return state;
</code></pre>
<p>Lastly, we loop through the window’s subscriber functions and call them before eventually returning the new state back.</p>
<hr />
<p><em>And…breathe</em>, because we’re done. Pretty darn cool, right?</p>
<p>This setup is all cool in theory, but how does it look in practice? Let’s take a look.</p>
<h2>Examples</h2>
<p>The first example is a countdown clock. Here’s a demo, which you will probably need to click “reset” to see the effect:</p>
<p></p><p>See the Pen <a href="https://codepen.io/piccalilli/pen/mdJqJbJ">Proxy-driven global state object - Countdown</a> by Andy Bell (<a href="https://codepen.io/piccalilli/">@piccalilli</a>) on <a href="https://codepen.io">CodePen</a>.</p><p></p>
<p>What we do here is set our default state to be 20 seconds. A <code>renderCountdown</code> subscriber function is then passed into the window’s <code>subscribers</code> array. This means that for each second that the <code>runTimer()</code> timer takes from state, the front-end will be re-rendered.</p>
<p>Once the timer has reached <strong>0</strong>, we change the HTML to a <code>role="alert"</code> element to notify the user of assistive technology that the timer has completed. This also reveals a reset button which when clicked, starts the timer again.</p>
<div><h2>FYI</h2>
<p>We use <code>aria-atomic="true"</code> on the <code>role="timer"</code> element because only the <strong>number changes</strong>.</p>
<p>The timer role is pretty darn handy, too, which you can <a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions">read about, along with other roles, here</a>.</p>
</div>
<p>Our second example is a good ol’ character counter that sits under a <code>&lt;textarea&gt;</code>.</p>
<p></p><p>See the Pen <a href="https://codepen.io/piccalilli/pen/eYNemYB">Proxy-driven global state object - Character Counter</a> by Andy Bell (<a href="https://codepen.io/piccalilli/">@piccalilli</a>) on <a href="https://codepen.io">CodePen</a>.</p><p></p>
<p>We trigger state changes with every <code>input</code> event on the <code>&lt;textarea&gt;</code> which renders the resulting message underneath. If the character count has not been exceeded, it shows how many you have left, in a positive state. If you exceed the character count, though, it shows how many you have gone over in an error state.</p>
<p>The <code>data-element="result"</code> element that houses the character count has <code>aria-live="polite"</code> which stops assistive technology being hounded by the constant updates. We also use <code>aria-atomic="true"</code> again to determine that the region is only partially updated.</p>
<p>Just for fun, there’s a summary <em>outside</em> the form which also updates with how many characters you’ve typed. Because state is stored centrally, this is pretty darn trivial.</p>
<h2>Wrapping up</h2>
<p>This is just one example of how you can use Proxies to create lots of power, with <em>tiny</em> amounts of code. They really are a fantastic addition to JavaScript.</p>
<p>The central system that powers this particular example is tiny snippet of code, which in a browser context where connection speed, processor speed and contextual environment attributes are completely unpredictable, is exactly the sort of thing we need to build hi-performance sites. With that in mind: this sort of global state system probably won’t be helpful if you have a <em>massive</em> app, but that only accounts for a tiny minority of cases, so for most stuff, you can achieve a lot with a meagre <strong>132 bytes</strong> of JavaScript.</p>
<p>I hope that this tutorial has piqued your interest in state management and state machines too, because there is a <em>lot</em> of good stuff to learn. Here’s a few of my recommended resources:</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=0cqeGeC98MA">Crafting Stateful Styles with State Machines by David Khourshid</a></li>
<li><a href="https://statecharts.github.io/what-is-a-state-machine.html">What is a state machine?</a></li>
<li><a href="https://css-tricks.com/build-a-state-management-system-with-vanilla-javascript/">Build a state management system with vanilla JavaScript</a> (hey it’s me again)</li>
<li><a href="https://hackernoon.com/introducing-javascript-es6-proxies-1327419ab413">Introducing Javascript ES6 Proxies</a> (the article that got me excited about Proxies)</li>
</ul>
        
        ]]></description>
        
      </item>
    
      <item>
        <title>Create a responsive grid layout</title>
        <link>https://piccalil.li/blog/create-a-responsive-grid-layout-with-no-media-queries-using-css-grid/?ref=intermediate-category-rss-feed</link>
        <dc:creator><![CDATA[Andy Bell]]></dc:creator>
        <pubDate>Tue, 10 Mar 2020 00:00:00 GMT</pubDate>
        <guid isPermaLink="true">https://piccalil.li/blog/create-a-responsive-grid-layout-with-no-media-queries-using-css-grid/?ref=intermediate-category-rss-feed</guid>
        <description><![CDATA[<p>Embracing the flexible nature of the web gives us resilient user interfaces. Instead of using prescribed, specific sizes, we give elements sensible boundaries and let them auto flow or resize where possible. A good way to think of this is that we provide browsers with some rules and regulations, then let them decide what’s best—using them as a guide.</p>
<h2>What we’re making</h2>
<p>In this tutorial, we’re going to take this flexible mindset and produce a responsive grid layout that rules and regulations, then renders the best possible grid for the device that asks for it.</p>
<p>Here it is in action:</p>
<p></p><p>See the Pen <a href="https://codepen.io/piccalilli/pen/MWwEzNe">Responsive grid layout: Example 1</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 a fully responsive grid that uses <strong>no media queries</strong> to work across <strong>all</strong> viewports and it’s all thanks to <a href="https://css-utilitys.com/snippets/css/complete-guide-grid/">CSS Grid</a>.</p>
<h2>How it works</h2>
<p>First of all, let’s take a look at the code:</p>
<pre><code>.auto-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--auto-grid-min-size, 16rem), 1fr));
  grid-gap: 1rem;
}
</code></pre>
<p>CSS Grid is incredibly powerful for layout but usually, this type of flexible layout utility would use <a href="https://css-tricks.com/snippets/css/a-guide-to-flexbox/">Flexbox</a>, because of its <em>flexibility</em>. The reason we use grid for this particular utility, though, is the magic function, <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/minmax">minmax</a>.</p>
<p>The minmax function allows us to clamp our grid items to maintain just the <em>right amount</em> of control. We achieve this by telling the function what the smallest size is for our items, by using a CSS Custom Property, which has a fallback of <strong>16rem</strong>. Then we tell it what the maximum size should be for each item. We declare that as <strong>1fr</strong>, which takes 1 portion of the <strong>remaining available space</strong>.</p>
<p>Because each item in this grid uses <strong>1fr</strong>, the remaining space is split up equally between the other items, so if there are 10 items, 1fr is equal to <strong>10% of the remaining available space</strong>. This is how we get that nice flexibility.</p>
<div><h2>FYI</h2>
<p>The <code>--auto-grid-min-size</code> <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/--*">Custom Property</a> powers the minimum width. We set this as a Custom Property because we probably never know where this layout will appear, so by making the min-width easily configurable, it can be modified to work in specific contexts.</p>
<p>Because we’re setting a default value at the point of using <code>--auto-grid-min-size</code>, we’re reducing the risk of of creating specificity issues <em>and</em> making sure that if <code>--auto-grid-min-size</code> isn’t set: the grid will still operate as we expect it too. Now <em>that’s</em> flexibility.</p>
</div>
<h2>How can we improve this layout utility?</h2>
<p>Because this <code>.auto-grid</code> utility will keep trying to fill remaining available space, it’s useful to either give the layout itself a maximum width or wrap it with a shared wrapper utility. I prefer the latter approach because explicitly sizing the grid itself will reduce its own flexibility.</p>
<p>It’s fine to explicitly set a width sometimes, sure, but creating specific components that <em>only</em> deal with page-wide composition, like the wrapper element make working with flexible component systems a dream!</p>
<p>Here’s the HTML code for the wrapper element:</p>
<pre><code>&lt;div class="wrapper"&gt;
  &lt;ul class="auto-grid"&gt;
    &lt;!-- items go here --&gt;
  &lt;/ul&gt;
&lt;/div&gt;
</code></pre>
<p>And then the CSS:</p>
<pre><code>.wrapper {
  max-width: 65rem;
  margin-left: auto;
  margin-right: auto;
  padding: 0 1rem;
}
</code></pre>
<p>Here it is in action:</p>
<p></p><p>See the Pen <a href="https://codepen.io/piccalilli/pen/YzXrdwR">Responsive grid layout: Example 2 - Wrapper</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>FYI</h2>
<p>The wrapper is set to be <code>max-width: 55rem</code> in this example, so you can see the effect in the context of this tutorial.</p>
<p>Usually I use the wrapper as a central container and limit the width to around 62rem or 1000px.</p>
</div>
<p>Making the wrapper a separate utility means that we can use it wherever we need it, which is some good ol’ portability!</p>
<p>Because the wrapper has a <code>max-width</code> set, it’ll support all viewport sizes too, without using media queries. The padding provides a gutter, so at tiny viewports, you get a <strong>1rem</strong> sized gap at each side, which is ideal.</p>
<h2>Progressive enhancement</h2>
<p><a href="https://caniuse.com/#feat=css-grid">CSS Grid support</a>, at the time of writing, is <strong>94.69%</strong>, so the <em>vast majority</em> of browsers have support for it. We <em>can</em> improve our code to provide a default experience for the minority of users that don’t have support for it with a <a href="https://hankchizljaw.com/wrote/the-p-in-progressive-enhancement-stands-for-pragmatism/">minimum viable experience</a>, though.</p>
<p></p><p>See the Pen <a href="https://codepen.io/piccalilli/pen/XWbeoMz">https://codepen.io/andybelldesign/pen/XWbeoMz</a> by Andy Bell (<a href="https://codepen.io/piccalilli/">@piccalilli</a>) on <a href="https://codepen.io">CodePen</a>.</p><p></p>
<p>For this layout, the minimum viable experience is stacked items with some vertical margin between each one, which for the <strong>5.31%</strong> of browsers that don’t support CSS grid:</p>
<pre><code>.auto-grid &gt; * {
  max-width: 25rem;
  margin-left: auto;
  margin-right: auto;
}

.auto-grid &gt; * + * {
  margin-top: 1rem;
}

@supports (display: grid) {
  .auto-grid {
    display: grid;
    grid-template-columns: repeat(
      auto-fill,
      minmax(var(--auto-grid-min-size, 16rem), 1fr)
    );
    grid-gap: 1rem;
  }

  .auto-grid &gt; * {
    max-width: unset;
    margin: unset;
  }
}
</code></pre>
<p>As the code block demonstrates, the minimum viable experience removes that default style using <a href="https://piccalil.li/tutorial/solution-006-auto-scrolling-responsive-grid">@supports</a> which detects CSS Grid support. This sample will work fine all the way back to <em>super old</em> IE browser versions. You could even use floats and Flexbox if you absolutely wanted a grid-like layout for <em>all</em> browsers and a real power move would be to use something like <a href="https://piccalil.li/tutorial/solution-006-auto-scrolling-responsive-grid">this auto-scrolling approach</a>.</p>
<p>Utilising the forgiving nature of CSS to build up, rather than fix and break down will very likely result in you writing better, lighter code, so finding the lowest-tech approach to compatibility issues is only ever going to be a good thing.</p>
<p><img src="https://piccalil.b-cdn.net/images/tutorials/auto-grid-ie11.jpg" alt="The minimum viable experience in IE11" title="The minimum viable experience in IE11 shows how although the grid isn’t present, the sensible defaults make the layout look smart. The user will probably never know that it is supposed to be a grid because it’s not broken. This is the magic of progressive enhancement." /></p>
<h2>Wrapping up</h2>
<p>I use this auto-grid <em>all the time</em>. I also use the wrapper utility, extensively in every project that I work on. Hopefully this tutorial has demonstrated the power of modern CSS and how we can write very little to achieve <em>a lot</em>. It really is a powerful language when you give it room to breathe.</p>
<p>Even the heavier, progressively enhanced version that supports <em>all</em> browsers is tiny and importantly, so much tinier than writing hacks and specific browser CSS!</p>
        
        ]]></description>
        
      </item>
    
    </channel>
  </rss>
