<?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 - Reality Check topic archive</title>
      <link>https://piccalil.li/</link>
      <atom:link href="https://piccalil.li/category/reality-check.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 - Reality Check topic archive 2026</copyright>
      <docs>https://www.rssboard.org/rss-specification</docs>
      <pubDate>Mon, 06 Apr 2026 16:02:07 GMT</pubDate>
      <lastBuildDate>Mon, 06 Apr 2026 16:02:07 GMT</lastBuildDate>

      
      <item>
        <title>Reality Check #3: Building out a layered hero grid layout from Dribbble</title>
        <link>https://piccalil.li/blog/reality-check-3-building-out-a-layered-hero-grid-layout-from-dribbble/?ref=reality-check-category-rss-feed</link>
        <dc:creator><![CDATA[Andy Bell]]></dc:creator>
        <pubDate>Wed, 17 Jan 2024 00:00:00 GMT</pubDate>
        <guid isPermaLink="true">https://piccalil.li/blog/reality-check-3-building-out-a-layered-hero-grid-layout-from-dribbble/?ref=reality-check-category-rss-feed</guid>
        <description><![CDATA[<p>First up, you may have noticed that <a href="https://piccalil.li/category/reality-check/">Reality Check</a> now has a new home. I've moved it from <a href="https://set.studio/">Set Studio's</a> blog to here because I think it makes more sense here, based on the technical nature of the content.</p>
<p>This is also my first post since deciding to <a href="https://piccalil.li/blog/piccalilli-will-return-in-2024/">bring back Piccalilli</a>. I wanted it to be a good'n so <em>here goes</em>.</p>
<h2>This edition’s project</h2>
<p>I’ve selected <a href="https://dribbble.com/shots/20598813-Wave-Web-Site-Design-Landing-Page-Home-Page-UI">Wave Web Site Design: Landing Page / Home Page UI</a> by <a href="https://dribbble.com/haloweb">Halo UI/UX</a> on Dribbble. The low fidelity hero background image with a photographic stripe through the middle really captured my imagination and it gave me an opportunity to use it — along with other elements on the page — as an opportunity to talk about choosing the path of least resistance. Our focus will be on the hero element only.</p>
<p><img src="https://piccalil.b-cdn.net/images/reality-check/original-23322c8f3d4294cfe9dc36f0df600155.png" alt="A landing page that features a mountain range background, that's clipped with a low fidelity, vector version of those mountains. It features all the usual elements of a site, but is laid out in a nice, overlapping grid layout." title="&lt;a href=&quot;https://dribbble.com/shots/20598813-Wave-Web-Site-Design-Landing-Page-Home-Page-UI&quot;&gt;Wave Web Site Design: Landing Page / Home Page UI&lt;/a&gt; by &lt;a href=&quot;https://dribbble.com/haloweb&quot;&gt;Halo UI/UX&lt;/a&gt; from Dribbble" /></p>
<p>I’m not actually going to make any design alterations either. Sure, I'm gonna use a <a href="https://fonts.google.com/specimen/DM+Sans">Google Font</a> and tweak some of the elements and copy, but there's been no static comp in Figma this time. We're instead going to explore how you can treat a static composition as a guide, rather than a source of truth because I know a lot of y'all work with designers who don't have much technical knowledge and I want to help you out for the future.</p>
<h2>Planning and asset creation</h2>
<p>Although I'm not re-working the design, I still wanted to create/treat some assets like the background image(s) and people shots.</p>
<p><img src="https://piccalil.b-cdn.net/images/reality-check/mh-figma.jpg" alt="A zoomed out view of my Figma board, featuring background asset frames, circular text, vector drawn assets and layout planning" /></p>
<p>It's also a great opportunity to <strong>plan before you build</strong>. If I were to give one piece of crucial advice to improve your front-end skills, it's before you even think about code, grab a cap of what you're building and draw all over it.</p>
<p><img src="https://piccalil.b-cdn.net/images/reality-check/mh-layout-planning.jpg" alt="A clip of the original composition with lines drawn over the top and annotations as I plan the layout of the page" /></p>
<p>For this project, I knew it was gonna be all about CSS grid, so it was imperative that I sketched out the grid lines. This helped me work out the markup of the page and which elements needed grouping where.</p>
<h3>Keeping things simple: background</h3>
<p>I'm lucky that I'm a designer by trade so I had the ability to draw out a low-fidelity version of the mountains with the pen tool, knowing I'd mask the photographic version in CSS.</p>
<p><img src="https://piccalil.b-cdn.net/images/reality-check/mh-bg-artboards.jpg" alt="A zoomed in view of Figma, showing the photographic mountain range background image and the vector version I drew" title="Mountain range image from &lt;a href=&quot;https://unsplash.com/photos/snow-covered-mountain-during-daytime-qcHkNRi5yD0&quot;&gt;Unsplash&lt;/a&gt; by Marin Tulard" /></p>
<p>I <em>could</em> have made this one background asset, but as this Dribbble composition has only considered large viewports (as is tradition), that central stripe would cause problems on smaller viewports. More on this later.</p>
<p>The main point to make is getting all this stuff ready in advance allows me to get everything in order, so my focus is purely on semantic markup and CSS when I come to write code.</p>
<p>With all the planning done and assets exported, converted and optimised, it's time to get stuck in with some code.</p>
<h2>HTML first, always</h2>
<p>Although the layout is on a pretty interesting grid, the markup actually follows a pretty logical flow.</p>
<pre><code>&lt;main&gt;
  &lt;article class="hero"&gt;
    &lt;h1 class="visually-hidden"&gt;Wave&lt;/h1&gt;
    &lt;div class="hero__inner wrapper"&gt;
      &lt;p class="hero__meta"&gt;&lt;abbr title="Established"&gt;Est.&lt;/abbr&gt; 1961&lt;/p&gt;
      &lt;div class="hero__content"&gt;
        &lt;p&gt;
          We create travels that feel like real adventures — in the most exciting of
          Earth’s corners.
        &lt;/p&gt;
        &lt;p&gt;
          &lt;a href="#"&gt;Explore&lt;/a&gt;
        &lt;/p&gt;
      &lt;/div&gt;
      &lt;img
        src="images/adventure.svg"
        alt="Adventure, in a nice handwritten-like form with illustrated birds surrounding it."
        class="hero__decor-text"
      /&gt;
      &lt;a class="hero__action" href="#"&gt;
        &lt;span class="visually-hidden"&gt;Explore surfer stories&lt;/span&gt;
        &lt;figure&gt;
          &lt;div class="negative-grid"&gt;
            &lt;div class="roundel" aria-hidden="true"&gt;
              &lt;svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"&gt;
                &lt;path fill="currentColor" d="m5 4 15 7.928L5 20V4Z" /&gt;
              &lt;/svg&gt;
              &lt;img src="images/roundel-text.svg" alt="" /&gt;
            &lt;/div&gt;
            &lt;img src="images/surfer-1.jpg" class="avatar" alt="" /&gt;
            &lt;img src="images/surfer-2.jpg" class="avatar" alt="" /&gt;
          &lt;/div&gt;
          &lt;figcaption class="visually-hidden"&gt;
            Two images of surfers, one surfing on a wave and the other, standing next to
            their board. A roundel sits next to them with round text, repeated, reading
            'surfer stories'.
          &lt;/figcaption&gt;
        &lt;/figure&gt;
      &lt;/a&gt;
    &lt;/div&gt;
    &lt;div class="hero__bg"&gt;
      &lt;picture class="hero__masked"&gt;
        &lt;source srcset="images/bg.avif" type="image/avif" /&gt;
        &lt;source srcset="images/bg.webp" type="image/webp" /&gt;
        &lt;img
          src="images/bg.jpg"
          loading="eager"
          alt="A mountain range with a sunrise, coming from behind the camera, reflecting off them."
        /&gt;
      &lt;/picture&gt;
      &lt;img
        src="images/bg.svg"
        loading="eager"
        alt="A low fidelity vector drawing of the mountain range"
        class="hero__mask"
      /&gt;
    &lt;/div&gt;
  &lt;/article&gt;
&lt;/main&gt;
</code></pre>
<div><h2>FYI</h2>
<p>I've made the roundel SVG an image (more on why we're using SVG later). You can <a href="https://reality-check.set.studio/masked-hero/images/roundel-text.svg">grab it here</a> if you want to build along and use an inline SVG for it.</p>
</div>
<p>One thing to note is I've added a visually hidden <code>&lt;h1&gt;</code> because there was no heading. There's headings later in the original composition, but you gotta start with at least a <code>&lt;h1&gt;</code>, so the rest of the page follows a logical hierarchy.</p>
<p>I've also used a <code>&lt;picture&gt;</code> element for the photographic background image because I wanted to <a href="https://piccalil.li/quick-tip/picture-as-a-progressive-enhancement/">provide modern image formats in a progressively enhanced manner</a>. The SVG mask is a standard <code>&lt;img&gt;</code> element.</p>
<h3>Keeping things simple: circular text</h3>
<p>The images of the surfers and the the "Surfer Stories" roundel presented a challenge in itself. It's tempting in these situations to flex out some CSS/JS techniques — <a href="https://css-irl.info/positioning-text-along-a-path-with-css/">like Michelle Barker’s smart approach to positioning text on a path</a> — but the repeated text provides no value to people that can't see the roundel itself.</p>
<p><img src="https://piccalil.b-cdn.net/images/reality-check/mh-roundels.jpg" alt="Two images of surfers, one surfing on a wave and the other, standing next to their board. A roundel sits next to them with round text, repeated, reading 'surfer stories'." /></p>
<p>Instead, I opted to treat the images and roundel as presentational(ish) and leverage a <code>&lt;figure&gt;</code> and <code>&lt;figcaption&gt;</code> to describe the content to people that can't see it. I also added a more appropriate, visually hidden label to make the link make more sense.</p>
<p>The text was achieved with the <a href="https://www.figma.com/community/plugin/762070688792833472/arc-bend-your-type">following Figma plugin</a>. I love how simple and inclusive this plugin is. Positioning text on a path is super fiddly in tools like Adobe Illustrator, so a plugin like this in Figma opens this technique up to more people.</p>
<p>The overall point I'm trying to make here though is keeping things simple where loads of effort would be wasted — for a largely presentational element — frees up your energy to focus on what's really important: making a website that works for everyone.</p>
<h2>Global CSS</h2>
<p>Ooof, I've just checked the word count and I've gone over a thousand words before I've even got near the CSS. There's lots of important things to get right before you style things up and the CSS is actually quite straightforward from here on in, so consider this as laying the foundation properly, to get things right for everyone.</p>
<p>Before the global CSS gets added, I pulled in <a href="https://piccalil.li/blog/a-more-modern-css-reset/">this CSS reset</a> to get some sensible defaults going. As always, I'm using the <a href="https://cube.fyi/">CUBE CSS methodology</a>.</p>
<p>Global CSS-wise, it's pretty slim</p>
<pre><code>:root {
  --font-base: 'DM Sans', -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui,
    helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, arial, sans-serif;
  --color-dark: #252525;
  --color-light: #ffffff;
  --color-mid: #555555;
  --gutter: 1.5rem;
}

body {
  background: var(--color-light);
  color: var(--color-dark);
  font-family: var(--font-base);
  margin: 0;
}

a:not([class]) {
  color: var(--color-mid);
}

a:not([class]):hover {
  text-decoration: none;
}

abbr {
  text-decoration: none;
  cursor: help;
}

figure {
  margin: 0;
}

:focus {
  outline: 2px solid currentColor;
  outline-offset: 0.25lh;
}
</code></pre>
<p>It's light because we're only focusing on the hero layout. Were we to be building the whole site, there'd be a lot more to add, but in the interests of keeping this article as slim as possible, we'll focus only on what appears on the page we have.</p>
<p>Everything is pretty self-explanatory, but the part I want to shed light on is the <code>&lt;abbr&gt;</code> styling. I used that for the <code>Est. 1961</code> section so I could expand "Est." as "Established". I like to add a <code>help</code> cursor to these elements too. Also, check out the <code>outline-offset</code>. Using the <a href="https://piccalil.li/blog/lh-units-are-cool/">new(ish) <code>lh</code> unit</a> is pretty handy to set that.</p>
<p>Lastly, <strong>always underline your links</strong>. I opted not to remove the link underlines as the original composition suggested.</p>
<h2>Utilities</h2>
<p>Traditionally, I tackle the C in CUBE CSS first: compositions, but today, it's utilities. Let's start with the <code>visually-hidden</code> utility.</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>Using <a href="https://piccalil.li/quick-tip/visually-hidden/">this quick tip</a>, elements that have this class visually disappear but are still accessible to screen readers etc.</p>
<p>Let's see at how the page currently looks:</p>
<p></p><p>To see this demo, head over to the <a href="https://reality-check.set.studio/masked-hero-globals-utils/">web version</a>.</p><p></p>
<h2>Compositions</h2>
<p>We've got two layouts to tackle. First, there's a negative grid which stacks items on top of each other.</p>
<h3>Negative grid</h3>
<pre><code>.negative-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, clamp(5rem, 33%, 10rem));
  transform: scale(83.333%);
  transform-origin: left;
}

