Managing Flow and Rhythm with CSS Custom Properties

An important part of designing user interfaces is creating consistent vertical rhythm between elements. Creating consistent, predictable space doesn’t just make your web pages and views look better, but it can also improve the scan-ability.

Browsers ship with default CSS and these styles often create consistent rhythm for flow elements out of the box. The problem is though that we often reset these styles with a reset. Elements such as <div> and <section> also have no default margin or padding associated with them.

I’ve tried all sorts of weird and wonderful techniques to find a balance between using inherited CSS while also levelling the playing field for component driven front-ends with very little success. This experimentation is how I landed on the flow utility, though and I’m going to show you how it works. Let’s dive in!

The Flow utility

With the ever-growing number of folks working with component libraries and design systems, we could benefit from a utility that creates space for us, only when it’s appropriate to do so. The problem with my previous attempts at fixing this is that the spacing values were very rigid.

That’s fine for 90% of contexts, but sometimes, it’s handy to be able to tweak the values based on the exact context of your component. This is where CSS Custom Properties come in handy.

The code

What this code does is enable you to add a class of flow to an element which will then add margin-top to sibling elements within that element. We use the lobotomised owl selector to select these siblings. This approach enables an almost anonymous and automatic system which is ideal for component library based front-ends where components probably don’t have any idea what surrounds them.

The other important part of this utility is the usage of the --flow-space custom property. We define it in the .flow component and each element within it will be spaced by --flow-space, by default. The beauty about setting this as a custom property is that custom properties also participate in the cascade, so we can utilise specificity to change it if we need it. Pretty cool, right? Let’s look at some examples.

A basic example

What we’ve got in this example is some basic HTML content that has a class of flow on the parent article element. Because there’s a very heavy-handed reset added as a dependency, all of the content would have been squished together without the flow utility.

Because our --flow-space custom property is set to 1em, the space between elements is 1X the font size of the element in question. This means that a <h2> in this context has a calculated margin-top value of 28.8px, because it has an assigned font size of 1.8rem. If we were to globally change the --flow-space value to 1.1em for example, we’d affect everything because margin values would be calculated as 1.1X the font size.

This example looks great because using font size as the basis of rhythm works really well. What if we wanted to to tweak certain elements within this article, though?

I like lots of whitespace with my article layouts, so the 1em space isn’t going to cut it for all elements. I like to provide plenty of space between headed sections, so I increase the --flow-space in these instances:

h2 {
--flow-space: 3rem;
}

Notice also how I also switch over to using rem units? I want to make sure that these overrides are always based on the root font size. This is a personal preference of mine and you can use whatever units you want. Just be aware that it’s better for accessibility to use flexible units like em, rem and %, so that a user’s font size preferences are honoured.

A more advanced example

Although the flow utility is super useful for a plethora of contexts, it really shines when working with a few unrelated components. Instead of having to write specific layout CSS just for your particular context, you can use flow and --flow-space to create predictable and contextual space.

In this example, we’ve got ourselves a little prototype layout that features a media element, followed by a grid of features. By using flow, it was really quick and easy to generate space between those two main elements. It was also easy to create space within the components. For example, I added it to the .media__content element, so that the article’s content would space itself:

<article class="media__content flow">
...
</article>

Something to remember though: the custom properties cascade in the same way that other CSS values do, so you’ve got to keep that in mind. We’ve got a great example of that in this example where because we’ve got the flow utility on our .features component, which has a --flow-space override: the child elements of .features will inherit that value, so we’ve had to set another value on the .features__list element.

“But what about old browsers?”, I hear you cry

We’re using CSS Custom Properties that at the time of writing, have about 88% support. One thing we can do to remedy the other 12% of browsers is to set a default, traditional margin-top value of 1em, so it calculates itself based on the element’s font-size:

Thanks to the cascading and declarative nature of CSS, we can set that default margin-top value and then immediately set it to use the custom property instead. Browsers that understand Custom Properties will automatically apply them—those that don’t will ignore them. Yay for the cascade and progressive enhancement!

Wrapping up

This tiny little utility can bring great power for when you want to consistently space elements, vertically. It also—thanks to the power of the modern web—allows us to create contextual overrides without creating modifier classes or shame CSS.

If you’ve got other methods of doing this sort of work, please let me know on Twitter. I’d love to see what you’re working on!

About the author

Andy Bell is an independent designer and front-end developer who’s trying to make everyone’s experience on the web better with a focus on progressive enhancement and accessibility.

Related articles

Richard Rutter shepherds a tea towel onto our collective heads and describes a technique for responsively scaling display text to maintain a consistent feel in both landscape and portrait screen orientations. That should put things back into proportion.

Mark Boulton completes our calendar with some typographic techniques to improve the reading experience. Typography, like Christmas and advent calendars, relies on the accumulation of small gains for its best effects.

Richard Rutter adapts an extract from his forthcoming book on web typography and encourages us to be brave with type choices. By allowing type to express a website’s intentions from even before the moment a visitor starts to read, we can help set the tone and our users’ expectations.

Nicole Sullivan understands how the accumulated weight of small typographic decisions can mount up into a tangle of CSS declarations. With a new tool at your disposal you can take stock and clear up the mess like it’s Boxing Day.

Drew McLellan dazzles us with the creative typographic possibilities of unicode-range, a little-used property of @font-face declarations. More than just a unloved bauble on the CSS Christmas tree, unicode-range can extend a site’s typography in useful and eye-catching ways.

Jeffrey Zeldman steps back and takes a long hard look at the issue of cross-platform web font rendering quality. It can feel like Christmas morning with the array of font options available – but are we opening Pandora’s box?