Sassy Lobotomised Owl

This article, part of the writing collection, was published on and last updated on .

Managing spacing between elements and components on your page can be a tiring task if undertaken manually. This is where the lobotomised owl comes in: a short, simple snippet of CSS that simplifies this whole process for you. In this article I’ll explain how I make use of it in a more dynamic way using a SCSS mixin.

This post expands on concepts and a mixin from a previous post, Variables for Both, and a more recent project, Bowhead, which I recommend you to read if you’re interested in the context around how the v mixin is used.

Skip to the full owl mixin solution.

The lobotomised owl technique, given graciously to us by the incredibly talented Heydon Pickering, takes away a great deal of pain that comes with setting up sensible spacing between elements and components on your page. Instead of specifically defining margin-bottom/margin-top for each component, we’ll make use of the * + * selector in CSS to perform the following:

For every direct child element of X which is not the first direct child of X, apply a margin-top.

And almost as if by magic, you’ll have a robust spacing system in place. All you need to do is decide which elements X can represent, and what the value of margin-top is going to be.

Working Backwards

Let’s say our goal is to end up with the following CSS output:

body > * + * {
margin-top: 4em;
}

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

article > * + * {
margin-top: 1em;
}

Let’s start things off by pumping the excitement all the way up to 3. At its simplest, the mixin looks like this:

@mixin owl($size) {
& > * + * {
margin-top: $size;
}
}

In fact, if we know that more often than not we'll be using a specific value, we can use a default parameter value, like so:

@mixin owl($size: 1em) {
& > * + * {
margin-top: $size;
}
}

And we can use it like so:

body {
@include owl(4em);
}

main {
@include owl(2em);
}

article {
@include owl;
}

But now let’s take the excitement up to to 4.

Using the same concepts, and, in particular, the v mixin that I introduced in Variables for Both and have refined recently with a project called Bowhead, we need to set up some SCSS variables and assign them within a Map so that we can iterate through them and reference them using our chosen familiar words—small, medium, and large in this case.

$size-large:  4em;
$size-medium: 2em;
$size-small: 1em;

$sizes: (
large: $size-large,
medium: $size-medium,
small: $size-small
)

:root {
@each $key, $value in $sizes {
--size-#{$key}: #{$value};
}
}

And using Bowhead I can now rewrite my owl mixin with the above configuration in place.

@mixin owl($size: small) {
& > * + * {
@include v(margin-top, $size);
}
}

In congruency with the methodology behind Bowhead, we’ve now abstracted away the need to remember or look up the numeric values for the various sizes you might be using, and can instead refer to them as you might think about them or speak about them—using words like small, medium, large, and so on:

body {
@include owl(large);
}

main {
@include owl(medium);
}

article {
@include owl;
}

The Solution

@mixin owl($size: small) {
@if not map-has-key($sizes, $size) {
@error "There is no size named #{$size} in `$sizes`. size should be one of #{map-keys($sizes)}.";
}

& > * + *
{
@include v(margin-top, $size);
}
}

body {
@include owl(large);
}

main {
@include owl(medium);
}

article {
@include owl;
}

Because we’ve included some error-checking within the mixin, if we were to attempt to pass a parameter to the mixin that does not map to a defined size (small, medium, or large)…

header {
@include owl(gigantic);
}

We get the following error message in our console:

Error: There is no size named gigantic in `$sizes`. size should be one of small, medium, large.

And with that, I’m calling it a day!

27 Responses

  1. 🔗 3 Mentions
  2. ❤️ 18 Likes
  3. 🔄️ 6 Reposts