As web developers we've used countless UI libraries that give us an interface for creating grids. Now things have become much easier thanks to Grid and Flexbox in CSS, but it is still nice to have everything arranged in nice consistent classes for production use. Recently, I set out on a mini-mission to find a grid library for our new design system. In my search, I had to go through many libraries, their features and their source code. Most of them were 90% fit for my use case, but then I'd find some flaw that'd put me off. This continued for a couple of hours, at the end of which I uttered the golden developer words "I know, I'll just implement my own grid library". The old folks here know what follows. They also know, irrespective of whether you know if this is a bad idea, the temptation to start working in an empty file without any dependencies is too real to just ignore.
Anyway, after spending some time baking my own grid library, and slowly realizing that in probably a week or so, I'll end up with exactly the kind of code that I'd rejected earlier, given how similar everything was looking, I decided to go with something readybaked. But this little exercise of trying to make a grid library taught me a lot about how these libraries are implemented. I wish to share some of that excitement, the learnings with you in this article.
By the end of this article, I want you to have at least a basic understanding of how a grid library is written and be confident to dig into the source code in case you need to customize a library to fit your needs. Note that I'm using Flexbox. Bootstrap 3 used widths and floats, and Bootstrap 4 uses Flexbox. I believe you can also use CSS3 Grids. Use what feels natural to you.
What's a grid system?
A grid means what you'd expect it to mean. It is a two dimensional cellular structure, like a cupboard or a chess board, and you can put/place stuff on/in it. In the context of web development and design, grids usually just define the vertical or columnar properties. The reason is that we do not want to design for horizontal scrolling, and assume that all navigation on a page will be based on vertical scrolling. As a result, whatever the screen width is, we take that as 100% and divide it into the number of columns that we prefer.
First step is to set the number of columns that we'd like the designs to use. 12 is commonly used because with 12, we can divide the page into 1, 2, 3, 4, 6 and 12 parts (notice that those are just divisors of 12). Then we set the gutter size. Gutter is the gap between two columns. Usually we set it to 1rem which, if we haven't modified, should be 16px.
After that, we set breakpoints. Breakpoints are points where our UI changes in response to a change in the screen size. For modern websites, we have to cater to a wide variety of browsers and screen sizes, and defining a set of screen sizes is our first step towards it. For example
- 0-450px can be considered to be small mobiles
- 450px-750px can be large mobiles
- 750px-1000px can be tables
- 1000px+ can be desktops
This is all very arbitrary, of course. we can set whatever sizes fit most of our users' device types or just copy what Bootstrap or other popular libraries do (in general, when in doubt follow the standards).
Next is to actually implement designs that adhere to this grid system. Here we make an assumption that we have a UI hi-fi design mockup that was built using the same 12 column grid as base. For ease of implementation, we can define the CSS classes that we frequently use, which while not absolutely necessary, is helpful.
While I mentioned that it isn't absolutely necessary to have CSS classes that help us with grids, more often than not we'll want to have some of them. The way we do it is we write classes for all the possible widths that a div might take on the screen, right from 1/12 to 12/12 (which is essentially 100% width).
In this short snippet, a
.row would be the wrapper class for one or more
Styling our HTML
Consider the simple markup. This is a typical layout for a blog with a navigation section on the left, blog content in the center and a sidebar on the right with some widget.
Now, if we want to split the page into three columns; left sidebar (25% or col-3 containing the ".navigation"), main content (50% or col-6 containing ".content") and right sidebar (25% or col-3 containing ".blog-roll"), we just have need to add the relevant classes to our markup
It should give you a grid like the following.
If you ignore the basic styling and borders, you'll notice that there's still no spacing between the columns (or gutters) here. We need spacing because otherwise we'll have to add internal padding to all the columns and we do not want content from one column sticking to the content from another. To have some of that, we define a gutter variable and add half gutter width padding on right and left side of our columns.
--gutter-half-width is just a variable in CSS that we access with the
& > *[class^="col-"] selects all direct descendants of row class which have the col-* class set.
:root selects the document root.
We don't want gutters on the extreme left and extreme right of our grid. We compensate for that with negative margins on the .row class.
If you were to re-run the CSS again, we'd have something like the following.
At this point, our grid system is ready. Ready for one screen size, that is. Let's see what happens if we switch to mobile view?
I mean, it kind of does what we asked it to do, but if those columns were in fact two sidebars and one main content section, and this being displayed on a 320px screen, your users would be pretty irate (or worse, your website becomes popular on r/CrappyDesign). You don't want that, don't you?
To fix that, we'll use the breakpoints that we had discussed about earlier. Essentially, we want to have three columns on desktops (which we've already made), one on tablet and one on mobile phones in a row. We start off with writing the media queries for each of our breakpoint.
Next, we write the column CSS classes for each media query (same code, but we'll rename them a bit so that we can tell which class is for which viewport). Notice the
col-[viewport-name]-[column-width] format. You'll might recognize this from libraries like Bootstrap (for example, col-md-3 or col-xs-6).
Now, we can edit our HTML to use these new classes. For each viewport, we add a class which tells the browser how wide it needs to be on that viewport. For example, our
article section is 50% in width (or 6 out of 12 columns) on desktop, while 100% on tablet and mobile (or 12 out of 12 columns).
This will ensure our columns are 100% width on tablet and mobile screens. And because our classes are inside of media queries that only fire at the viewport width breakpoints, only the relevant class gets applied. As you can imagine, one can really fine tune how things look by selecting a higher number of breakpoints and designing for a more consistent UI across the spectrum of screen sizes.
Bonus - Hiding sections
Let's say we decide that the
blog-roll section isn't super important on mobile screens, and should be removed. This is a common usecase; hiding specific blocks on specific viewports. And there's a very easy way of doing this right in the grid system.
The little trick is to add hidden classes, just like our column classes, to our viewport media queries.
Now, to hide the
blog-roll on mobile phones, we just add the hidden class to its classlist.
I've posted truncated snippets above. The whole thing would look something like the following.
The flexbox standard defines various ways of arranging things inside the container both along the flex-direction axis (called the main axis) and the cross axis (the axis perpendicular to the main axis). If we were to extend this, we could define helper classes like
.col-reverse (flex-direction: reverse) or
.col-spread-out (justify-content: space-between) using these properties depending on our use cases.
The flexbox standard is pretty elaborate, and extremely well documented. There are also good libraries build around Flexbox that provide what we just build, and much more than that, out of the box. In production, one should try to stick to libraries and not reinvent the wheel.
Having said that, it is also important to understand the libraries that we use. Now we know what actually happens when we use that familiar
col-md-6 class from Bootstrap, and if need be, we won't shy away from editing the source code to make the library fit our needs!
Thank you for reading!