.negative-grid &gt; * {
  transform: scale(120%);
  transform-origin: left;
}
</code></pre>
<p>You might have thought some negative margin would power this, but there's a simpler way: using the power of <code>transform</code>.</p>
<p>The trick is to upscale each item — <code>120%/1.2</code> in our case — then downscale the parent to <code>83.333%</code>. You calculate this by dividing <code>1</code> by the upscaled ratio (<code>1.2</code>). This means everything is the same size, but the items now stack naturally. Make sure you set a <code>transform-origin</code> though to make it as predictable as possible.</p>
<p>One thing to highlight here is none of the visual styles for the surfer photos or the roundel are being covered in this layout. <a href="https://cube.fyi/composition.html">Compositions are skeletal layouts</a> that focus only on positioning their children.</p>
<h3>Wrapper</h3>
<p>This one is really straightforward:</p>
<pre><code>.wrapper {
  margin-inline: auto;
  max-width: 1600px;
  padding-inline: var(--gutter);
}
</code></pre>
<p>It pushes content into the middle of the viewport. You could use relative units for the max width, or get more creative about the sizing like <a href="https://piccalil.li/quick-tip/use-css-clamp-to-create-a-more-flexible-wrapper-utility/">this article</a>, but in this context, we're keeping it simple.</p>
<p>The page is not looking <em>great</em> yet, but it's worth us checking in to see how things are looking still:</p>
<p></p><p>To see this demo, head over to the <a href="https://reality-check.set.studio/masked-hero-globals-utils-comps/">web version</a>.</p><p></p>
<h2>Blocks</h2>
<p>Let's get stuck into the good stuff now: making stuff look sweet!</p>
<h3>Hero</h3>
<p>Before we tackle the layout of the <code>hero</code>, I just want to remind you of the planning I did before:</p>
<p><img src="https://piccalil.b-cdn.net/images/reality-check/mh-layout-planning.jpg" alt="A clip of the original composition with lines drawn over the top and annotations as I plan the layout of the page" /></p>
<p>The wrapper (magenta lines) is already accounted for and is already present in our <code>hero</code> markup. All we need to focus on now is:</p>
<ol>
<li>The grid layout</li>
<li>Layering the backgrounds</li>
<li>Masking the background photo</li>
</ol>
<p>The other point of focus is what the hell do we do on small viewports? I think slightly modifying the display order (tread carefully with this) and creating a right-hand gutter that reveals the mountains photo is a pretty sensible approach.</p>
<p><img src="https://piccalil.b-cdn.net/images/reality-check/mh-polypane.jpg" alt="Three viewports in Polypane demonstrating what I described above" /></p>
<p>Let's tackle that first, building mobile-up.</p>
<pre><code>.hero {
  position: relative;
  padding-block: 2rem;
  text-transform: uppercase;
}

.hero p {
  max-width: 40ch;
}

.hero a {
  text-transform: none;
}
</code></pre>
<p>I'm letting inheritance do its job with <code>text-transform</code> because most of the content is uppercase. I then revert that for the links (I'd personally keep it all consistent).</p>
<p>Setting a <code>max-width</code> on the paragraphs is to account for those weird viewport sizes that are not quite narrow. The grid takes care of them in most cases though.</p>
<p>The outer <code>hero</code> block is a relative parent and the grid is on <code>hero__inner</code>, so let's do the smaller viewport version first.</p>
<pre><code>.hero__inner {
  display: grid;
  grid-template-columns: 80% 20%;
  grid-template-rows: 1fr auto auto;
  gap: var(--gutter) 0;
  position: relative;
  z-index: 1;
}

.hero__inner &gt; * {
  grid-column: 1;
}
</code></pre>
<p>This is still a three row grid, but now I'm setting two columns, split as 80/20. You could make the <code>20%</code> column a fixed width if you wanted then <code>1fr</code> on the first column to fill the space.</p>
<p>The reason I've added <code>z-index</code> is because:</p>
<ol>
<li>I want to create a stacking context to better control the illustrative text later</li>
<li>I want the grid to sit above the soon to be absolutely positioned background elements</li>
</ol>
<p>All the direct children of the grid are added to the first column here too because as it stands, the second column is a gutter.</p>
<p>Let's map out the rows and other details.</p>
<pre><code>.hero__meta {
  grid-row: 1;
  color: var(--color-mid);
}

