Learning from Lego: A Step Forward in Modular Web Design

Samantha Zhang
Feb 23, 2017 · 11 min read

With hundreds of frameworks and UI kits, we are now assembling all kinds of content blocks to make web pages. However, such modularity and versatility hasn’t been achieved on the web element level yet. Learning from Lego, we can push modular web design one step forward.

Image for post
Image for post

Rethinking the status quo

Modular atomic design has been around for a while. Conceptually, we all love it — web components should be versatile and reusable. We should be able to place them like bricks, interlocking them however we want without worrying about changing any code.

So far, we have been doing it on the content block level — every block occupies a full row, has a consistent width, and is self-contained. We are now able to assemble different blocks to make web pages without having to consider the styles and elements within each block. That’s a great step forward. And it has led to an explosion of frameworks and UI kits, making web page design more modular and also more accessible to the masses.

Achieving similar modularity on the web element level is not as easy. Pattern Lab says we should be able to put UI patterns inside each other like Russian nesting dolls. But thinking about Russian nesting dolls, every layer has its own thickness — the equivalent of padding and margin in web design. When a three-layer doll is put next to a seven-layer doll, the spacing in between is uneven. While it’s not an issue in particular with dolls, on web pages, that could lead to either uneven white space or multilevel CSS overrides.

I’ve been using Bootstrap and Foundation for years, and that’s exactly what would happen when I’d try to write complex layouts within those frameworks — rows nested in columns nested in rows, small elements in larger ones, all with paddings and margins of their own like Russian dolls. Then I would account for the nesting issues, take out the excessive padding on first- and last-child, calculate, override, add comments here and there.

It was not the prettiest thing I could do to my stylesheets, but it was still tolerable. Then I joined Graphiq, a knowledge company delivering data visualizations across more than 700 different topics. Here, content editors are allowed to put in any data they want, in any format they want, to create the best experience possible for their readers. Such flexibility makes sense for the small startup and we have a drag and drop interface to help organize everything from a single data point to infographics and charts, to columns, blocks, and cards. Content editors can also add logic to the layout of the page. Two similar bar charts right next to each other could end up being in quite different HTML structures. As you can imagine, this level of versatility oftentimes results in a styling hell for the designers and developers. Though a very promising solution — CSS Grid Layout — is on the horizon, it hasn’t made its way to Chrome yet. And it might take years for us to fully adapt to a new display attribute. That led me to thinking if we can change the Russian doll mentality, we can take one step further toward modular design with the tools available.

Learning from Lego

To find a better metaphor, I went back to Lego — the epitome of modular atomic design. Turns out we don’t ever need to worry about padding and margin when we “nest” a small Lego structure in a large Lego structure, and then in an even larger Lego structure. In fact, there is no such concept as “nesting” in Lego. All the elements appear to live on the same level, not in multiple layers.

But what does that mean for web design? We have to nest web elements for the semantic structure and for easy selecting. I’m not saying that we should change our HTML structures, but in our stylesheet, we could put spacing only on the lowest-level web elements (or “atoms” to quote atomic design terms) and not the many layers in between.

Take a look at the top of any individual Lego brick. If you see the space around the outside of the pegs as the padding of a web element, and everything inside the padding as the content, you will find that all Lego bricks have a consistent padding surrounding the content, which is exactly half of the gap between elements.

Image for post
Image for post

And when Lego bricks are placed together, all the elements will have the same gutter in between.

Image for post
Image for post

No other padding or margin needed; the gaps are naturally formed. All the elements — no matter how deeply they are nested — appear to be on the same level and need no CSS override or adjustment, not even the first- and last-child reset.

Putting it in code, we can make a class that adds the half-gutter spacing, and apply it to all the lowest-level web elements on the page. Then we can remove all the spacing on structural divs like .row and .col.

$gutter: 20px;
.element {
padding: $gutter / 2;
}

One tiny tweak to be mindful of is that when the padding is only on .element, the padding between the outermost elements and the parent div would only be half the gutter.

Image for post
Image for post

We need to add the same padding to the outermost container as well.

Image for post
Image for post
$gutter: 20px;
.container,
.element {
padding: $gutter / 2;
}

