<?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 - Tutorial topic archive</title>
      <link>https://piccalil.li/</link>
      <atom:link href="https://piccalil.li/category/tutorial.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 - Tutorial topic archive 2026</copyright>
      <docs>https://www.rssboard.org/rss-specification</docs>
      <pubDate>Mon, 10 Aug 2026 04:09:38 GMT</pubDate>
      <lastBuildDate>Mon, 10 Aug 2026 04:09:38 GMT</lastBuildDate>

      
      <item>
        <title>A highly configurable switch component using modern CSS techniques</title>
        <link>https://piccalil.li/blog/a-highly-configurable-switch-component-using-modern-css/?ref=tutorial-category-rss-feed</link>
        <dc:creator><![CDATA[Andy Bell]]></dc:creator>
        <pubDate>Tue, 30 Jan 2024 07:55:00 GMT</pubDate>
        <guid isPermaLink="true">https://piccalil.li/blog/a-highly-configurable-switch-component-using-modern-css/?ref=tutorial-category-rss-feed</guid>
        <description><![CDATA[<p>Safari Technology preview has <a href="https://alvaromontoro.com/blog/68049/new-toggle-switch-lands-in-safari">recently added a native switch component with version 185 and 186</a>, which is great! It’s going to be a long while before this is ready to rock on a production website though.</p>
<p>Still, <a href="https://nt1m.github.io/html-switch-demos/">this collection of demos is worth enjoying</a>. Here’s a video for those who don’t have the latest version of Safari Technology Preview.</p>
<figure>
<video></video>
<figcaption>Native switches that are seemingly highly customisable with CSS in Safari Technology preview.</figcaption>
</figure>
<div><h2>FYI</h2>
<p>You’re going to need to enable the HTML switch, <code>::thumb</code> and <code>::track</code> pseudo-element feature flags for this to work for you.</p>
</div> 
<p>While we wait for native switch support, I thought I would build a highly configurable switch component using <code>:has()</code>, container queries, Logical Properties and Custom Properties for fun <em>and</em> to show you how much goes into a truly flexible component. Let’s dig in.</p>
<h2>What we’re building</h2>
<p></p><p>See the Pen <a href="https://codepen.io/piccalilli/pen/MWxERjV/615c784c307feb96f5e7459c5d5aa456">Our final switch component</a> by Andy Bell (<a href="https://codepen.io/piccalilli/">@piccalilli</a>) on <a href="https://codepen.io">CodePen</a>.</p><p></p>
<h2>HTML first, always</h2>
<p>The HTML for this is pretty straightforward:</p>
<pre><code>&lt;label class="switch-input"&gt;
  &lt;span class="visually-hidden"&gt;Enable setting&lt;/span&gt;
  &lt;input type="checkbox" role="switch" class="visually-hidden" /&gt;
  &lt;span class="switch-input__decor" data-switch-input-state="on" aria-hidden="true"
    &gt;On&lt;/span
  &gt;
  &lt;span class="switch-input__decor" data-switch-input-state="off" aria-hidden="true"
    &gt;Off&lt;/span
  &gt;
  &lt;span class="switch-input__thumb" aria-hidden="true"&gt;&lt;/span&gt;
&lt;/label&gt;
</code></pre>
<p>The first thing to note is the root of this component is a <code>&lt;label&gt;</code> element. I like that pattern for checkbox and radio buttons because you get a nice increased tap area.</p>
<p>The HTML form control is a checkbox, but I’ve added a <code>role="switch"</code> attribute to it. This is so the component is announced as a switch by a screen reader and also each state change is announced as “on” or “off”, which is appropriate for a switch in my opinion.</p>
<p>I could have used an <code>aria-label</code> for the text label (always add a text label, pals), but I opted instead for a visually hidden <code>&lt;span&gt;</code>. The main reason is it’s easier for a user’s in-browser translation tool to translate the content.</p>
<div><h2>FYI</h2>
<p>If you’re going to use this in the wild, avoid using the word “switch” in your label because it’s already announced as a switch by screen readers.</p>
</div>
<p>Lastly, there’s 3 decorative only elements. The “on” and “off” text are hidden from assistive tech with <code>aria-hidden="true"</code> because that tech is already announcing those states. The visual thumb of the control is hidden in the same manner too because it only provides value for sighted users.</p>
<h2>Configuration settings</h2>
<p>As promised, this thing is <em>configurable</em>. There’s no better way in native CSS than Custom Properties.</p>
<pre><code>:root {
  --switch-input-thumb-size: 44px;
  --switch-input-thumb-bg: #ffffff;
  --switch-input-thumb-stroke: 1px solid grey;
  --switch-input-off-bg: #444444;
  --switch-input-off-text: #ffffff;
  --switch-input-on-bg: #00a878;
  --switch-input-on-text: #ffffff;
  --switch-input-gutter: 4px;
  --switch-input-decor-space: var(--switch-input-gutter) 1.25ch;
  --switch-input-focus-stroke: 2px solid #ff6978;
  --switch-input-font-weight: bold;
  --switch-input-font-family: sans-serif;
  --switch-input-font-size: 18cqw;
  --switch-input-transition: inset 50ms linear;
}
</code></pre>
<p>I’m not going to go into too much detail at this point. I’ll explain stuff as we come to it in the component’s CSS. One thing I will note though is that the pixel sizes are there for reasons like:</p>
<ol>
<li>I wanted to hit the <a href="https://www.smashingmagazine.com/2023/04/accessible-tap-target-sizes-rage-taps-clicks/#:~:text=It's%20worth%20noting%20that%20according,%2C%20the%20larger%2C%20the%20better.">WCAG minimum tap target size</a></li>
<li>The thumb size and thumb gutter are used in calculations, so <code>clamp()</code> is outa the window</li>
</ol>
<h2>Visually hidden utility</h2>
<pre><code>.visually-hidden {
  border: 0;
  clip: rect(0 0 0 0);
  height: auto;
  margin: 0;
  overflow: hidden;
  padding: 0;
  position: absolute;
  width: 1px;
  white-space: nowrap;
}
</code></pre>
<p>I roll this variant of screen reader only CSS out on pretty much every project and have done so <a href="https://piccalil.li/quick-tip/visually-hidden/">for quite some time</a>. This will allow a screen reader to read the label’s content, but will visually hide it and also prevent it from affecting layout etc.</p>
<h2>The <code>.switch-input</code> root</h2>
<p>It’s time to get stuck into the component now.</p>
<pre><code>.switch-input {
  width: calc((var(--switch-input-thumb-size) * 2) + (var(--switch-input-gutter) * 3));
  height: calc(var(--switch-input-thumb-size) + (var(--switch-input-gutter) * 2));
  border-radius: calc(var(--switch-input-thumb-size) + var(--switch-input-gutter));
  padding: var(--switch-input-gutter);
  background: var(--switch-input-off-bg);
  color: var(--switch-input-off-text);
  text-align: left;
  text-transform: uppercase;
  font-family: var(--switch-input-font-family);
  font-weight: var(--switch-input-font-weight);
  position: relative;
  cursor: pointer;
  container-type: inline-size;
}
</code></pre>
<p>The first calculation is the width of the component itself because it is also the switch “track”. The two configuration options used are <code>--switch-input-thumb-size</code> and <code>--switch-input-thumb-gutter</code>. The thumb size is self-explanatory, but the gutter is the space around the thumb.</p>
<p><img src="https://piccalil.b-cdn.net/images/blog/finished-switch.jpg" alt="The finished switch component in its off state" /></p>
<p>The gutter provides space around the thumb, so we need to account for each side of the overall <code>.switch-input</code> element and also added space for the middle. The formula for the calculation is <code>thumbSize x 2</code> added to <code>gutter x 3</code>.</p>
<p>The <code>border-radius</code> uses <a href="https://set.studio/relative-rounded-corners/">the following formula</a> for relative rounded corners: <code>radius</code> + <code>padding</code>. It’s not as visible in this context as say, a rounded rectangle, but if you have a large gutter it will make a difference.</p>
<p>There’s a lot of inheritable CSS in here such as font treatments, so we’ll skip that. The last bit I want to focus on is the <code>container-type: inline-size</code>. This will be used later to calculate the label’s size, so it’s critical that it is present here because that size will be relative to the track’s inline size. We also roll out <code>position: relative</code> because everything is absolutely positioned from here on in.</p>
<h2>The decorative text elements</h2>
<div><h2>FYI</h2>
<p>These are completely optional. You might also want to use icons for a theme switcher, for example.</p>
</div>
<pre><code>.switch-input__decor {
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  padding: var(--switch-input-decor-space);
  font-size: var(--switch-input-font-size);
  display: flex;
  width: 100%;
  align-items: center;
}
</code></pre>
<p>We’re using the logical versions of <code>inset</code> here because it’ll be handy for this component to respond the HTML <code>dir</code> attribute. For example, if a parent HTML has <code>dir="rtl"</code>, it’ll automatically present as <code>rtl</code>.</p>
<div><h2>FYI</h2>
<p>You might not want this to be the behaviour of your version of this component. If you add <code>direction: ltr</code> to the <code>.switch-input</code> CSS, it’ll behave as you wish it too.</p>
</div> 
<p>It’s worth noting that the <code>inset</code> shorthand property is not logical. It is shorthand for <code>top</code>, <code>right</code>, <code>bottom</code> and <code>left</code>. That’s why we’re using the specific logical versions.</p>
<p>You can also see that we’re applying our <code>font-size</code> here, rather than inheriting from the parent. This is because the <code>--switch-input-font-size</code> Custom Property is using <code>cqw</code> units: a portion of the container’s computed width.</p>
<figure>
<video></video>
<figcaption>As the thumb sizes changes, the whole component, including the text changes with it. As the padding increases, the need for a relative border radius for the parent component is demonstrated too.</figcaption>
</figure>
<p>As that demo shows, if the component grows, the labels grow with it nicely. Please use this approach with caution though. You need to make sure that the text is large enough (ideally computed to at least <code>16px</code>) and that it also zooms appropriately. With the switch input’s overall size being pixel controlled in this example, and the fact we’re using <code>44px</code> — the minimum tap target size — that is the case.</p>
<h3>Future stuff: align-content</h3>
<p>A nice improvement to this decorative text’s rule would be the following (don’t add this to your code):</p>
<pre><code>align-content: center;
display: block;
block-size: 100%;
</code></pre>
<p>We’re only using flexbox to vertically align our text labels in the middle. This new alignment capability is <a href="https://rachelandrew.co.uk/archives/2023/12/19/align-content-in-block-layout/">arriving to block elements</a> in the future which should render those already completely out of date, vertical alignment CSS memes, useless, once and for all. I imagine this won’t change the social media clout chaser’s behaviour though, unfortunately.</p>
<pre><code>.switch-input__decor[data-switch-input-state='off'] {
  justify-content: flex-end;
}
</code></pre>
<p>Lastly for this element, we’re adding a <a href="https://cube.fyi/exception.html">CUBE Exception</a> to push the “off” label out to the inline end because the thumb will be at the inline start in the default off state.</p>
<h2>The thumb element</h2>
<p>Time to style up our little round thumb element.</p>
<pre><code>.switch-input__thumb {
  display: block;
  width: var(--switch-input-thumb-size);
  height: var(--switch-input-thumb-size);
  border-radius: var(--switch-input-thumb-size);
  background: var(--switch-input-thumb-bg);
  border: var(--switch-input-thumb-stroke);
  z-index: 1;
  position: absolute;
  inset-block-start: var(--switch-input-gutter);
  inset-inline-start: var(--switch-input-gutter);
  transition: var(--switch-input-transition);
}
</code></pre>
<p>We’re making a square by setting the <code>width</code> and <code>height</code>, then making that square a circle by using the same configuration value for <code>border-radius</code>. You could use percentages here, but I personally prefer this approach. I think it’s a symptom of the bad old days of browsers.</p>
<p>It’s important to set <code>z-index</code> because we want this to always be a layer up from our decorative labels and guarantees that the thumb won’t interfere with the visual text.</p>
<p>Finally, using absolute positioning, the thumb is set to the inline and block start, using the logical <code>inset</code> values. A <code>transition</code> (very quick one!) is added to smooth out the on and off state changes. Normally I would recommend that you don’t transition <code>inset</code> because <code>translate</code> is much smoother, but in this context, it should be fine because it’s not a large surface area and the transition is very quick (<code>100ms</code>).</p>
<h2>Focus states</h2>
<p>The important thing to get right here for me is that the focus ring should show for keyboard users only and not when the component is clicked or tapped.</p>
<pre><code>.switch-input:has(:focus-visible) .switch-input__thumb {
  outline: var(--switch-input-focus-stroke);
}
</code></pre>
<p>Luckily <code>:focus-visible</code> does this well and is <a href="https://caniuse.com/?search=focus-visible">very well supported</a>.</p>
<p>The party trick here is the usage of <code>:has()</code>. Historically with this sort of component you’d have to write a selector like this: <code>.switch-input input:focus-visible ~ .switch-input__thumb</code>.</p>
<p>This would require the source order to be just right in the component. Now that we have <code>:has()</code> — <a href="https://caniuse.com/?search=has">which is also very well supported</a> — that state can be determined at the root level of the component. In theory, the input could be the last child element now.</p>
<h2>The on/off states</h2>
<p>This is the last bit of CSS and we are done!</p>
<pre><code>.switch-input:has(:checked) {
  background: var(--switch-input-on-bg);
  color: var(--switch-input-on-text);
}

.switch-input:has(:checked) .switch-input__thumb {
  inset-inline-start: calc(
    var(--switch-input-thumb-size) + (var(--switch-input-gutter) * 2)
  );
}
</code></pre>
<p>Historically setting the background of the switch component would again, require some weird combination selectors and extra elements, <em>or</em> require a JavaScript dependency. Not anymore because <code>:has()</code> allows us to change the background and text colour of the component (if required) based on its child <code>:checked</code> state. Handy!</p>
<p>We also use that pattern to change the position of the thumb. It’s a calculation that similar to the one we added earlier to set the size of the overall component. This time, it’s pushing the thumb to the end when the switch is “on”. This is why the <code>--switch-input-gutter</code> is doubled, to account for the start space and also the middle space.</p>
<h2>Wrapping up</h2>
<p>After all that, we’ve got ourselves a lovely little component:</p>
<p></p><p>See the Pen <a href="https://codepen.io/piccalilli/pen/MWxERjV/615c784c307feb96f5e7459c5d5aa456">Our final switch component</a> by Andy Bell (<a href="https://codepen.io/piccalilli/">@piccalilli</a>) on <a href="https://codepen.io">CodePen</a>.</p><p></p>
<p>Would I use this in production? Probably, yeh. There has to be a damn good reason for a switch in the first place though.</p>
<p>Regardless, this is a handy little context to teach you about some of the super powers CSS gives us now.</p>
        
        ]]></description>
        
      </item>
    
      <item>
        <title>Build a fancy hover animation</title>
        <link>https://piccalil.li/blog/build-a-fancy-hover-animation/?ref=tutorial-category-rss-feed</link>
        <dc:creator><![CDATA[Andy Bell]]></dc:creator>
        <pubDate>Fri, 09 Apr 2021 00:00:00 GMT</pubDate>
        <guid isPermaLink="true">https://piccalil.li/blog/build-a-fancy-hover-animation/?ref=tutorial-category-rss-feed</guid>
        <description><![CDATA[<p>If you’ve got a collection of images, each with different colour profiles, it’s hard to make them look cohesive—especially when they’re in a tight grid.</p>
<p><img src="https://piccalil.b-cdn.net/images/tutorials/fancy-hover-intro.jpg" alt="A grid of very different colour-treated profile shots of people" /></p>
<p>We could edit and grade them all in Photoshop, but what happens when a new image is added? With CSS, we can use filters, pseudo-elements and transitions to create not only a cohesive look and feel, but also a fancy interactive element—all while maintaining semantic HTML and accessibility.</p>
<h2>What we’re building</h2>
<p>We’re building a team page that features 6 team members, in a responsive grid. By default, all you can see is their name and a black and white picture. When you hover or focus, their picture goes full-colour, their job title slides in and we introduce some nice filter effects.</p>
<p></p><p>See the Pen <a href="https://codepen.io/piccalilli/pen/poRdqvZ">Build a fancy hover animation - complete</a> by Andy Bell (<a href="https://codepen.io/piccalilli/">@piccalilli</a>) on <a href="https://codepen.io">CodePen</a>.</p><p></p>
<p>Pretty sweet right? Let’s dig in.</p>
<h2>Getting started</h2>
<p>For this tutorial you only need two files. A HTML file and a CSS file:</p>
<ul>
<li><code>index.html</code></li>
<li><code>global.css</code></li>
</ul>
<h2>HTML</h2>
<p>The first thing we’ll do is add the shell of our HTML document. 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;Fancy hover card&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=Inter:wght@400;500;700;900&amp;display=swap"
      rel="stylesheet"
    /&gt;
  &lt;/head&gt;
  &lt;body&gt;

  &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>All we have here are the basics of a HTML document. We’re pulling in a CSS reset and some fonts from Google Fonts. We’re also linking up to our <code>global.css</code> file.</p>
<p>Let’s add the main <code>&lt;article&gt;</code>. Inside the <code>&lt;body&gt;</code> element, add the following HTML:</p>
<pre><code>&lt;article class="flow"&gt;
	&lt;h1&gt;Our Team&lt;/h1&gt;
	&lt;p&gt;
		Hover or focus over each card to see the person’s job title slide in and the
		colour treatment change.
	&lt;/p&gt;
	&lt;div class="team"&gt;
	
	&lt;/div&gt;
&lt;/article&gt;
</code></pre>
<p>This is pretty straightforward, too. The <code>&lt;article&gt;</code> element creates a semantic grouping of the team section for us and in that group, we’re using a heading and paragraph to describe the content.</p>
<p>Next, let’s add the list of team members. Inside the <code>&lt;div class="team"&gt;</code> element, add the following:</p>
<pre><code>&lt;ul class="auto-grid" role="list"&gt;
	&lt;li&gt;
		&lt;a href="https://swop.link/cool" class="profile"&gt;
			&lt;h2 class="profile__name"&gt;Anita Simmons&lt;/h2&gt;
			&lt;p&gt;Founder&lt;/p&gt;
			&lt;img alt="Anita Simmons" src="https://source.unsplash.com/BhcutpohYwg/800x800" /&gt;
		&lt;/a&gt;
	&lt;/li&gt;
	&lt;li&gt;
		&lt;a href="https://swop.link/cool" class="profile"&gt;
			&lt;h2 class="profile__name"&gt;Celina Harris&lt;/h2&gt;
			&lt;p&gt;Creative Director&lt;/p&gt;
			&lt;img alt="Profile shot for Celina Harris" src="https://source.unsplash.com/j5KAuRrYX7g/800x800" /&gt;
		&lt;/a&gt;
	&lt;/li&gt;
	&lt;li&gt;
		&lt;a href="https://swop.link/cool" class="profile"&gt;
			&lt;h2 class="profile__name"&gt;Ruby Morris&lt;/h2&gt;
			&lt;p&gt;Technical Lead&lt;/p&gt;
			&lt;img alt="Profile shot for Ruby Morris" src="https://source.unsplash.com/pQyIutdScOY/800x800" /&gt;
		&lt;/a&gt;
	&lt;/li&gt;
	&lt;li&gt;
		&lt;a href="https://swop.link/cool" class="profile"&gt;
			&lt;h2 class="profile__name"&gt;Nicholas Castro&lt;/h2&gt;
			&lt;p&gt;Designer&lt;/p&gt;
			&lt;img alt="Profile shot for Nicholas Castro" src="https://source.unsplash.com/55JRsxcAiWE/800x800" /&gt;
		&lt;/a&gt;
	&lt;/li&gt;
	&lt;li&gt;
		&lt;a href="https://swop.link/cool" class="profile"&gt;
			&lt;h2 class="profile__name"&gt;Marc Dixon&lt;/h2&gt;
			&lt;p&gt;Developer&lt;/p&gt;
			&lt;img alt="Profile shot for Marc Dixon" src="https://source.unsplash.com/5wn6DeAEcmE/800x800" /&gt;
		&lt;/a&gt;
	&lt;/li&gt;
	&lt;li&gt;
		&lt;a href="https://swop.link/cool" class="profile"&gt;
			&lt;h2 class="profile__name"&gt;Chad Chadson&lt;/h2&gt;
			&lt;p&gt;Intern&lt;/p&gt;
			&lt;img alt="Profile shot for Chad" src="https://source.unsplash.com/7jCYw6a2Wao/800x800" /&gt;
		&lt;/a&gt;
	&lt;/li&gt;
&lt;/ul&gt;
</code></pre>
<p>There’s a couple of things to cover here. The first thing is that you might be confused why we’ve got <code>role="list"</code> <em>on a list</em>. The reason for this is <a href="https://www.scottohara.me/blog/2019/01/12/lists-and-safari.html">VoiceOver on iOS and Mac can remove list semantics when list styles are removed</a>. It still annoys me now, but here we are.</p>
<p>Inside this list is each profile. These are <code>&lt;a&gt;</code> elements with, <em>wait</em>, headings as child elements?! Yep, you can <a href="http://html5doctor.com/block-level-links-in-html-5/">add all sorts of flow content</a> inside an <code>&lt;a&gt;</code> element and it makes life much easier with this sort of context. Just be wary that selecting text can get a bit trickier for users when you use this sort of markup.</p>
<p>That’s the HTML sorted, so let’s make it look good!</p>
<h2>CSS</h2>
<p>We’re going to partially lean into the <a href="https://cube.fyi">CUBE CSS</a> methodology for this project. If you haven’t already <a href="https://piccalil.li/blog/cube-css">read up on it</a>, no worries: we’re not getting hugely into it, but knowing what it’s all about will probably benefit you.</p>
<p>The first thing we’re doing is setting some global styles. I try to style as much as I can as high up as I can and let the cascade do as much as possible. This results in some very light CSS, even in extremely large projects.</p>
<p>In this particular context, there’s not a huge amount of global CSS to write, but there’s still some! Open up <code>global.css</code> and add the following to it:</p>
<pre><code>/* Globals */
body {
  font-family: 'Inter', sans-serif;
  max-width: 55rem;
  padding: 2rem 1.5rem;
  margin: 0 auto;
  color: #241623;
  background: #eef2f4;
}

h1 {
  font-weight: 900;
  font-size: 2.7rem;
  max-width: 20ch;
}

p {
  max-width: 60ch;
}

a {
  color: currentColor;
}
</code></pre>
<p>The only thing to highlight here is that we’re limiting the line-lengths of the <code>&lt;h1&gt;</code> and <code>&lt;p&gt;</code> elements to improve readability. You can <a href="https://piccalil.li/quick-tip/line-length">read about that here</a>.</p>
<p>Let’s add some utilities now. There’s two to add: a <a href="https://piccalil.li/tutorial/create-a-responsive-grid-layout-with-no-media-queries-using-css-grid">grid layout </a>and a <a href="https://piccalil.li/quick-tip/flow-utility">flow utility</a>. I’ve written explainers for them both so check those out if you want to learn more, but for now, we’re just going to pop them into our CSS file.</p>
<p>Open up <code>global.css</code> and add the following:</p>
<pre><code>.auto-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--auto-grid-min-size, 14rem), 1fr));
  grid-gap: var(--auto-grid-gap, 0);
  padding: 0;
}

