Skip to content

Code Rush is a regular podcast about front-end design & development challenges in a fast-moving industry,

Stack utilities to space page builder blocks

In my Statamic Starter Kit Peak, I use a page builder approach where all blocks within the page builder get space between them by putting them in a one column grid with gaps. This quickly becomes limiting when you want certain blocks to have no spacing above or below them. Or when you want to increase or decrease the spacing for a particular block.

The solution

The lobotomized owl CSS selector can make this work. In short the selector (.class * + *) selects all children that are preceded by a sibling. You can then add margin to the top of those elements to create even spacing between various elements within a container. The space-y-* utilities in Tailwind already use this lobotomized owl selector to give children that are preceded by a sibling margin-top. Unfortunately, they don’t give you the ability to modify the spacing on a child level.

There are various techniques going around that implement this, however I didn’t come across a good solution that also enables you to remove spacing on the next sibling and that works with the Tailwind utility mindset.

In order to make this work I created custom stack-* utilities and stack-space-* utilities to modify the space above a child element. We can now use the following classes:

  • stack-*: Stack classes on wrapper elements following the Tailwind spacing scale. It uses CSS Custom Properties to set the space between children.

  • stack-space-*: Space classes that modify the Custom Property used for the margin top of a child element.

  • no-space-t: Disable the margin top for a child element.

  • no-space-b: Disable the margin top for the next sibling.

  • no-space-y: Disable the margin top for a child element and the margin top for the next sibling.

You can use this API like this:

1<main class="stack-16">
2 <section></section>
3 <section>A space of 4 rem above.</section>
4 <section class="no-space-y">No space above this and the next sibling</section>
5 <section>No space above.</section>
6 <section class="stack-space-8">A space of 2 rem above.</section>
7 <section>A space of 4 rem above.</section>
8</main>
An example of the stack utilities in practice.

In this example all child elements of <main> get a space of 4rem between them. However the third child (no-space-y) will be flush against its predecessor and the sibling following it. The penultimate sibling gets a decreased spacing above it, because we add a stack-space-* utility to it. In this case we set the stack-space Custom Property to 2rem.

The following CSS is what gets generated by Tailwind in this example.

1.stack-16 > * {
2 --stack-space: 4rem;
3}
4.stack-16 > *:not(.no-space-y, .no-space-b) + *:not(.no-space-y, .no-space-t) {
5 margin-block-start: var(--stack-space, 4rem);
6}
7.stack-space-8 {
8 --stack-space: 2rem;
9}
An example of the generated CSS when using the stack utilities.

You can see here that when you use stack-16, Tailwind generates a Custom Property called stack-space with a value of 4rem. This will be set on the wrapper container. The lobotomized owl selector selects all children that are preceded by a sibling to give it a margin top using the stack-space value. However, using the :not selector it makes sure that:

  1. When sibling 1 has no-space-y or no-space-b, it won’t get selected and the next sibling won’t get margin top.

  2. When sibling 2 has no-space-y or no-space-t, it also won’t get selected and therefore itself won’t get a margin top.

Arbitrary values and modifiers are also supported. E.g. stack-[5px] and md:stack-16. And the technique can be used to satisfy all kinds of vertical spacing needs

With these utilities in Peak we can evenly space out page blocks and influence the space above and below a block on the block level itself. This got released with version 16.

The utilities

Being able to use those stack utilities requires some custom Tailwind plugins. This is the code I used. Feel free to use and modify this for your own use.

1// Stack utilities.
2plugin(function({ matchUtilities, theme }) {
3 matchUtilities(
4 {
5 stack: (value) => ({
6 '> *': {
7 '--stack-space': value,
8 },
9 '> *:not(.no-space-y, .no-space-b) + *:not(.no-space-y, .no-space-t)': {
10 marginBlockStart: 'var(--stack-space, 4rem)'
11 },
12 }),
13 },
14 { values: theme('spacing') }
15 )
16}),
17 
18// Stack space utilities.
19plugin(function({ matchUtilities, theme }) {
20 matchUtilities(
21 {
22 'stack-space': (value) => ({
23 '--stack-space': value,
24 }),
25 },
26 { values: theme('spacing') }
27 )
The plugin code for the Tailwind stack and space utilities.

Image credits by Ryan Stone.

Creating a clipping mask hover effect with Tailwind CSS

  • Tailwind CSS