And that will result in this:

Image for post
Image for post

Think about how many layers of overrides we would need to create this layout with the current rows and columns mentality. The best we can do is probably something like this:

Image for post
Image for post

And in code:

With the Lego mentality, the spacing and the code can be much simpler, as shown in the two examples below:

Example with div:

Example with Flexbox:

More flexible than Lego

Lego is a true one-size-fits-all solution. With Lego, we don’t get to tweak the padding of the bricks according to our projects, and we can’t have different horizontal and vertical padding. Web design offers us much more variation in this area.

Instead of just setting one value as the gutter, we can set four different variables and get more flexible layout this way:

$padding-x: 10px;
$padding-y: 20px;
$padding-outer-x: 40px;
$padding-outer-y: 30px;
.container {
padding: $padding-outer-y $padding-outer-x;
}
.element {
padding: ($padding-y / 2) ($padding-x / 2);
}

The result looks like this:

Image for post
Image for post

It’s still modular, but also has varying spaces to create a more dynamic style.

With responsive design, we could also want different spacing for different media queries. We can take our approach one step further and write our logic into a Sass mixin (alternatively you can do it with LESS, too):

@mixin layout ($var) {  $padding-x: map-get($var, padding-x);
$padding-y: map-get($var, padding-y);
$padding-outer-x: map-get($var, padding-outer-x);
$padding-outer-y: map-get($var, padding-outer-y);
.container {
padding: $padding-outer-y $padding-outer-x;
}
.element {
padding: ($padding-y / 2) ($padding-x / 2);
}
}

Using this mixin, we can plug in different spacing maps to generate CSS rules for different media queries:

// Spacing variables
$spacing: (
padding-x: 10px,
padding-y: 20px,
padding-outer-x: 40px,
padding-outer-y: 30px
);
$spacing-tablet: (
padding-x: 5px,
padding-y: 10px,
padding-outer-x: 20px,
padding-outer-y: 15px
);
// Generate default CSS rules
@include layout($spacing);
// Generate CSS rules for tablet view
@media (max-width: 768px) {
@include layout($spacing-tablet);
}

And as easy as that, all our elements will now have different spacing in desktop and tablet view.

Live example:

Discussion

After using this method for almost a year, I’ve encountered a few common questions and edge cases that I’d like to address as well.

1. Background and Borders
When adding backgrounds and borders to the web elements, don’t apply it to the .element div. The background will cover both the content and padding areas of the element, so it will visually break the grid like this:

Image for post
Image for post

Instead, apply the background to a child div within the .element div:

<div class="element">
<div style="background-image:url();"></div>
</div>
Image for post
Image for post

I used this structure in all my examples above.

Similarly, the border goes around the padding in the box model, so we should also apply the border of the element to a child div to maintain the correct spacing.

Image for post
Image for post

You could use background-clip: content-box; to avoid the additional <div> for background as well, but that won’t work for borders.

2. Full Row Elements
Another common issue occurs because we occasionally want full row elements, conceptually like this:

Image for post
Image for post

To style full row elements following the .container and .element structure, we need to make use of negative margin:

.element-full-row {
margin: 0 (-$padding-outer-x);
padding: ($padding-y / 2) ($padding-x / 2 + $padding-outer-x);
}

Notice that we need to add back the $padding-outer-x to the padding, so that the content in .element-full-row and the content in .element align.

Image for post
Image for post

The code above handles the horizontal spacing, and the same logic can be applied to take over vertical spacing as well (as shown in the example above–the header element takes over the top padding). We can also add a negative margin very easily in our stylesheets.

.element-full-row:first-child {
margin: (-$padding-outer-y) (-$padding-outer-x) 0;
padding: ($padding-y / 2 + $padding-outer-y) ($padding-x / 2 + $padding-outer-x) ($padding-y / 2);
}

It can be applied as a standalone rule or be included in the Sass or LESS mixin, then you will never have to worry about them again.

3. Nesting
The full freedom in nesting is the strong suit of this Lego CSS method. However, there is one kind of nesting we can’t do–we can’t ever nest an .element within an .element. That will create double padding and the whole point of this method would be lost. That’s why we should only apply the .element class to the lowest level web elements (or “atoms” to quote atomic design terms) like a button, input box, text box, image, etc.