.hero__decor-text {
  grid-row: 1;
  grid-column: 1/3;
  margin-top: 3rem;
  z-index: -1;
}

.hero__content {
  grid-row: 2;
}

.hero__action {
  grid-row: 3;
}
</code></pre>
<p>The main thing to touch on here is the <code>hero__decor-text</code> element. Everything is in the first column, but I wanted this to "bleed out" into the gutter. The reason it's <code>grid-column: 1/3</code> and not <code>grid-column: 1/2</code> is because if you want an item to span over multiple columns, you need to set the end to be the next column's <a href="https://web.dev/learn/css/grid?continue=https%3A%2F%2Fweb.dev%2Flearn%2Fcss%23article-https%3A%2F%2Fweb.dev%2Flearn%2Fcss%2Fgrid#grid_lines">grid line</a>. Column three doesn't exist, but its grid line does.</p>
<div><h2>FYI</h2>
<p>You're not alone if you find this confusing by the way. I also find it confusing as hell. I'd recommend reading <a href="https://web.dev/learn/css/grid">this lesson of the Learn CSS course</a>.</p>
</div>
<p>The negative <code>z-index</code> is to put the decorative text behind proper content. We can do this because we're in a new <a href="https://web.dev/learn/css/z-index">stacking context</a>, thanks to our <code>.hero__inner</code> grid layout.</p>
<p>That's the small viewport layout done, so let's get the small viewport background done.</p>
<pre><code>.hero__bg {
  position: absolute;
  inset: 0;
  z-index: 0;
}

.hero__bg :is(img, picture) {
  width: 100%;
  height: 100%;
  object-fit: cover;
  position: absolute;
  inset: 0;
}

.hero__bg picture {
  z-index: 1;
  clip-path: inset(0 0 0 80%);
}
</code></pre>
<p>The <code>.hero__bg</code> element is a wrapping container for both backgrounds. Because <code>.hero</code> is a relative parent, we can safely set the background to be absolutely positioned and fill the whole parent with <code>inset: 0</code>. Setting <code>z-index: 0</code> sits the images behind the content.</p>
<p>Next up, we need all the images to fill the <code>.hero__bg</code>, so again, we can use absolute positioning. Setting <code>object-fit: cover</code> prevents them from squishing and crops them out for us, accordingly.</p>
<p>Lastly, the mask that hides most of the photographic mountains. We know we have 20% of the layout as gutter, so the <code>clip-path</code> can take up 80% of the image. Job done!</p>
<p>Let's now expand the layout for larger viewports.</p>
<pre><code>@media (min-width: 985px) {
  .hero__inner {
    padding-block: 4rem;
    gap: 0 var(--gutter);
    grid-template-columns: 1fr 2fr 1fr;
    grid-template-rows: 1fr 2fr 1fr;
  }

  .hero__meta {
    grid-column: 3;
    grid-row: 1;
  }

  .hero__content {
    grid-column: 1;
    grid-row: 1;
  }

  .hero__decor-text {
    grid-column: 1/4;
    grid-row: 4/1;
  }

  .hero__action {
    grid-column: 3;
    grid-row: 3;
  }

  .hero__bg picture {
    clip-path: inset(0 40% 0 40%);
  }
}
</code></pre>
<p>I can hear some of you thinking “Andy, using media queries for layout?!?!”. Sometimes you just gotta do it — especially for very specific layouts like this one.</p>
<p>There's a balance with these things. Sure, I could spend <em>hours</em> working out an <a href="https://every-layout.dev/">Every Layout-like layout</a>, but I think this setup is also pretty flexible. If I were having to roll out multiple breakpoints, I'd start to re-work the visual design, but I don't need to here.</p>
<p>I guess the message is even I use media queries and specific layouts sometimes, so if you're dialled in on being the <a href="https://buildexcellentwebsit.es/">browser's mentor, not it's micromanager</a>: be easy on yourself and choose the path of least resistance where you need to.</p>
<p>In terms of our above code, it's pretty self explanatory based on what we've already covered earlier in the article. All we're doing is expanding and spanning multiple columns/rows and placing items in specific places. Because it was planned out before the build, it was quick and easy with grid.</p>
<h3>Avatar</h3>
<p>This is pretty cut and dry CSS:</p>
<pre><code>.avatar {
  border-radius: 100%;
  border: 0.25em solid var(--color-light);
  aspect-ratio: 1/1;
  object-fit: cover;
}
</code></pre>
<p>We're making them round with a square aspect ratio. The <code>object-fit</code> property is making sure images don't get squished. It's more <a href="https://ishadeed.com/article/defensive-css/">defensive CSS</a> than anything else because even though our images are square, they might not be in the longer term (were this a real project), so this little rule future proofs things a bit.</p>
<h2>Roundel</h2>
<p>Right, we're on the last part. Sorry, this has been a real <em>deep dive</em> 😅</p>
<p>Let's first get everything looking nice.</p>
<pre><code>.roundel {
  position: relative;
  aspect-ratio: 1/1;
  background: var(--color-dark);
  color: var(--color-light);
  padding: 0.5em;
  border-radius: 100%;
}

.roundel img {
  width: 100%;
  height: 100%;
  transform-origin: center;
  animation: roundel-rotate 5s linear 0s infinite;
  animation-play-state: paused;
}

