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.

Article Continues Below

Rethinking the status quo#section1

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#section2

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.

Lego bricks seen from the top, with the pegs representing box content and the padding highlighted to show its consistent width no matter the number of pegs on a brick
No matter how many pegs a brick has, the padding around them is the same as on every other Lego.

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

A rectangle of Lego bricks seen from the top, edges touching, each with a thin line between the pegs and the padding
The padding extends from the outer edge of the pegs of one brick to the outer edge of the pegs of the neighboring brick.

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.

White rectangle representing the complete assembly of bricks in the previous figure with gray rectangles representing the area taken up by pegs, showing that the padding creates full-size gutters between peg areas but only half-size gutter around the periphery.
The periphery is only half the gutter, for now.

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

Peg areas shown as gray rectangles within a white rectangle representing the complete assembly, edges between bricks shown as black lines, and an extra pink line around the outermost edge showing added padding.
The outermost container with a half gutter added all around.
$gutter: 20px;
.container,
.element {
	padding: $gutter / 2;
}

And that will result in this:

White rectangle with gray rectangles and consistent padding between and around them.
With the added outermost padding, all the padding looks the same.

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:

Diagram representing the previous Lego brick rectangle arrangement in rows and columns.
The same layout via rows and columns.

And in code:

See the Pen Complex layout the old way by Samantha Zhang (@moyicat) on CodePen.


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

Example with div:

See the Pen Complex layout with div by Samantha Zhang (@moyicat) on CodePen.


Example with Flexbox:

See the Pen Complex layout with flexbox by Samantha Zhang (@moyicat) on CodePen.


More flexible than Lego#section3

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:

The same arrangement of rectangles representing bricks, but with different values for the x and y padding.
Unlike with physical Lego pieces, we can set different values for the padding.

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:

See the Pen Complex layout with mixin and varying gutter by Samantha Zhang (@moyicat) on CodePen.


Discussion#section4

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#section5

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:

Gray rectangles with white padding, with one of the rectangles and its padding replaced by a picture.
A background applied to .element breaks the grid.

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

<div class="element">
  <div style="background-image:url();"></div>
</div>
Gray rectangles with white padding, with one of the rectangles replaced by a picture.
A child div contains the image so it doesn’t break the grid.

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.

White rectangle with blue borders around content areas.
As with a background, apply a border to a child div.

Full row elements#section6

Another common issue occurs because we occasionally want full row elements, conceptually like this:

A gray horizontal rectangle with the words Title Here atop a white rectangle holding smaller gray rectangles.
Sometimes we want to break the grid with full row elements.

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.

A gray horizontal rectangle with the words Title Here atop a white rectangle holding smaller gray rectangles and a dashed line to show how the words align on the left with the left-most of the small rectangles.
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#section7

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.

Generic comment box with title, text area, helper text, and button.

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#section8

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.

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

See the Pen Pinterest Flow by Samantha Zhang (@moyicat) on CodePen.


Maintenance#section9

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#section10

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#section11

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.

About the Author

Samantha Zhang

Samantha is the Design Lead at Graphiq, where she crafted the design system that generates millions of visualizations from the world’s deepest knowledge graph. When she’s not buried in the codebase, she can be found daydreaming in a hammock, traveling around the world, and creating side projects for fun. Say hello @moyicat.

34 Reader Comments

  1. 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.

  2. 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.

  3. 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.)

  4. 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 🙂

  5. 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!

  6. @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.

  7. 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.

  8. @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? 😉

  9. @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 🙂

  10. “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?

  11. 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.

  12. @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.

  13. 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.

  14. 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 🙂

  15. 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?

  16. @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.

  17. 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 😉

  18. 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.

  19. 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.

  20. 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.

  21. 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?

  22. The flexibility looks great. Do optimization plugins that combine and minimize CSS have any deleterious effect on the appearance of the grid?

  23. 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!

Got something to say?

We have turned off comments, but you can see what folks had to say before we did so.

More from ALA