.flow &gt; * + * {
  margin-top: var(--flow-space, 1em);
}
</code></pre>
<p>With those in, let’s add a tiny <a href="https://cube.fyi/composition">composition style</a> too. The <a href="https://piccalil.li/quick-tip/flow-utility">flow utility</a> adds space to adjacent sibling elements and uses a custom property which allows us to override it. I’m happy with the default <code>1em</code> space for the heading and paragraph, but the grid of team members needs a touch more space. Add the following to your CSS:</p>
<pre><code>.team {
  --flow-space: 2em;
}
</code></pre>
<p>Because the <code>.team</code> element is a <code>&lt;div&gt;</code>, it inherits the base <code>font-size</code>, which because we’ve not changed it, is around <code>16px</code>. This means <code>1em</code> will be equal to that amount, which is a touch too tight, so we instead, set it to <code>2em</code>. The handy thing about using <code>em</code> units is that if the <code>font-size</code> changes, that <a href="https://piccalil.li/tutorial/relative-sizing-with-em-units">spacing will be relative</a>, too.</p>
<h3>Creating the profile block</h3>
<p>Time to get stuck into the main course of this tutorial. All the shell is built and really, it doesn’t look too bad.</p>
<p></p><p>See the Pen <a href="https://codepen.io/piccalilli/pen/OJWOYrK">Build a fancy hover animation - pre-profile</a> by Andy Bell (<a href="https://codepen.io/piccalilli/">@piccalilli</a>) on <a href="https://codepen.io">CodePen</a>.</p><p></p>
<p>Those profile cards need building, so first of all, let’s add the base block CSS. Open up <code>global.css</code> and add the following to it:</p>
<pre><code>.profile {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  aspect-ratio: 1/1;
  min-height: 150px;
  position: relative;
  padding: 1.5rem;
  backface-visibility: hidden;
  text-decoration: none;
  color: #ffffff;
  overflow: hidden;
}
</code></pre>
<p>We’re using flexbox in a column flow, which in this current writing mode, means that the main axis runs top-to-bottom. To push all the content <em>down</em>, we justify the content to <code>flex-end</code>. We’re also using <code>aspect-ratio</code> to make it a square. If that’s not supported, the <code>padding</code> and <code>min-height</code> will take over for us as a suitable fallback; good ol’ <a href="https://piccalil.li/blog/a-minimum-viable-experience-makes-for-a-resilient-inclusive-website-or-app">progressive enhancement</a>.</p>
<p>Lastly, we add a bit of visual treatment and <code>backface-visibility: hidden;</code> to help smooth out the transitions later on.</p>
<p>We need to add two pseudo-elements now that provide two gradient overlays. One gradient overlay is for making the text readable, regardless of what image is on display, and the other is for creating a nice orange overlay on hover. Add the following CSS:</p>
<pre><code>.profile::before,
.profile::after {
  content: '';
  width: 100%;
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;
}
</code></pre>
<p>We use absolute positioning for this decorative treatment because we want it to sit on top of everything else. We also don’t want it to affect the flow of content inside a profile card.</p>
<p>We need to add the gradients next, so first, add the following CSS:</p>
<pre><code>.profile::before {
  background: linear-gradient(
    to top,
    hsl(0 0% 0% / 0.79) 0%,
    hsl(0 0% 0% / 0.787) 7.8%,
    hsl(0 0% 0% / 0.779) 14.4%,
    hsl(0 0% 0% / 0.765) 20.2%,
    hsl(0 0% 0% / 0.744) 25.3%,
    hsl(0 0% 0% / 0.717) 29.9%,
    hsl(0 0% 0% / 0.683) 34.3%,
    hsl(0 0% 0% / 0.641) 38.7%,
    hsl(0 0% 0% / 0.592) 43.3%,
    hsl(0 0% 0% / 0.534) 48.4%,
    hsl(0 0% 0% / 0.468) 54.1%,
    hsl(0 0% 0% / 0.393) 60.6%,
    hsl(0 0% 0% / 0.31) 68.3%,
    hsl(0 0% 0% / 0.216) 77.3%,
    hsl(0 0% 0% / 0.113) 87.7%,
    hsl(0 0% 0% / 0) 100%
  );
  transition: 300ms opacity linear;
}

