Sassy Lobotomised Owl

This post expands on concepts and a mixin from a previous post, Variables for Both, 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($measure) {
  & > * + * {
    margin-top: $measure;
  }
}

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($measure: 1em) {
  & > * + * {
    margin-top: $measure;
  }
}

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, 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.

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

$measures: (
  large:  $measure-large,
  medium: $measure-medium,
  small:  $measure-small
)

:root {
  @each $key, $value in $measures {
    --measure-#{$key}: #{$value};
  }
}

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

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

In congruency with the methodology behind using the v mixin, we’ve now abstracted away the need to remember or look up the numeric values for the various measures 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($measure: small) {
  @if not map-has-key($measures, $measure) {
    @error "There is no measure named #{$measure} in `$measures`. measure should be one of #{map-keys($measures)}.";
  }

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

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 measure (small, medium, or large)…

header {
    @include owl(gigantic);
}

We get the following error message in our console:

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

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

🗓 published on
📚 reading length ~400 words
🏷 tagged under
🔗 shorturl repc.co/a54a1


G.R.E.Y.G.O.D.S.I.I. Grime MC