.roundel svg {
  width: 2rem;
  height: 2rem;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
</code></pre>
<p>The <code>.roundel</code> itself is the black circular element which is also a relative parent. Just like the <code>.avatar</code>, we're using <code>aspect-ratio</code> to make it a square, before using <code>border-radius</code> to make that a circle.</p>
<p>Now that we have a relative parent, we can again, use absolute positioning to position it's children. Just like the <code>.hero</code> images, it's a pretty safe context for that — mainly because in our <code>.roundel</code> context, it's all pretty predictable.</p>
<div><h2>FYI</h2>
<p>You might be thinking "where the hell is the sizing for this and the avatar?". We're letting the layout size our blocks rather than specifying a block's size and space specifically.</p>
<p>This results in resilient user interfaces that are less prone to breakages.</p>
<p>Read more about this approach <a href="https://cube.fyi/composition.html">here</a>.</p>
</div>
<p>Because I opted to artwork the circular text, it's a case of letting <code>.roundel img</code> fill the parent. There's an animation to rotate it too, but by default, that state is paused.</p>
<p>The <code>.roundel svg</code> is the little arrow icon. Using a combo of positioning and transform, we stick that right in the middle of the <code>.roundel</code>.</p>
<pre><code>@keyframes roundel-rotate {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

:hover &gt; .roundel img {
  animation-play-state: running;
}
</code></pre>
<p>The animation is quite simple because we know it needs to rotate from <code>0deg</code> to <code>360deg</code>. CSS animation syntax gives us a handy <code>from</code> and <code>to</code> to make that quite easy to understand.</p>
<p>The <code>:hover</code> selector waits for the <code>.roundel</code>'s parent to be hovered, rather than the element itself. This means that if any of its siblings are hovered, it'll trigger the animation.</p>
<h2>Wrapping up</h2>
<p>That's it, we are done! Let's check in how it looks.</p>
<p></p><p>To see this demo, head over to the <a href="https://reality-check.set.studio/masked-hero/">web version</a>.</p><p></p>
<p>I feel like this one has been considerably simpler than the other two editions, but it has been a great opportunity for me to show you the pre-coding stuff and also — most importantly — the value and trade-offs of simplifying a build as much as you can.</p>
        
        ]]></description>
        
      </item>
    
      <item>
        <title>Reality Check #2: Building out a fancy 404 page from Layers</title>
        <link>https://piccalil.li/blog/reality-check-2-building-out-a-fancy-404-page/?ref=reality-check-category-rss-feed</link>
        <dc:creator><![CDATA[Andy Bell]]></dc:creator>
        <pubDate>Wed, 15 Nov 2023 00:00:00 GMT</pubDate>
        <guid isPermaLink="true">https://piccalil.li/blog/reality-check-2-building-out-a-fancy-404-page/?ref=reality-check-category-rss-feed</guid>
        <description><![CDATA[<p>Ok, I promised a more complex example on the <a href="https://piccalil.li/blog/reality-check-1-building-out-a-furniture-site-from-dribbble/">last edition of Reality Check</a>, and here we go. The trick here though is it <em>looks</em> complex, but we’re gonna lean into the power of CSS to make it pretty darn easy.</p>
<h2>This edition’s project</h2>
<p>I’ve selected <a href="https://layers.to/layers/clou1aiq40004kw0b79pfjajz">404 Error Page (Mobile)</a> by <a href="https://layers.to/davatrs">Davi Pribadi</a> on Layers. You might look at this and think, that it’s tricky. It certainly <em>can</em> be tricky, but we’re gonna make it easy and you’re gonna learn some CSS tricks.</p>
<p><img src="https://piccalil.b-cdn.net/images/reality-check/image.png" alt="A mobile chrome with a website design in it. The page has a massive 404 with a curved content container overlaying it.
The content reads &quot;Oops, page not found please go back to the homepage&quot;. This is followed by a gradient purple button, labeled &quot;Homepage&quot;." title="&lt;a href=&quot;https://layers.to/layers/clou1aiq40004kw0b79pfjajz&quot;&gt;404 Error Page (Mobile)&lt;/a&gt; by &lt;a href=&quot;https://layers.to/davatrs&quot;&gt;Davi Pribadi&lt;/a&gt;" /></p>
<p>I’m, as usual, going to make some design alterations though. I’m not a huge fan of the colours, the content, or the inner shadow, so I’ve updated those. The overall principle of that bleed-out heading that’s got a gradient, and set behind the curved content container remains though, because this is where the tricks will be learned.</p>
<p><img src="https://piccalil.b-cdn.net/images/reality-check/CleanShot-2023-11-14-at-17.17.35@2x-scaled.jpg" alt="A Figma composition that has two artboards: a '@min' that is mobile-like and a '@max' that is desktop-like.
There's a navy to orange '404' on both with a curved content area, overlaying it.
The content reads 'We’re sorry but the page you’re looking for can’t be found.' and the gradient button reads 'Go back to homepage'." /></p>
<h2>HTML first, always</h2>
<p>Here’s everything inside the <code>&lt;body&gt;</code> element. HTML-wise, it’s really straightforward.</p>
<pre><code>&lt;main&gt;
  &lt;h1 class="mega-heading"&gt;404&lt;/h1&gt;
  &lt;div class="curved-container"&gt;
    &lt;div class="curved-container__content flow"&gt;
      &lt;p&gt;We’re sorry but the page you’re looking for can’t be found.&lt;/p&gt;
      &lt;a class="button" href="/"&gt;Go back to homepage&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/main&gt;
</code></pre>
<p>You might be wondering why there’s an extra <code>&lt;div&gt;</code>: the <code>.curved-container__content</code> element. It’ll make sense later, I promise. The takeaway here is we want to keep the HTML as simple and semantic as possible, which is what we’ve got.</p>
<h2>Global CSS</h2>
<p>Just like the <a href="https://piccalil.li/blog/reality-check-1-building-out-a-furniture-site-from-dribbble/">previous edition</a>, we’re going to be building with the <a href="https://cube.fyi/">CUBE CSS principles</a>. That starts with styling as much as you can, globally.</p>
<p>It makes sense to start with the global variables that give us some nice consistency. The main part of those variables is the fluid type and fluid space scale, using <a href="https://utopia.fyi/">Utopia</a>.</p>
<p>Fluid type and fluid space allow us to create truly responsive designs that respond to the viewport, rather than forcing rigid, catch-all sizes. We’d definitely recommend that you read up on the <a href="https://utopia.fyi/">Utopia site</a>.</p>
<pre><code>:root {
  --font-base: Inter, Segoe UI, Roboto, Helvetica Neue, Arial, sans-serif;
  --font-display: 'Rubik Mono One', monospace;
  --color-dark: #363950;
  --color-light: #ffffff;
  --color-light-shade: #f3f3f3;
  --color-primary: #b25d66;
  --gradient-primary: linear-gradient(111deg, #002846 0%, #ff7373 82.7%, #ffaf7b 97.2%);
  --gradient-secondary: linear-gradient(180deg, #a25863 0%, #373950 100%);
  --size-step-0: clamp(1rem, 0.9592rem + 0.2041vw, 1.125rem);
  --size-step-1: clamp(1.2rem, 1.1022rem + 0.4888vw, 1.4994rem);
  --size-step-2: clamp(1.44rem, 1.2576rem + 0.9122vw, 1.9988rem);
  --size-step-3: clamp(1.7281rem, 1.4224rem + 1.5286vw, 2.6644rem);
  --size-step-4: clamp(2.0738rem, 1.5911rem + 2.4133vw, 3.5519rem);
  --size-step-5: clamp(2.4881rem, 1.7545rem + 3.6684vw, 4.735rem);
  --size-mega: 45vw;
  --space-s: clamp(1rem, 0.9592rem + 0.2041vw, 1.125rem);
  --space-m: clamp(1.5rem, 1.4388rem + 0.3061vw, 1.6875rem);
  --space-l: clamp(2rem, 1.9184rem + 0.4082vw, 2.25rem);
  --space-xl: clamp(3rem, 2.8776rem + 0.6122vw, 3.375rem);
  --gutter: var(--space-m);
}
</code></pre>
<p>The tricky part of this article though, is although global styles would be extremely useful in the wider context of the website this 404 page lives in, I don’t want to make this article longer than it needs to be. Still, let’s just have a look at the baseline styles, which build on top of <a href="https://andy-bell.co.uk/a-more-modern-css-reset/">this CSS reset</a>.</p>
<pre><code>body {
  font-family: var(--font-base);
  font-size: var(--size-step-0);
  background: var(--color-light-shade);
  color: var(--color-dark);
  overflow-x: hidden;
}

h1 {
  font-size: var(--size-step-5);
}

h2 {
  font-size: var(--size-step-4);
}

h3 {
  font-size: var(--size-step-3);
}

:is(h1, h2, h3) {
  max-width: 30ch;
}

:focus {
  outline-offset: 4px;
  outline-color: var(--focus-color, var(--color-dark));
}

p {
  max-width: 60ch;
}

a {
  color: currentColor;
}

::selection {
  background: var(--color-dark);
  color: var(--color-light);
}
</code></pre>
<p>It’s mainly global typography settings to make <a href="https://set.studio/some-simple-ways-to-make-content-look-good/">prose content read nicely</a>. Even though this stuff doesn’t feature much on our build, it’s useful to add in.</p>
<p>The one part I do want to draw your attention to though is the <code>overflow-x: hidden</code> rule on the <code>body</code>. Because our “404” heading bleeds out of the edges of our viewport, we need to conceal that. It won’t affect anything for the user, but if <a href="https://uxdesign.cc/position-stuck-96c9f55d9526">we wanted to use <code>position: sticky</code>, we’ll have problems</a>, so I thought I’d pre-warn you.</p>
<h2>Compositions (layout)</h2>
<p>We’ve only got one composition in this build, the <code>flow</code> utility, which adds space to the top of sibling elements.</p>
<pre><code>.flow &gt; * + * {
  margin-top: var(--flow-space, 1em);
}
</code></pre>
<p>You’ll spot that it is already on our <code>.curved-container__content</code> element and if you’re interested in how it works, <a href="https://andy-bell.co.uk/my-favourite-3-lines-of-css/">check out this article</a>.</p>
<p>Let’s check in how it looks:</p>
<p></p><p>To see this demo, head over to the <a href="https://reality-check.set.studio/404-globals/">web version</a>.</p><p></p>
<p>Only global CSS and layouts are in, but it’s taking shape.</p>
<h2>Blocks (components)</h2>
<p>Let’s start with the most interesting block: that big ol’ “404” heading. We’ve got an accurately named class of <code>mega-heading</code> on it, so let’s apply some styles:</p>
<pre><code>.mega-heading {
  width: calc(100vw + 0.2ch);
  font-family: var(--font-display);
  letter-spacing: -0.1ch;
  font-size: var(--size-mega);
  line-height: 1;
  margin-inline-start: 50%;
  transform: translateX(-50%);
  color: transparent;
  background-color: var(--color-dark);
  background-image: var(--gradient-primary);
  background-clip: text;
  -webkit-background-clip: text;
}
</code></pre>
<p>Big ol’ block of CSS right? Fear not, let’s break it down:</p>
<ol>
<li>We want the heading to be more than full bleed, so using <code>calc</code>, it’s <code>100%</code> plus <code>0.2</code> the width of a <code>0</code> character (a <code>ch</code> unit) in the font’s rendered size. The text is <em>massive</em>, so that equates to a decent bleed out.</li>
<li>Still using a <code>ch</code> unit, we compress the letters a bit with <code>letter-spacing</code>.</li>
<li>Even though we’re using a <a href="https://fonts.google.com/specimen/Rubik+Mono+One">mono font</a> and therefore can better predict font sizing, I’m sorry to say that <code>45vw</code> is a <a href="https://en.wikipedia.org/wiki/Magic_number_(programming)">magic number</a> (but at least we made it a variable). It’s just one of those things you gotta do by eye I’m afraid. The saving grace is in this instance we know that our heading is going to be “404”, so we have a modicum of control. If this was to be more of a shared component, I’d recommend looking into the “fit text” pattern, along with some <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/white-space"><code>white-space</code> treatment</a>.</li>
<li>We completely center align the heading with a combination of <code>50%</code> margin and <code>-50%</code> transform. This centers the element even when it’s as wide as, or wider than the viewport.</li>
</ol>
<h3>Adding the gradient</h3>
<p>The gradient part needs a bit more explanation, so let’s break out of the breakdown.</p>
<p>First up, we’re using <code>--gradient-primary</code> from our global variables, but setting it as the background. This is because we’re going to use the text as a mask with <code>background-clip: text</code>, which uses the shape of the text to clip the background image / colour.</p>
<div><h2>FYI</h2>
<p>You’ll also notice we are using background-color too. This is an insurance policy for if the gradient fails on a low powered device or if someone accidentally applies transparency (alpha) to the gradient, being that it’s a global variable. It’s all about defensive CSS!</p>
</div>
<p>The only way this gradient or colour is going to show up is if we make the text transparent in colour with <code>color: transparent</code>. Don’t panic though, because it renders all good in high contrast mode.</p>
<p><img src="https://piccalil.b-cdn.net/images/reality-check/high-contrast-mode-404.jpg" alt="A clip from Windows high contrast mode that shows the 404 heading and the content as white, with the button being a pale blue colour with only a border and no background." /></p>
<h3>Adding the curved container block</h3>
<p>This is the part of the page that contains the short paragraph and button. There’s a nice curve on the top too.</p>
<p>You’ll also notice that this content appears to sit above the “404” and might rightly be thinking “damn this is gonna be complicated”.</p>
<p>One thing I can’t stress enough is that <strong>CSS stuff only gets complicated if you make it complicated.</strong> CSS is outrageously powerful if you learn how it works at the core. Let me teach you some of that now.</p>
<p>Firstly, the curve. We could over-complicate this by utilising <code>clip-path</code>, but why bother? Let’s add a nice, simple SVG element to our HTML, which now looks like this.</p>
<pre><code>&lt;main&gt;
  &lt;h1 class="mega-heading"&gt;404&lt;/h1&gt;
  &lt;div class="curved-container"&gt;
    &lt;svg
      xmlns="http://www.w3.org/2000/svg"
      fill="none"
      viewBox="0 0 1440 105"
      preserveAspectRatio="none"
      aria-hidden="true"
      focusable="false"
    &gt;
      &lt;path d="M1440 97.315C716.52-78.932 182.417 23.879 0 97.315V105h1440v-7.685Z" /&gt;
    &lt;/svg&gt;
    &lt;div class="curved-container__content flow"&gt;
      &lt;p&gt;We’re sorry but the page you’re looking for can’t be found.&lt;/p&gt;
      &lt;a class="button" href="#"&gt;Go back to homepage&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/main&gt;
</code></pre>
<p>A couple of bits to note about this SVG element:</p>
<ol>
<li>We’ve got a <code>preserveAspectRatio="none"</code> attribute on there because we want this thing to “squish” into shape later.</li>
<li>We’ve got <code>aria-hidden="true"</code> and <code>focusable="false"</code> on there because it’s purely decorative, so we don’t want assistive tech to accidentally interact with it.</li>
</ol>
<p>Now, let’s add some CSS:</p>
<pre><code>.curved-container {
  font-family: var(--font-display);
  font-size: var(--size-mega);
  background: var(--color-light);
  position: relative;
  margin-block-start: -0.45ex;
}
</code></pre>
<p>Now, you might be thinking “what the hell are you doing setting such massive text?”. It’s a good question! Remember how we want to do things as simple as possible right? The simplest way to position everything is to know exactly how big the text we’re covering up is. Now we’ve got that in place, we can use <a href="https://every-layout.dev/rudiments/units/">relative units</a> in a predictable manner, knowing they are explicitly related to the text we’re partially concealing.</p>
<p>In fact, the first place we use that is positioning this container over part of the text. Because the container’s font size is the same as the “404” heading’s, we can use an <code>ex</code> unit — the height of the <code>x</code> character — to add negative margin to our curved container.</p>
<pre><code>.curved-container svg {
  font: inherit;
  fill: var(--color-light);
  display: block;
  width: 100%;
  height: 0.2ex;
  position: absolute;
  bottom: calc(100% - 1px);
  filter: drop-shadow(0px -10px 18px rgb(0 0 0 / 25%));
  z-index: 0;
}
</code></pre>
<p>Again, keeping things super simple, we’re using <code>font: inherit</code> here to keep the font trickery going. This time, we’re using an <code>ex</code> unit to set the height of the curve. This will now always be relative to the height of the heading that it partially conceals. Handy, right?</p>
<div><h2>FYI</h2>
<p>You might think that using viewport units would be a good idea to set height. You’re right, it would be, but the problem is you <a href="https://viewports.fyi/">don’t know what viewport conditions the site will render in</a>, so viewport units can cause havoc.</p>
<p><img src="https://piccalil.b-cdn.net/images/reality-check/problem-with-using-dvh.jpg" alt="A clip of the curved container completely concealing the heading because the viewport is very narrow and very tall" /></p>
</div>
<p>In terms of positioning, we’re going all in with <code>position: absolute</code> because we don’t want this curve to be part of the curved container’s rendered size. Using <code>bottom</code>, we’re positioning the bottom edge of the <code>&lt;svg&gt;</code> to the top of the curved container, by setting the value to <code>100%</code>.</p>
<p>Unfortunately, thanks to the new IE — AKA mobile Safari, <a href="https://open-web-advocacy.org/">which is forced on all iOS users</a> — we have to <code>calc</code> a pixel off that value, or a hairline crack will appear…</p>
<p>Another point I want to touch on here is the use of the drop shadow filter. The reason we use that instead of <code>box-shadow</code> is because drop shadow will follow the shape of the <code>&lt;path&gt;</code>, whereas <code>box-shadow</code> does exactly what it says on the tin: applies shadow to the box.</p>
<p><img src="https://piccalil.b-cdn.net/images/reality-check/drop-shadow-vs-box-shadow.jpg" alt="A side by side comparison of drop shadow vs box shadow" /></p>
<p>Drop shadow on the left and box shadow on the right</p>
<p>One thing we absolutely do need to consider is high contrast users. Luckily, there’s a media query we can use.</p>
<pre><code>@media (prefers-contrast: more) {
  .curved-container svg {
    display: none;
  }
}
</code></pre>
<p>All we’re doing here is determining if a user prefers high contrast and if they do, we hide the curve itself, so it doesn’t interfere with the text. This is because the <code>fill</code> value of the <code>svg</code> will still be honoured, even in high contrast mode, so it’s best to just remove the element, visually.</p>
<p>Right, let’s style up the content in this container.</p>
<pre><code>.curved-container__content {
  font-family: var(--font-base);
  font-size: var(--size-step-1);
  text-align: center;
  padding-block: var(--space-m);
  position: relative;
  z-index: 1;
  background: var(--color-light);
}
</code></pre>
<p>Remember how we set the text to be massive to assist with positioning? Well, we need to reset that here to make sure text doesn’t actually render massive.</p>
<p>The <code>background</code> application means we can hide the drop shadow spillage from our curve and setting <code>z-index: 1</code>, we’re making sure our content layer always sits above the actual curve. This is also why we added that extra <code>&lt;div&gt;</code> earlier.</p>
<p>Let’s add the last couple of bits for our curved container:</p>
<pre><code>.curved-container__content &gt; * {
  max-width: 30ch;
  margin-inline: auto;
}

@media (min-width: 800px) {
  .curved-container__content {
    padding-block-start: 0;
  }
}
</code></pre>
<p>The first part reduces the width of the content by its own character width, then pushes it into the center of the container with <code>margin-inline: auto</code>. We already have <code>text-align</code> set on the <code>curved-container__content</code> so don’t need to set that again. Without <code>margin-inline: auto</code>, the text would be center-aligned, but not horizontally centered, thanks to the max width. Now we have both.</p>
<p>The last part is a little visual tweak. This is where I see media queries being the most useful, rather than <a href="https://buildexcellentwebsit.es/">being used for layout changes</a> (where you can help it of course).</p>
<p>All we’re doing is removing the top padding where space allows. This is because thanks to the massive text, the size of the curve will be big enough to give the illusion of padding. The curve isn’t big enough to do that on smaller viewports.</p>
<h3>Making the curved container fill available space</h3>
<p>We’ve got a problem. Because our content is short, the background and even part of the “404” heading can show up under our content. Let’s fix it with the power of flexbox.</p>
<p></p><p>To see this demo, head over to the <a href="https://reality-check.set.studio/404-pre-flexbox/">web version</a>.</p><p></p>
<p>First up, we need to adding the following to our body:</p>
<pre><code>body {
  /* All the other CSS */
  display: flex;
  flex-direction: column;
}
</code></pre>
<p>Next, add this rule for the <code>&lt;main&gt;</code>:</p>
<pre><code>main {
  display: flex;
  flex-direction: column;
  flex: auto;
}
</code></pre>
<p>This makes the <code>&lt;main&gt;</code> stretch to fill the available space in the <code>body</code> (thanks to <code>flex: auto</code>) and will then allow a child element to do the same.</p>
<p>Lastly, we add this to the curved container:</p>
<pre><code>.curved-container {
  /* The rest of the CSS */
  flex: auto;
}
</code></pre>
<p>The curved container will now fill any available space left in the <code>&lt;main&gt;</code> element. Flexbox is the best.</p>
<p></p><p>To see this demo, head over to the <a href="https://reality-check.set.studio/404-post-flexbox/">web version</a>.</p><p></p>
<h3>Adding the button component</h3>
<p>All the really hairy stuff is done! All we’ve got left to do is make our button look lovely.</p>
<pre><code>.button {
  display: inline-block;
  padding: 0.7em 1.2em 0.85em 1.2em;
  background: var(--color-dark);
  background-image: var(--gradient-secondary);
  color: var(--color-light);
  font-weight: 700;
  text-decoration: none;
  border-radius: 0.5em;
  line-height: 1;
  border: 4px solid var(--color-primary);
}
</code></pre>
<p>It’s a bit different to <a href="https://layers.to/layers/clou1aiq40004kw0b79pfjajz">the original on Layers</a>, but I wanted the colours to match the heading better.</p>
<p>Next, some interactive states:</p>
<pre><code>.button:hover {
  background-size: 150% 150%;
}

.button:active {
  transform: scale(99%);
}
</code></pre>
<p>Firstly, we’re making the gradient bigger on hover, so it appears to get lighter 😎. You could set a different gradient on hover, but meh, why bother when you can tweak the gradient’s canvas size? Always be keeping things simple, friends.</p>
<p>Lastly, this is one of my favourite tricks in CSS. Ideally, you want a button to interact to being pressed, so one way to do that is make it appear to be squidgy. Using <code>transform: scale(99%)</code> does exactly that!</p>
<p></p><p>To see this demo, head over to the <a href="https://reality-check.set.studio/404/">web version</a>.</p><p></p>
<h2>Wrapping up</h2>
<p>I think the key takeaway from this edition of <a href="https://piccalil.li/category/reality-check/">Reality Check</a> is even if a design looks like it’s gonna be tricky, spend plenty of time planning and thinking not “how am I going to code this?”, but instead “what is CSS already giving me to make this work?”.</p>
<p>The power CSS gave us in this build is <a href="https://every-layout.dev/rudiments/units/">relative units</a>, which when you think slightly out of the box with them, are unbelievably powerful and allow you to truly express yourself in design.</p>
        
        ]]></description>
        
      </item>
    
      <item>
        <title>Reality Check #1: Building out a furniture site from Dribbble</title>
        <link>https://piccalil.li/blog/reality-check-1-building-out-a-furniture-site-from-dribbble/?ref=reality-check-category-rss-feed</link>
        <dc:creator><![CDATA[Andy Bell]]></dc:creator>
        <pubDate>Wed, 18 Oct 2023 00:00:00 GMT</pubDate>
        <guid isPermaLink="true">https://piccalil.li/blog/reality-check-1-building-out-a-furniture-site-from-dribbble/?ref=reality-check-category-rss-feed</guid>
        <description><![CDATA[<p>Welcome to <a href="https://piccalil.li/category/reality-check/">Reality Check</a>, a new blog series where I take some work from <a href="https://dribbble.com/">Dribbble</a> or <a href="https://layers.to/">Layers</a>, then refactor the design into something that will work really well on the web.</p>
<p>After that, I go ahead and build it — breaking down the steps — to show you how to build in an efficient, truly responsive and progressively enhanced manner. It’ll give you a look behind the curtain at how we do things <a href="https://set.studio">in the studio</a>.</p>
<h2>Why am I doing this?</h2>
<p>So often, work on Dribbble and Layers is visually stunning, but how it will actually work in the browser hasn’t been considered. This isn’t just exclusive to Dribbble and Layers too because designers handing off static Figma files to developers seems good in theory, but often, developers will be stuffing codebases full of magic numbers and hacks to get a “pixel perfect” output in the browser.</p>
<p>The problem with this approach is you <a href="https://viewports.fyi/">have no idea what context your users will be visiting your site in</a> and <a href="https://buildexcellentwebsit.es/">being the browser’s mentor, not it’s micromanager</a> is a guaranteed way to build truly responsive front-ends that work for everyone. We’ll show you how to do that in each edition of this series.</p>
<h2>This edition’s project</h2>
<p>I’ve chosen <a href="https://dribbble.com/shots/22833315-Furniture-e-commerce-website">Furniture e-commerce website</a> by <a href="https://dribbble.com/hrvojekraljevic">Hrvoje Kraljevic</a>. It’s a fairly straightforward one to start with, but it’s even simpler when you approach it flexibly.</p>
<p><img src="https://piccalil.b-cdn.net/images/reality-check/image-1.png" alt="A 50/50 split layout that shows a cropped chair on the left, followed by page content on the right." title="Original &lt;a href=&quot;https://dribbble.com/shots/22833315-Furniture-e-commerce-website&quot;&gt;Dribbble shot&lt;/a&gt; by &lt;a href=&quot;https://dribbble.com/hrvojekraljevic&quot;&gt;Hrvoje Kraljevic&lt;/a&gt;" /></p>
<p>As you can see, it’s a 50/50 split layout that also has a vertical split on the right-hand side content section. The problem is, this only has a desktop design. The first thing we’re going to do is mock-up a similar version in Figma, including a minimum width viewport.</p>
<p><img src="https://piccalil.b-cdn.net/images/reality-check/CleanShot-2023-10-18-at-11.42.08@2x-scaled.jpg" alt="A Figma screenshot that shows the original image, followed by two artboards named @min and @max.
The design has been been refactored to be more responsive-friendly, along with a more sensible type scale." title="Our Figma comp tackles the smallest and largest viewports. I’ve also change the type scale and reduced padding in places." /></p>
<p>This doesn’t look exactly the same — mainly because I don’t know what fonts were used, and I don’t have access to the image asset. I’ve also levelled things out a little bit so we can use fluid type and space and also free Google Fonts, in case you wanted to give this a go yourself. The main change I wanted to make was that the typography should follow a type scale, to improve the overall rhythm.</p>
<p>Still, it’s pretty darn close, so let’s get cracking with the build!</p>
<h2>HTML first, always</h2>
<p>It’s important to get the foundation of our build in the best place possible with semantic HTML. Using semantic HTML has so many benefits, but some key ones:</p>
<ol>
<li>If nothing but the HTML arrives, the content will make complete sense to the user because the browser has its own user agent styles</li>
<li>Screen readers and other assistive tech will have a much easier time describing content to users</li>
<li>It benefits SEO</li>
<li>Users who use tools such as Reader Mode on Safari, will have a much better experience</li>
</ol>
<p>In short: if you mark up the page using only <code>&lt;div&gt;</code> elements, you might be making <em>your</em> life easier (although I’ve never subscribed to that logic), but you will be making the experience of your users much, much worse.</p>
<p>Here’s all the HTML of the page, within the <code>&lt;body&gt;</code>:</p>
<pre><code>&lt;main class="switcher wrapper"&gt;
  &lt;picture class="decorative-image"&gt;
    &lt;source
      srcset="images/graphic-shallow.jpg 1x, images/graphic-shallow-2x.jpg 2x"
      media="(max-width: 600px)"
    /&gt;
    &lt;source srcset="images/graphic.jpg 1x, images/graphic-2x.jpg 2x" /&gt;
    &lt;img src="images/graphic-shallow.jpg" alt="" loading="lazy" /&gt;
  &lt;/picture&gt;
  &lt;div class="content repel" data-repel-variant="vertical"&gt;
    &lt;header class="site-head repel"&gt;
      &lt;p class="site-head__name"&gt;spaziovisia&lt;/p&gt;
      &lt;p&gt;Elevate your space&lt;/p&gt;
    &lt;/header&gt;
    &lt;article class="flow"&gt;
      &lt;h1&gt;A tribute to ancient handicrafts&lt;/h1&gt;
      &lt;p&gt;
        The materials are simple and completely natural, the internal structure of the
        stem consists of hundreds of cavities, which makes it strong and light.
      &lt;/p&gt;
      &lt;p&gt;
        &lt;a class="icon-link" href="#"&gt;
          &lt;span&gt;Explore Kettal collection&lt;/span&gt;
          &lt;svg aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 25" height="1.5ex" width="auto"&gt;
            &lt;g&gt;
              &lt;path
                fill="currentColor"
                d="m16.172 11.5-5.364-5.364 1.414-1.414L20 12.5l-7.778 7.778-1.414-1.414 5.364-5.364H4v-2h12.172Z"
              /&gt;
            &lt;/g&gt;
          &lt;/svg&gt;
        &lt;/a&gt;
      &lt;/p&gt;
    &lt;/article&gt;
  &lt;/div&gt;
&lt;/main&gt;
</code></pre>
<p>Let’s pick out some key areas.</p>
<ol>
<li>We’re using <code>&lt;div&gt;</code> elements, but they are dividing the content to make CSS layout easier.</li>
<li>The semantic <code>&lt;main&gt;</code>, <code>&lt;header&gt;</code> and <code>&lt;article&gt;</code> elements structure the content, along with using a <code>&lt;h1&gt;</code> for the page’s only heading.</li>
<li>The <code>&lt;picture&gt;</code> element allows us to render either a shallow image for smaller viewports, or a deeper image for larger viewports. It also allows us to provide low-resolution and higher-resolution versions of the image so lower resolution devices don’t need to waste bandwidth.</li>
<li>The <code>&lt;img&gt;</code> inside of the <code>&lt;picture&gt;</code> has an empty <code>alt</code> attribute. This is because the image is decorative, so it can safely be hidden from screen readers. You must provide an empty <code>alt</code>, rather than no <code>alt</code> if you want to do that.</li>
<li>The <code>&lt;svg&gt;</code> is also hidden from screen readers with <code>aria-hidden="true"</code>. This is again, because it’s decorative and provides no real value unless you can see it.</li>
</ol>
<h2>Styling it up</h2>
<p>Before we start, I just want to note that we will be using the <a href="https://cube.fyi/principles.html">CUBE CSS principles</a> to build out this front-end.</p>
<p>The first thing to do is pick out as much of the UI style as we can as global styles. As it <a href="https://cube.fyi/css.html">says in the CUBE documentation</a>:</p>
<blockquote>
<p>With CUBE CSS, we embrace the cascade and inheritance to style as much as possible at a high level. This means that when nothing but your global styles make it to the browser, the page will still look great. It’s progressive enhancement in action and enables us to write as little CSS as possible.</p>
</blockquote>
<p>Along with this global CSS, we will create some global variables that give us some nice consistency too. The main part of those variables is the fluid type and fluid space scale, using <a href="https://utopia.fyi/">Utopia</a>.</p>
<p>Fluid type and fluid space allow us to create truly responsive designs that respond to the viewport, rather than forcing rigid, catch-all sizes. We’d definitely recommend that you read up on the <a href="https://utopia.fyi/">Utopia site</a>.</p>
<pre><code>:root {
  --font-base: Inter, Segoe UI, Roboto, Helvetica Neue, Arial, sans-serif;
  --font-display: 'Azeret Mono', monospace;
  --color-dark: #000000;
  --color-light: #e5e8df;
  --size-step-0: clamp(1rem, 0.9661rem + 0.1695vw, 1.125rem);
  --size-step-1: clamp(1.125rem, 1.0243rem + 0.5034vw, 1.4963rem);
  --size-step-2: clamp(1.2656rem, 1.0692rem + 0.9822vw, 1.99rem);
  --size-step-3: clamp(1.4238rem, 1.0921rem + 1.6585vw, 2.6469rem);
  --size-step-4: clamp(1.6019rem, 1.0817rem + 2.6008vw, 3.52rem);
  --size-step-5: clamp(1.8019rem, 1.0209rem + 3.9051vw, 4.6819rem);
  --space-2xs: clamp(0.5rem, 0.4831rem + 0.0847vw, 0.5625rem);
  --space-xs: clamp(0.75rem, 0.7161rem + 0.1695vw, 0.875rem);
  --space-s: clamp(1rem, 0.9661rem + 0.1695vw, 1.125rem);
  --space-m: clamp(1.5rem, 1.4492rem + 0.2542vw, 1.6875rem);
  --space-l: clamp(2rem, 1.9322rem + 0.339vw, 2.25rem);
  --space-xl: clamp(3rem, 2.8983rem + 0.5085vw, 3.375rem);
  --space-m-xl: clamp(1.5rem, 0.9915rem + 2.5424vw, 3.375rem);
  --space-mega: clamp(6rem, 4.5763rem + 7.1186vw, 11.25rem);
  --gutter: var(--space-s);
}
</code></pre>
<p>You might be thinking “holy heck, that’s a lot” and yeh, fair point, but fluid type and space scales basically power your entire UI and simplify decision making, so a block of variables seems like a darn good trade-off — especially for a full website project.</p>
<p>These variables are really handy because if you need larger text than what you’ve currently got for an element: go one (or many) up in the size scale and it’ll be perfectly in ratio — maintaining the flow and rhythm of your page. The same applies to spacing too.</p>
<p>With these variables in place, we’re in a position to start writing some global styles.</p>
<pre><code>body {
  font-family: var(--font-base);
  font-size: var(--size-step-0);
  background: var(--color-dark);
  color: var(--color-light);
  padding-block: var(--gutter);
}

:is(h1, h2, h3) {
  font-family: var(--font-display);
  font-weight: 400;
  word-spacing: -0.3ch;
  max-width: 30ch;
}

h1 {
  font-size: var(--size-step-5);
}

h2 {
  font-size: var(--size-step-4);
}

h3 {
  font-size: var(--size-step-3);
}

p {
  max-width: 60ch;
}

a {
  color: currentColor;
}

svg:not([width]):not([height]) {
  height: 1.5ex;
  width: auto;
}

main {
  --switcher-vertical-alignment: stretch;
}

::selection {
  background: var(--color-light);
  color: var(--color-dark);
}
</code></pre>
<p>There’s not a huge amount here because it’s a very simple UI. Also, we only have one section, of one page, so it’s tricky to build out a whole suite of global styles.</p>
<p>Still, it’s important to start global because the aim is to write as little CSS as possible. Even in this part of the build, we’re saving time and bytes by using <a href="https://andy-bell.co.uk/a-more-modern-css-reset/">this reset</a>.</p>
<p>Most of that CSS is pretty self-explanatory, but the key parts are:</p>
<ol>
<li>Setting the initial step of the type size scale as the <code>body</code> font size means that any <code>em</code> unit will by proxy, be fluid, even if we don’t apply any of the other type size scale steps.</li>
<li>The negative word spacing for headings is because our display font is a <a href="https://fonts.google.com/specimen/Azeret+Mono?query=azeret">monospace font</a>. This means that all characters are the same width, including <em>spaces</em>. This is fine for smaller text, but it gets real grim for larger text.</li>
<li>For <code>&lt;svg&gt;</code> elements that don’t have a width and height, we want to prevent blow-outs, so setting a default size, based on the x-height of it’s parent is a great set and forget thing.</li>
<li>The <code>&lt;main&gt;</code> element is setting a <code>--switcher-vertical-alignment</code>. We’ll come on to that later…</li>
<li>We set the opposite colours for text and background when text is selected, to provide contrast.</li>
</ol>
<p>Let’s check in how our project looks so far. Activate the preview to scroll.</p>
<p></p><p>To see this demo, head over to the <a href="https://reality-check.set.studio/furniture-site-globals/">web version</a>.</p><p></p>
<h2>Time to get on some layout</h2>
<p>With global CSS in place, the next step is to move on to the C of CUBE CSS: <a href="https://cube.fyi/composition.html">Composition</a>. From the documentation:</p>
<blockquote>
<p>The composition layer’s job is to create flexible, component-agnostic layout systems that support as many variants of content as possible.</p>
</blockquote>
<p>What this means is that our layout does only layout and <strong>nothing else</strong>. This prevents the need for authors to add layout that will affect sibling elements in their components, which in turn, results in <em>extremely</em> resilient pages.</p>
<h3>The Switcher</h3>
<p>This is one of the many layouts on <a href="https://every-layout.dev/">Every Layout</a> and in short, it allows content to sit inline (next to each other) as long as a configurable container width is available.</p>
<pre><code>.switcher {
  display: flex;
  flex-wrap: wrap;
  gap: var(--gutter, var(--space-s));
  align-items: var(--switcher-vertical-alignment, flex-start);
}

.switcher &gt; * {
  flex-grow: 1;
  flex-basis: calc((var(--switcher-target-container-width, 40rem) - 100%) * 999);
}
</code></pre>
<p>Now what this doesn’t do is make the columns exactly 50% wide: we’re letting the browser take over here to let the content size the elements because we just don’t need that strict control.</p>
<p>The aim of the component and it’s weird looking <code>flex-basis</code> calculation is for a 50/50 split, or more accurately, an equal distribution of space where available. The mathematics is explained in the <a href="https://every-layout.dev/layouts/switcher/">Every Layout chapter</a>.</p>
<p>That’s our split layout sorted, so let’s move on to the next layout.</p>
<h3>Repel</h3>
<p>This does exactly what it says on the tin: items repel from each other like polar-opposites, where space allows.</p>
<pre><code>.repel {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: var(--repel-alignment, center);
  gap: var(--gutter, var(--space-s-m));
}

.repel[data-repel-variant='vertical'] {
  --repel-alignment: stretch;
  flex-direction: column;
}
</code></pre>
<p>This is what powers the top part of the content panel <em>and</em> the vertical split between header and content, thanks to the <code>[data-repel-variant="vertical"]</code> <a href="https://cube.fyi/exception.html">CUBE CSS Exception</a>.</p>
<p>It doesn’t matter what the content is of Repel child elements, because if their content prevents there being enough room to actually repel: the layout stacks nicely. Proper responsive design!</p>
<h3>Flow</h3>
<p>This is my <a href="https://andy-bell.co.uk/my-favourite-3-lines-of-css/">favourite 3 lines of CSS</a>.</p>
<pre><code>.flow &gt; * + * {
  margin-top: var(--flow-space, 1em);
}
</code></pre>
<p>In short, it creates a configurable, but relative space between sibling elements. To keep this article as short as possible, I’ll encourage you to go ahead and <a href="https://andy-bell.co.uk/my-favourite-3-lines-of-css/">read the explainer</a>.</p>
<p>What I definitely will note, though, is we use this in <strong>every project</strong>.</p>
<h3>Wrapper</h3>
<p>This one is pretty self-explanatory. It’s a central wrapper with a <code>max-width</code>.</p>
<pre><code>.wrapper {
  max-width: 1300px;
  margin-inline: auto;
  padding-inline: var(--gutter);
}
</code></pre>
<p>In this case, we’re using pixels, but you could use whatever relative unit is appropriate in your project’s context.</p>
<p>Again, let’s check in to see how things look.</p>
<p></p><p>To see this demo, head over to the <a href="https://reality-check.set.studio/furniture-site-layouts/">web version</a>.</p><p></p>
<h2>Blocks (components)</h2>
<p>Now the global CSS and the compositional layouts are done, it’s time to do the colouring in.</p>
<p>In CUBE CSS, a <a href="https://cube.fyi/block.html">Block is just like a component</a>. Where we didn’t want to apply visuals to layouts, we do in blocks. We’re also going against the grain of the global styles.</p>
<p>At this point, you’ll likely find that blocks are mostly extremely light, because we’ve done so much already.</p>
<h3>Content block</h3>
<p>This is the right-hand (in left-to-right language) panel of content.</p>
<pre><code>.content {
  --gutter: var(--space-mega) 0;

  background: var(--color-light);
  color: var(--color-dark);
  padding: var(--space-m-xl);
  font-size: var(--size-step-1);
}

.content h1 {
  max-width: 11ch;
}

.content p {
  text-wrap: balance;
}

.content h1 + p {
  --flow-space: var(--space-l);
}
</code></pre>
<p>The first bit to cover is <code>--space-mega</code>. This is a <a href="https://utopia.fyi/space/calculator?c=320,16,1.125,1500,18,1.33,6,1,&amp;s=0.75%7C0.5%7C0.25,1.5%7C2%7C3%7C4%7C6%7C7%7C8%7C9%7C10,3xl-7xl&amp;g=s,l,2xl,12#custom">custom spacing pair in Utopia</a>, between <code>3xl</code> and <code>7xl</code>. This space grows and shrinks based on the viewport, which means we get responsive spacing.</p>
<p>Even though we’re using Repel to vertically split content — which in turn, uses <code>--gutter</code> to control <code>gap</code> — we still want to make sure there’s always space, even when the split layout is stacked.</p>
<p>The main thing I want to highlight here is setting a max width of <code>11ch</code> on the heading. This is the length of the longest word — “handicrafts”.</p>
<p>Because we’re using a monospace font, every character is the same width, so we can comfortably set that limit to shape our heading nicely. Without a monospace font, your <code>ch</code> unit will be the width of a <code>0</code> character.</p>
<p>Lastly, notice how we are setting a <code>::selection</code> style because the colours are reversed in the panel. We want to make sure that text selection is visible.</p>
<h3>Site head</h3>
<p>This is the repelled header, at the top of the content panel.</p>
<pre><code>.site-head {
  --repel-alignment: baseline;
  --gutter: var(--space-2xs) var(--space-m);
  font-size: var(--size-step-0);
}

.site-head__name {
  font-size: var(--size-step-2);
  font-family: var(--font-display);
  line-height: 1.1;
}
</code></pre>
<p>Notice how we’re configuring the Repel composition here? Because the two element’s text sizes are different, along with different fonts, setting a <code>baseline</code> alignment keeps things looking nice and neat.</p>
<h3>Icon link</h3>
<p>This is the little link with an icon.</p>
<pre><code>.icon-link {
  display: inline-flex;
  align-items: baseline;
  gap: 0 var(--space-xs);
}

.icon-link svg {
  transform: translateY(0.2ex);
}

.icon-link:hover {
  text-underline-offset: 0.2ex;
}
</code></pre>
<p>Using <code>inline-flex</code> allows the element to size itself based on content. Otherwise it would be block-like and try to fill available space.</p>
<p>We’re again, aligning to the <code>baseline</code> instead of <code>center</code> because if this link was multi-line, it would look real weird with a center-aligned icon. The <code>transform: translateY(0.2ex)</code> is an <a href="https://marvelapp.com/blog/optical-adjustment-logic-vs-designers/">optical adjustment</a> to account for that alignment choice, pulling the icon into the center of the first line of text.</p>
<h3>Decorative image</h3>
<p>This is our last block! Let’s first see the code:</p>
<pre><code>.decorative-image {
  container-type: inline-size;
}

.decorative-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

@container (min-width: 70vw) {
  .decorative-image img {
    max-width: unset;
    width: 100vw;
    height: unset;
    margin-inline-start: 50%;
    transform: translateX(-50%);
    aspect-ratio: 10/5;
  }
}
</code></pre>
<p>Oh hello, it’s <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_container_queries">container query</a> time! What we are doing here is determining if the container — which is our <code>&lt;picture&gt;</code> element — is <code>70vw</code> wide or larger. If that’s the case, our Switcher layout has stacked, which means we can safely presume that our image now occupies 100% of the available width.</p>
<p>When that is the case, we change the aspect ratio to be shallower and make the image “full bleed”, by forcing it to be <code>100vw</code> wide, then positioning in the center, using <a href="https://archive.hankchizljaw.com/wrote/creating-a-full-bleed-css-utility/">this trick</a>.</p>
<p>Instead of using a media query to determine this state, or a <code>px</code>/<code>em</code>/<code>rem</code> value, we are letting the browser do its job, then responding to that state change. Pulling everything together like this is a super resilient way of doing things.</p>
<h2>Wrapping up</h2>
<p>First of all, let’s take a look at the final result. You can use the resizer to size the frame.</p>
<p></p><p>To see this demo, head over to the <a href="https://reality-check.set.studio/furniture-site/">web version</a>.</p><p></p>
<p>I hope this has shown you by simplifying a UI at the core, you can build something that’s visually pleasing, while not negatively affecting the end-user’s experience.</p>
<p>I’ll definitely pick a more complex item from Dribbble or Layers for the next edition of Reality Check too. I just wanted to warm us all up with this first post of the series 😉</p>
        
        ]]></description>
        
      </item>
    
    </channel>
  </rss>