.profile::after {
  background: linear-gradient(45deg, hsl(5 97% 63% / 0.7) 0, hsl(5 97% 63% / 0) 100%);
  opacity: 0;
  transition: 300ms opacity linear;
}
</code></pre>
<p>You’re probably thinking, “what the hell is that gradient all about?!?” And frankly, I get it. The first thing to explain this is that we need to provide contrast for the white text, so a very dark gradient does this nicely. The problem with gradients <em>at the moment</em> is that they are not as smooth as they could be if we did a simple black to transparent black. The reason we add so many colour stops to the <code>::before</code> gradient is to create a really smooth finish. I recommend you read <a href="https://css-tricks.com/design-considerations-text-images/">this article</a> and <a href="https://bluegg.co.uk/blog/making-css-gradients-smooth">this article</a>. I used <a href="https://larsenwork.com/easing-gradients/#editor">this editor</a> to create it too.</p>
<p>The second gradient is much simpler. It’s a <code>45deg</code> angled gradient of a lush orange tone to provide that nice hover treatment.</p>
<p>Gradients are done, so now, let’s make sure that content sits on top of them. Add the following CSS:</p>
<pre><code>.profile &gt; * {
  z-index: 1;
}
</code></pre>
<p>This selects all direct child elements of the <code>.profile</code> element and sets their <code>z-index</code> to <code>1</code>. We don’t need to add <code>position: relative</code> to do this because we are in a flex context. The same would be the case if we were in a grid context too.</p>
<p>Let’s deal with the image next. Add the following CSS:</p>
<pre><code>.profile img {
  width: 100%;
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;
  margin: 0;
  z-index: -1;
  object-fit: cover;
  filter: grayscale(1);
  transition: filter 200ms ease, transform 250ms linear;
}
</code></pre>
<p>There’s loads going on here, so let’s break it down:</p>
<ol>
<li>We make it absolutely positioned so it effectively acts as a background image, not affecting the content within the profile block</li>
<li>Using <code>object-fit: cover</code> means that if we use an image that isn’t square, it won’t look squished when it’s set to fill it’s parent with <code>width: 100%;</code> and <code>height: 100%;</code></li>
<li>We make it grayscale by using the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/filter-function/grayscale()">grayscale filter</a></li>
<li>Finally, we set <code>z-index: -1</code> to send it to the back of it’s stacking context, which is the <code>.profile</code> element it lives in</li>
</ol>
<p>Lots covered there, but we’ve still got more things to add. Let’s concentrate on the text content. Add the following CSS:</p>
<pre><code>.profile h2 {
  font-size: 1.7rem;
  line-height: 1.2;
  font-weight: 900;
  letter-spacing: 0.03ch;
  transition: 300ms transform ease;
}