Take this very generic comment box as an example.

Image for post
Image for post

Instead of treating it as one “element,” we need to treat it as a pre-defined group of elements (title, textarea, button, and helper text):

<div class="comment">
<h3 class="comment-title element">Add a new comment</h3>
<textarea class="element"></textarea>
<div class="clearfix">
<div class="float-left">
<div class="element">
<button class="btn-post">Post comment</button>
</div>
</div>
<div class="float-right">
<div class="helper-text element">
<i class="icon-question"></i>
Some HTML is OK.
</div>
</div>
</div>
</div>

Then, we can treat .comment as one reusable component–or in the atomic design context, a “molecule”–that will play well with other reusable components written in the same manner, and can be grouped into higher level HTML structures. And no matter how you organize them, the spacing among them will always be correct.

4. Varying Heights and Layouts
In the bulk of this article, we’ve been using the same fitted row example. This may lead some to think that this method only works for elements with defined height and width.

It’s more versatile than that. No matter how elements change in height and width, lazy load, or float around, the Lego-like padding will ensure the same consistent gap between elements.

I made a quick Pinterest flow layout to demonstrate how this mentality works with fluid and changing elements.

Image for post
Image for post

5. Maintenance
Some of you might also be worrying about the maintenance cost. Admittedly, it takes time to learn this new method. But once you start to adopt this mentality and write CSS this way, the maintenance becomes extremely simple.

Especially with the layout mixin, all the spacing rules are centralized and controlled by a few groups of variables. A single change in the variables would be carried out to all the elements on the web page automatically.

In comparison, we might have to change padding and margin in 20 different places with the old method, and then we have to test to make sure everything still works. It would be a much more hectic process.

6. Grid Layout
And finally, there is the Grid layout, which supports very complicated layouts and nests much more gracefully than block. You might be thinking this is quite a lot of hard work for a problem that is actually going away.

While many of the issues we talked about in this article might go away with Grid, it might take Grid years to get browser support. And then, it might take a long time for the community to get familiar with the new method and develop best practices and frameworks around it. Like Flex–it’s already supported by most browsers, but it’s far from widely adopted.

And after all, it could take a typical web user a long time to understand Grid and how that works. Similarly, it would require quite a lot of development for us to translate user layout input into good CSS Grid code. The old by-column and by-row method is way easier to understand, and when nesting is not an issue, it could stand as a good solution for websites that allow user configuration.

Conclusion

We started to implement this method at Graphiq in the beginning of 2016. Almost a year in, we love it and believe this is how we should write web layouts in the future. As we refactor each page, we’re deleting hundreds of lines of old CSS code and making the stylesheets way more logical and much easier to read. We also got far fewer layout and spacing bugs compared to all our refactors in the past. Now, no matter how our content editors decide to nest their data points, we’ve got very little to worry about.

From what we’ve seen, this is a real game changer in how we think about and code our layouts. When web components are modular like Lego bricks down to the elements level, they become more versatile and easier to maintain. We believe it’s the next step to take in modular web design. Try it for yourself and it might change the way you write your web pages.

This article was published first on A List Apart. Reprinted with the permission of A List Apart and the author[s].

If you find this article helpful, please subscribe to my email list so you will get an notification every time I write something new:

Image for post
Image for post

Given my slow speed of writing, you will never find it spammy.

5000 Things

“Designing a product is keeping 5,000 things in your brain…

Samantha Zhang

Written by

Christian. Help people understand data with design. Get an email when I write new articles: http://eepurl.com/g-LENj · My portfolio: http://samanthaz.me/

5000 Things

“Designing a product is keeping 5,000 things in your brain and fitting them in new and different ways to get what you want.” — Steve Jobs

Samantha Zhang

Written by

Christian. Help people understand data with design. Get an email when I write new articles: http://eepurl.com/g-LENj · My portfolio: http://samanthaz.me/

5000 Things

“Designing a product is keeping 5,000 things in your brain and fitting them in new and different ways to get what you want.” — Steve Jobs

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store