For many years, the popular methods of layout have involved float
or inline-block
. A less popular alternative involved using display: table;
. All three techniques have caveats and limitations, or involve ‘hacks’ – or workarounds that others consider to be hacks – and this is because these CSS properties weren’t intended to handle robust layouts.
I sought a better alternative to these classic layout methods. As this is 2015, a time where some really incredible things can be done with pure CSS3 – surely an improved grid system should be trivial?
My personal preference has been inline-block grids for years, but some other developers despise the commented-out white-space, and that can be a deal breaker. There are workarounds, but none of them are ideal: you can hackily remove the white-space with negative word-spacing
set on the container, which is liable to breakage across different browsers with different pixel rounding; you can set the font-size
on the container to zero and then back to normal within the cells; or you can set the grid container to use a blank font (where spaces have no width), which is a pretty cool technique but does mean sending an additional font request(s) over the wire.
With floats, I found the lack of vertical alignment and the loss of a pseudo-element to a clearfix more frustrating than having to comment out some white-space.
Table grids have some pretty impressive features – you can arbitrarily control vertical alignment and the cells will be of equal height automatically – but the one-container-per-row ruins it for me – it means that multiple breakpoints require grids nested in grids which can quickly get out of hand.
So here’s where we are so far, we need our to new grid system to have the following capabilities:
We have at our disposal a pair of new CSS technologies that can help us achieve what we need here:
Flexbox can do all of these, and more! Hang on though:
This is still a valid concern, but support is getting there. Within GRIT, we support the three latest major versions of the most popular browsers. And at the time of writing, needing to support IE9 means we can’t use flexbox without polyfilling it in some capacity. However, with Microsoft Edge coming out soon, this will no longer be the case.
Beyond IE9 we’re almost there bar the odd inconsistency: IE10 sometimes needs a little attention in order to play ball.
Unfortunately that’s true. As this article and others state, Flexbox is more intended towards smaller components and chunks of layout, like a stretchy navbar, for example.
That said, inline-block, float and table were not intended to solve layout problems either, so I think flexbox can’t be written off here. One of my first concerns was performance: Google’s rendering performance documentation states that the newest implementation of flexbox is considerably faster than float; but conversely an article from front-end developer Ben Frain says that flexbox is slower than display: table – but not enough to be concerned about.
From CSS-Tricks:
The grid property in CSS is the foundation of Grid Layout. It aims at fixing issues with older layout techniques like float and inline-block.
Sounds very promising. However:
The stumbling block for grid comes in the form of support. It’s currently only supported in IE10+. I’ve spent some time experimenting with it and it looks incredibly promising, but we need something we can use in production and grid is not our answer; not yet, and likely not for some time. Watch this space.
Flexbox is the one worth exploring at this point, but it’s important to be aware of the above regarding support, performance and general best practice. As more articles and code examples surface this will hopefully become clearer over time.
[code lang=”scss”]// Define default grid gutter
$grid-gutter-width: 20px !default;
// Create grid mixins
@mixin grid($gutter-width: $grid-gutter-width;) {
list-style: none; // Enable usage on <ul> and <ol>
margin: 0 0 0 (0 – $gutter-width); // Remove margin and set negative gutter-margin
padding: 0; // Remove unwanted padding
display: flex; // Magic
flex-wrap: wrap; // Allow cells to overflow over multiple rows
}
@mixin grid-item($gutter-width: $grid-gutter-width) {
width: 100%; // Have cells full-width by default
padding-left: $gutter-width; // Add the gutters
}
/**
* Basic Grid object
*/
.grid {
@include grid;
}
.grid-item {
@include grid-item;
}[/code]
My preference for handling grid sizing is to use multiple classes assigned to breakpoints, but we should also allow the flexibility for clean markup, so let’s create a mixin that generates the CSS we need here:
[code lang=”SCSS”]@mixin grid-width($fraction) {
@if ($fraction == auto) {
flex: 1 1 0%; // Harness flexbox’s automatic sizing if wish
} @else {
width: percentage($fraction); // If not, use a fixed percentage width
}
}
// Usage: @include grid-width(1/2);[/code]
Here, I add some width utility classes, nested in breakpoints with the mixin like so:
[code lang=”SCSS”].u-auto {
@include grid-width(auto);
}
.u-1/2 {
@include grid-width(1/2);
}
@media screen and (min-width: 768px) {
.u-auto-desk {
@include grid-width(auto);
}
.u-1/2-desk {
@include grid-width(1/2);
}
}[/code]
And so on. That gives us the flexibility to control each cell over a variety of breakpoints, so our HTML ends up as follows:
[code lang=”HTML”]
<div class=”grid”>
<div class=”grid-item u-1/2 u-1/3-desk”>
Half by default, third on desktop-sized devices
</div>
<div class=”grid-item u-1/2 u-auto-desk”>
Half by default, fills up remaining space on desktop-sized devices
</div>
</div>
[/code]
With this, the markup can quickly become a mess of classes. Creating clean HTML is easy though:
[code lang=”SCSS”]main {
@include grid;
}
article {
@include grid-item;
@media screen and (min-width: 768px) {
@include grid-width(auto);
}
}
aside {
@include grid-item;
@media screen and (min-width: 768px) {
@include grid-width(1/3);
}
}[/code]
[code lang=”HTML”]<main>
<article>
Full-width by default, auto-sized on desktop-sized devices
</article>
<aside>
Full-width by default, third on desktop-sized devices
</aside>
</main>[/code]
Alternatively, you could create silent classes and @extend
those to keep your code DRY:
[code lang=”SCSS”]%grid {
@include grid;
}
%grid-item {
@include grid-item;
}
// Etc…
main {
@extend %grid;
}
article {
@extend %grid-item;
@extend %u-desk-auto;
}
// Etc…[/code]
So we have our basic responsive grid setup, let’s get on to the required features.
The align-items
property is set to stretch
by default which will enable us to create equal-height columns – we’ll come to those shortly.
[code lang=”SCSS”].grid {
justify-content: center | flex-end; // center or right alignment respectively
}[/code]
You can also arbitrarily align individual cells in the same fashion:
[code lang=”SCSS”].sidebar {
@include grid-item;
align-self: flex-start | center | flex-end;
}[/code]
[code lang=”SCSS”].grid {
flex-direction: row-reverse;
}[/code]
Flexbox makes discrete cell ordering very easy, in the following example the .content
element will appear after the .sidebar
regardless of its position in the source:
[code lang=”SCSS”].content {
@include grid-item;
order: 2
}
.sidebar {
@include grid-item;
order: 1;
}[/code]
We touched on the align-items: stretch;
property above which will help us with equal-height columns. If we were to apply a background-color to our grid-cells, they would already be of equal height:
[code lang=”SCSS”].grid-item {
@include grid-item;
background-color: #efefef;
}[/code]
But there’s a problem – the gutters are generated using padding, which means they too have the background colour.
We can get around this by adding display: flex;
to the .grid-item
, which will cause its child elements to be stretch to the full height of that cell:
[code lang=”HTML”]
<div class=”grid-item”>
<div class=”box”>
I will fill the available height of this row
</div>
</div>
[/code]
This works, but it’s not ideal, we have surplus markup – and the display: flex;
on the .grid-item
element means that multiple children within the cell will not display as expected.
We’d love to be able to add the .box
class to the cell itself, but the problem is the padding. If margin were used for the gutters instead, it would be ideal, but we’ll have to rethink how our grid sizing works. Let’s revisit two of the original mixins:
[code lang=”SCSS”]@mixin grid-item($gutter-width: $grid-gutter-width) {
width: 100%;
margin-left: $gutter-width; // Use margin for the gutters
}
@mixin grid-width($fraction, $gutter-width: $grid-gutter-width) {
@if ($fraction == auto) {
flex: 1 1 0%;
} @else {
width: calc(#{percentage($fraction)} – #{$gutter-width}); // Use a fixed percentage width, minus the width of the gutter
}
}[/code]
Great, this now works as want it to: we don’t have to nest markup and we can add aesthetic classes to the .grid-item
elements.
So where are we now? We have a powerful, fully-featured and modern grid system with which to layout our websites. It’s a vast improvement on the tech we were battling with before and shoehorning functionality into, but is it best practice?
The answer isn’t clear and will, at this point, be largely down to personal preference. As mentioned above, we have conflicting articles and information regarding the performance of flexbox, and we also have the younger display: grid;
shaping together slowly in the spec.