.profile p {
  font-size: 1.2rem;
  font-weight: 500;
}

.profile p {
  opacity: 0;
  transition: 300ms opacity linear, 300ms transform ease-in-out;
}

.profile h2,
.profile p {
  transform: translateY(2ex);
}
</code></pre>
<p>We start with some typesetting and then the attention quickly turns to how this content will interact. By default, we set the <code>&lt;p&gt;</code> to have <code>0</code> opacity. This combined with both the <code>&lt;h2&gt;</code> and <code>&lt;p&gt;</code> being pushed down with <code>transform</code> makes the <code>p</code> “disappear” because it has the job title in it.</p>
<p></p><p>See the Pen <a href="https://codepen.io/piccalilli/pen/VwPrNva">Build a fancy hover animation - visible p element</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>In that the above demo, I’ve made the <code>&lt;p&gt;</code> visible so you can see how it works with the <code>&lt;h2&gt;</code>. Go ahead and interact with it too!</p>
</div>
<p>One thing to touch on before we move on to interactions is that we are using <code>ex</code> units to push the text down. An <code>ex</code> unit is the x-height—the height of the “x” character in the chosen font and size. It’s a good idea to <a href="https://every-layout.dev/rudiments/units/">use type units to deal with type</a> because they scale with the type itself, giving you a solid and flexible front-end.</p>
<p>Right, let’s add the last bit, the interactive state. Add this CSS to the end of your <code>global.css</code> file:</p>
<pre><code>.profile:focus {
  outline: 0.5rem solid white;
  outline-offset: -0.5rem;
}

