Learning from Lego: A Step Forward in Modular Web Design
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.
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.
And when Lego bricks are placed together, all the elements will have the same gutter in between.
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.
We need to add the same padding to the outermost container as well.
$gutter: 20px;
.container,
.element {
padding: $gutter / 2;
}
And that will result in this:
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:
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:
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:
Instead, apply the background to a child div
within the .element
div
:
<div class="element">
<div style="background-image:url();"></div>
</div>
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.
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:
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.
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.
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.
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:
Given my slow speed of writing, you will never find it spammy.