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#section2
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#section3
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#section4
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#section5
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.
Background and borders#section6
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.
Full row elements#section7
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.
Nesting#section8
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.
Varying heights and layouts#section9
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.
Maintenance#section10
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.
Grid layout#section11
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#section12
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.
Awesome. So elegantly simple! For someone fairly new to the front end world, this seems to make perfect sense to me to use with the component driven architecture of some of today’s frameworks like Angular 2 or React.
Wow. Excellent article. You are a genius.
Nice article…. but I think its preaching to the converted here. In my experience it’s the designers of the page that should think about this and it would be a relief if some popular UI design programs would support and encourage this kind of thinking.
Hi Samantha.
Thanks for sharing your knowledge and insight with us. I want to put this to use. But the main reason I always stick to a FE framework like Bootstrap, is tested in as many browsers on as many REAL devices. Financially, and on my own, I can only do so much testing on different devices.
So, what sort of devices was this tested on?
(I realise that it is only padding.)
Thank you all so much for the comments!
@Chris5argent agree, but we are kinda close. Grid was an every old design concept. And yes, maybe all designers need a tool to help us think more holistically and consistently.
@InToGraphics Yes as you mentioned, it’s just padding so theoretically it should work in all places. And at Graphiq we try to support everything IE9+. That said, I can’t guarantee it’s as well tested as Bootstrap. If you find bugs with this method, please let us know
@Samantha Zhang Thanks. I will.
P.S. The current comment #4 (by sniya) looks like spam to me.
I`ve stumbled upon a series of scenarios where your approach of designing grids would`ve fit so well. But still, I had to rely on heavy JS libraries, just because this is the way we`re used to building stuff. I agree that we need to shift our thinking and move from the comfort zone we`re stuck into.
This is pure gold and probably the future!
Happy Holidays!
The current comment #9 (by sniyaarora) is also spam.
Regarding backgrounds, `background-clip` would have been useful here.
@Nexii With background-clip you would forfeit a sizable area of the background image or colour because of clipping.
– With border-box, the image/colour would be clipped and bleed under padding and under border.
– With padding-box, the image/colour would be clipped and bleed under padding.
– With content-box, the image would be clipped and not bleed, but stay under the content only.
Excellent article. Modular application design has benefits beyond flexibility. It allows you to separate different aspects of design to be worked upon and make them independent of each other. It also encourages re-use of the developed components allowing web development companies to increase profits.
Management of such websites become easier and speedy.
@Samantha / @moyicat Since you’re using React at Graphiq I would be really interested in your experiences (pains and gains) of this Lego-approach in combo with React.
Did I even inspire a follow-up post?
@InToGraphics yup quite a few spam messages here. Will let my editor know.
@Nexii Great point. Yah that should be the best solution for background image there.
@Aki Fukai we don’t use React to write CSS – not yet. We are just controlling the HTML structure and set class names in .jsx files. But yes, if we do integrate this method and CSS more into React, will be happy to write a follow up
“Instead, apply the background to a child div within the .element div:”
Are you advocating adding additional markup purely to achieve a visual effect? Wont this lead to code bloat, and in any case, doesn’t this create an additional ‘layer’ that goes against your analogy with the bricks all being on the same level? At this point surely you’re nesting?
I was thinking the same as Cyberi4n. You could avoid that extra div with background-origin: content-box and background-repeat: no-repeat. You would have to add padding to the elements inside though.
I like the approach discussed in the article, I think is really clever, but it feels like there are too many divs.
Thanks for sharing this insight, Samantha. The Pinterest example was helpful.
@Cyberi4n @jpmelguizo valid concerns. Yes background-origin: content-box; would be a nice fix here. So the extra div is only needed for when the element need a border.
For the nesting issue and the feeling of “too many divs” – that nesting to create border method was meant for individual elements. Occasionally a button would need a border and we need a way to do that. In this case the whole button and the space out of it is an element, not just the button text and the space surrounding the text.
If we create a pseudo element to fake the correct border and reset the padding of the button, that could be even more messy than having an extra div.
And if you mean the structural borders – they should only be applied to the .container divs, not the .element divs.
Hi Samantha, Thanks for throwing light on a topic that can transform the future of designing and prove to be beneficial for all web development firms. The way you explained the whole concept is great, even the ones who are new to this world can get an insight.
To those who are trying to troubleshoot the extra divs and background clipping… Maybe I’m thinking too simplistically, but those gutters don’t ever need a background on them, so why couldn’t we just use margins instead of padding to achieve the gutters? The top/bottom margins would collapse so you’d have to make them double the full gutter height and have a 50% negative top/bottom margin on the column containers, but then you could have the background and border be exactly where you expect it to be.
Please tell me why my thinking is wrong, because I’m tempted to start using this right away
Maybe I’m missing something here, but wouldn’t the space-between option of flexbox give me the same thing with a LOT less coding?
@Paul d’Aoust My thought exactly, margins also work. They behave a bit differently but seem easier to use to me.
If you use flexbox the margins don’t collapse so you can use the same values horizontally and vertically.
I used a similar technique with margins in this Pen:
http://codepen.io/malvese/pen/bwxXjX
This is terrific, Samantha! Thanks for sharing!
@Paul d’Aoust and @PhilippeG Yes margin would also work. I explained it with padding because it’s a little bit easier to explain with the Lego example.
@RobM Can you be more specific about how you are planning to implement space-between method? From what I can see, to use space-between you first need to define the width of each element-which feels like more work to do than applying a padding to all of them.
Still choose Bootstrap for my projects. I think it’s still the best for next years.
Anyway, your article is really excellent!
Really nice explained, Samantha. You kept it simple, but totally clear to understand. We also take similar approach in WONDROUS and write component based css. Btw with the scss mixin for components is this approach first step to the styleguides world
This idea is awesome! I’ve been playing around with it and have realized some things I wanted to share.
I wanted the pad amount rolled up into a variable that was consumed by my styles. In other words, instead of hard coding the margin/padding to make things lego-y, I import that variable from my theme file and use it instead. This makes it so that I can change the amount of padding from one place at any time.
To keep myself honest and make sure that I was consistently using this variable value, I set it to randomize between 8 and 15 (I’m using styled-components so I did this with javascript, but I’m betting that you could also use a less or sas var). Now as I refresh, the pad between my blocks changes and I can visually tell if something is trying to hard code the pad.
Isn’t most of this much easy to do with Semantic UI? These designs he did…I’ve made related space-splitting with semantic, and it didn’t need nearly the number of code that was pasted in the samples.
Wow Samantha – I have never thought about padding or margins like a “lego” before, I really like that idea and makes a design consistent. BIG PLUS.
Great analogy! Truly. Implementing this style of layout works well with padding: calc(X – vw); or a simple padding: 4vw; (just an example) as well for padding that way a ratio is maintained across screen sizes/devices and it scales with size (with correct media queries in place when it becomes too large). Thoughts on that approach?
Fine way of telling, and pleasant article to obtain facts about my presentation focus, education which i am going to present in institution of higher education.
The flexibility looks great. Do optimization plugins that combine and minimize CSS have any deleterious effect on the appearance of the grid?
The way you explained each point is excellent, Thanks for sharing
best article web desgin
طراحی سایت
http://www.sitewebco.com/
Great post!
I love bootstrap so much. This is really so much easy to make a responsive web design.
Now a days maximum clients need is responsive web design!
Great thanks for sharing this amazing informative post with us!