.profile:hover :is(h2, p),
.profile:focus :is(h2, p) {
  transform: none;
}

.profile:hover p,
.profile:focus p {
  opacity: 1;
  transition-delay: 200ms;
}

.profile:hover::after,
.profile:focus::after,
.profile:hover::before,
.profile:focus::before {
  opacity: 0.7;
}

.profile:hover img,
.profile:focus img {
  filter: grayscale(0);
  transform: scale(1.05) rotate(1deg);
}
</code></pre>
<p>For this section, when I say “shared interactive state”, I mean where both <code>:hover</code> and <code>:focus</code> share the same styles. Let’s break this down into pieces:</p>
<ol>
<li>We create a distinct <code>:focus</code> state on <code>.profile</code> so when a keyboard user focuses with their <kbd>tab</kbd> key, it’s very clear which one is in focus</li>
<li>The first thing we change with the shared interactive state is the <code>transform</code> of the <code>&lt;h2&gt;</code> and <code>&lt;p&gt;</code>, bringing them <em>back</em> to their natural flow</li>
<li>The <code>&lt;p&gt;</code> gets shown next in the shared interactive state by setting <code>opacity</code> to <code>1</code>. We also use a little <code>transition-delay</code> to allow other animations to happen prior to it being shown.</li>
<li>Lastly, we show the second gradient and set it to have the same opacity as the existing gradient. This happens along with the image getting the grayscale effect removed and a very subtle zoom and skew</li>
</ol>
<p>With that, <strong>we are done</strong>!</p>
<p>See the Pen <a href="https://codepen.io/piccalilli/pen/poRdqvZ">Build a fancy hover animation - complete</a> by Andy Bell (<a href="https://codepen.io/piccalilli/">@piccalilli</a>) on <a href="https://codepen.io">CodePen</a>.</p>
        
        ]]></description>
        
      </item>
    
      <item>
        <title>Fluid typography with CSS clamp</title>
        <link>https://piccalil.li/blog/fluid-typography-with-css-clamp/?ref=tutorial-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=tutorial-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=tutorial-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=tutorial-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=tutorial-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=tutorial-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>
    
    </channel>
  </rss>
