How you’ve been getting the Bootstrap grid all wrong—and how to fix it.
No grid is perfect. Some grids are useful.
A long time ago, in a galaxy far far away, I wrote 3 articles on Bootstrap. They have amassed a staggering 1.5 million page views. 😮 I wrote them after hanging out in the Bootstrap IRC channel for a few weeks, and after working with grids (Blueprint, 960!), designers, and developers for over a decade, it became clear that people know how to use a grid, but don’t really understand how it works and how to get themselves out of trouble.
These articles are for Bootstrap v3, but the core principles are still relevant. See the original 3 articles here: Intro to the Grid, How the grid works, LESS Workflow Tutorial
Bootstrap 4
There are a lot of articles on Bootstrap 4 and the grid as a whole. I am only covering a very narrow topic here: building your grid via mixins. This is a topic that is very under-documented, and I want to share my perspective on it.
Disclaimer: If you are using Bootstrap to quickly prototype and try things out, using the HTML classes is fine, it doesn’t really matter. It’s often preferable to just start creating a layout in HTML and include the Bootstrap CSS, without actually creating/planning a whole SCSS architecture. The point of this article is to look at more enduring, scalable, decoupled usage of the grid system.
The Misconception
For people new to Bootstrap or other CSS grids, the first thing they usually see is the application of a grid via in-HTML classes. There is a presumption that the Bootstrap grid implementation has to look like this:
<div class="container">
<div class="row">
<div class="banana col-sm-6 col-md-4">
<p>banana 🍌 content</p>
</div>
</div>
</div>
Notice the div called .banana. An HTML element (a div) that has the classes applied to it for each responsive breakpoint column width. The behavior of the div is defined by the classes you put in the HTML:
col-sm-6 col-md-4
In the sample above, the div would be a 6-column at the small breakpoint range, and a 4-column at the medium breakpoint range. This would also imply that the undeclared “zero to small” it is a 100% width 12-column div, and everything from large upwards inherits the medium 4-column declaration.
When you’re designing a complex layout across lots of pages, you end up with col-*-* (and more) all over the place like a plague. It’s not uncommon for there to be dozens of different combos of this on a single page. Everywhere, you literally see elements that look like this:
<div class="col-6 col-sm-4 col-md-8 offset-md-4 align-self-start justify-content-end"> wow 😱 </div>
That’s not an extreme example. It’s a common occurrence. Now repeat it for every structural layout element on a page.
The Problems With This
The method of including the CSS classes in your HTML has various problems. First, it’s non-semantic; col-sm-6 col-md-4 says nothing about what that element is, only how it will behave responsively.
This also creates tons of unused CSS. Here is a Gist that shows the uncompressed CSS that Bootstrap creates so that all of its grid capabilities are available through direct CSS classes called in the HTML.
As is, it’s just about 2000 individual lines. Minifying or gzipping this reduces a lot of the payload, but it’s still pollution. Whether you’re prototyping something and just want to include the CSS as-is, or if you’re creating something more enduring but aren’t using SASS mixins, all of this is being included in your CSS.
Next, you’re coupling the behavior to the structure. If you’re working with something like React and SCSS, you would need to have the HTML classes in your React .jsx files, and then also be styling your HTML via your SCSS. This puts grid behavior in the HTML/.jsx, and styling behavior in the SCSS. You’ve now coupled your responsive behavior to the wrong end of your codebase.
This leaves you with split logic. In your HTML, you have:
<div class="container">
<div class="row">
<div class="banana col-sm-6 col-md-4">
<p>banana 🍌 content</p>
</div>
</div>
</div>
And then if that .banana 🍌 class contains style, you will have this in your SCSS:
.banana {
p {
font-size: 3rem;
color: yellow;
font-weight: 600;
}
}
The .banana 🍌 now is being controlled in two places. You’re defining .banana in SCSS as a large, bold yellow piece of text. Then, you’re putting .banana’s responsive behavior over in the HTML.
This doesn’t make durable, long-term sense. For prototyping, fine. But if you’re working on big, serious codebases across multiple developers, development teams, designers, repositories, plugins (like I do) etc, this is untenable and scales very poorly.
The Mixin Solution
So what’s the cure? The mixin.
Here’s our same .banana HTML block as above, but the grid behavior is now controlled in the SCSS by mixins along with the expression of the style. The HTML:
<div class="banana">
<p>banana 🍌 content</p>
</div>
And the SCSS:
.banana {
@include make-col-ready;
@include media-breakpoint-up(sm) {
@include make-col(6);
}
@include media-breakpoint-up(md) {
@include make-col(4);
}
p {
font-size: 3rem;
color: yellow;
font-weight: 600;
}
}
That’s it. It’s all moved to the SCSS, and .banana can be changed and controlled from the central location. Entire, complex grid behaviors that cover any use case are centralized, organized, and scaleable.
Caveat: there is an argument about “look at all those mixins and media-query breakpoints I have to include.” Yes, this does move the heavy lifting to the SCSS and requires you to add both the media-query mixin (the media-breakpoint-up) as well as the column mixin (the make-col(*)). I find this to be much more preferable than NOT having it there and stuffing it into the HTML.
Turning off the CSS
Since I’m talking about eliminating the CSS and the usage of the classes in the HTML, we need to turn it off. Easy.
In the Bootstrap _variables.css file, there is a single variable that can be set to control if the grid classes get output in the CSS:
$enable-grid-classes: true !default;
If this is set to true, you get ALL the CSS classes seen in this Gist. If you set it to false, NONE of the CSS for the grid is generated — all if your grid behavior is controlled via mixins inside of specific classes. Create a new variable in your own variables override file, and place it before the Bootstrap import (so it overrides the !default):
$enable-grid-classes: false;
Containers and Rows
One nitpick with the above is that often, containers and rows don’t really have their own semantic names. You can do some semantic gymnastics and try and name them all, but often just including those as your ONLY non-semantic grid elements isn’t that bad. It adds very, very little to your compiled CSS, and lets you keep these global, structural elements named something that makes sense.
Since the .container and .row won’t be output by the compile (as noted above), I just recreate them, usually with a more unique name so that CSS styles don’t leak out, or leak in. It looks like this:
.grid-container {
@include make-container();
@include make-container-max-widths;
}
The above code will add the max-widths (the breakpoint pixel values) at all breakpoints. If you wanted to just have the max-widths from “md” up, you would create it like this:
.grid-container {
@include make-container();
@include media-breakpoint-up(md) {
@include make-container-max-widths;
}
}
This will give you the “full width fluid” container up until the “md” breakpoint. Great for when you want your grid to just be fluid on phones and tablets.
Now you have just one CSS class for your containers. The same can be done for the .row now:
.grid-row {
@include make-row();
}
It’s not a perfect solution, but it’s drastically better than including the entire grid CSS. If you want to get fancy, you can make anything you want a .container or .row:
.tangerine {
@include make-container();
@include make-container-max-widths;
}.pineapple {
@include make-row();
}
Then in your HTML, you just wrap all your rows in .tangerine, and all your columns in .pineapple.
The containers and rows do lean away from semantic purism, but perfect is the enemy of good. By using the above, you add 38 lines of uncompressed CSS to your final compiled code, as opposed to 2000.
Happy Mixin-ing
I hope this helps clear up how to create your grid without the verbose, tightly coupled, non-semantic HTML classes.
Read Bootstrap’s grid documentation here, which touches on the above
Bonus Content: Customizing Grid Columns, Breakpoints, Gutters
One last thing: the actual grid parameters are controllable by variables in the SCSS as well.
Grid breakpoint values:
$grid-breakpoints: (
xs: 0,
sm: 576px,
md: 768px,
lg: 992px,
xl: 1200px
) !default;
Container max-widths:
$container-max-widths: (
sm: 540px,
md: 720px,
lg: 960px,
xl: 1140px
) !default;
Number of columns and their gutter width:
$grid-columns: 12 !default;
$grid-gutter-width: 30px !default;
By changing these, you can customize the grid to anything you need. Want 17 columns? Done. 100 columns? Easy. 20px gutters instead of 30? A 1-character change. Just note the use of !default when you override these.
But before you go…
You should give this article maximum claps 👏, and you should follow me Erik Flowers on Medium (and on Twitter — @erik_flowers).