A List Apart

Menu

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

Article Continues Below

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.

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

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

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

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

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

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

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

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

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.

About the Author

29 Reader Comments

Load Comments