Using CSS grid to generate layouts in a design provides developers with value, and makes things a lot easier for developers, but there are still some problems or downsides of using the CSS grid systems.
The main reason designers and web developers use CSS grid systems is to make the generation of layouts easier and faster. But the question is “can the generation of layouts be made easier and faster, and still overcome these problems?”.
Fortunately for us, the answer is “Yes!’, all thanks to CSS preprocessors such as LESS and SASS/SCSS that can work with CSS variables, and perform calculations and output CSS via ‘mixins’.
Outline
- How CSS Grid System Works
- Some Downsides Of Using CSS Grids
- High amounts of unused code
- Restrictions on layout
- set number of overall columns
- Non semantic markup
- Restrictions on nesting
- An Alternative Solution
- Tackling The Classic Layout
- Easy px to em/rem conversion
- .Row() mixin
- .Cols() mixin
- Adding Padding And Margins
- Adding padding
- Adding wrapping padding
- Adding column gutters
- Nesting And Increasing Layout Complexity
- Conclusion
Prerequisite
- Basic knowledge of CSS and CSS grids
How CSS Grid Systems Work
A grid system is usually made up of a specific number of columns, normally 12 or 16 columns, and the ability to set an element, like the div
or class
element, to a width of X columns.
Using CSS grid systems to create layouts on a website can be really easy, and of course, there are many values you can get from using CSS grid systems, but there are also downsides to using the CSS grid system.
You should not think that using the CSS grid is bad because it isn’t. But as a tool you will be using consistently in your codes, you should be able to know the pros and cons, to determine if it is right for a specific project.
Some Downsides Of Using CSS Grids
Some of the downsides of using CSS Grids include:
High amounts of unused code: Grid classes could be anywhere from 200 to 700 lines of code, but even in a simple design, most of the codes may never be used.
Restrictions on layout: Grids are already calculated, they tend to have set widths that are difficult to change, e.g. column widths, gutter widths. The design becomes restricted to working only those widths.
Set number of overall columns: Already calculated grid classes normally uses 12 or 16 columns. Let’s say you wanted a different number of columns, well, that won’t be possible.
Non Semantic markup: The use of grid classes tends to bring about the placement of numerous non-semantic class names all through a document, such as a “row” or “column”.
Restrictions on nesting: Most times, grid systems can only have columns that are nested inside one another. This restricts the complexity and flexibility of layout generation.
An Alternative Solution
‘Mixins’ are available in both LESS and SASS/SCSS. Mixins are reusable packages of rules that outputs different CSS depending on the information that is passed through them for processing.
If you are not yet familiar with the understanding of what preprocessors are, and how they work with mixins and variables, you can read about it from http://lesscss.org/.
Before we get started, you should know that there are already some existing libraries of LESS and SASS mixins that are already solving the problems if CSS grids, but we are going to be taking a different approach.
Note:
I am going to be writing these mixins in LESS, because it is harder to make LESS perform sophisticated operations than SASS, so it is easier for SASS users to adapt LESS mixins.
Tackling The Classic Layout
Let’s create a layout via LESS mixins, keeping the amount of CSS required to a minimum, with no pixel values, and maintaining a semantic markup. Let’s look at the HTML below:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Classic Layout</title>
<script type='text/javascript' src='js/modernizr.js'></script>
<link rel="stylesheet" href="css/normalize.css">
<link rel="stylesheet" href="css/classiclayout.css">
</head>
<body>
<header>
</header>
<main>
<article>
</article>
<aside>
</aside>
</main>
<footer>
</footer>
</body>
</html>
If you notice, there are no classes used here, only semantic HTML5 tags. I am going to be adding the following LESS code, using mixins and variables:
header, main, footer {
.Row;
background-color: #ccc;
min-height: 200 * @toRems;
}
article {
.Cols( 3 );
min-height: 500 * @toRems;
background-color: #ddd;
}
aside {
.Cols( 1 );
min-height: 500 * @toRems;
background-color: #eee;
}
it will then generate the following lines of CSS:
header,
main,
footer {
max-width: 75rem;
width: 100%;
margin: 0 auto;
background-color: #ccc;
min-height: 12.5rem;
}
header:before,
main:before,
footer:before,
header:after,
main:after,
footer:after {
content: "";
display: table;
}
header:after,
main:after,
footer:after {
clear: both;
}
article {
width: 75%;
float: left;
min-height: 31.25rem;
background-color: #ddd;
}
aside {
width: 25%;
float: left;
min-height: 31.25rem;
background-color: #eee;
}
Check out the result on your browser, you will see a nice layout. Let’s check out the LESS mixins and variables that were used to create the layout you will be seeing in your browser:
//
// Variables for em / rem use
//
@base_px: 16; //set to the most common base px size used in browsers, should generally be left at default
@toRems: (1 / @base_px) + 0rem; //allows you to set values as the default px size to target, which is then converted into scalable rem values.
@toEms: (1 / @base_px) + 0em; //same as above, but with em values
//
// Grid mixins
//
@default-width: 1200 * @toRems;
@default-colspan: 1;
@default-total_cols: 4;
.Row ( @width : @default-width ) {
max-width: @width;
width: 100%;
margin: 0 auto;
// clear at the end of container
&:before,
&:after {
content:"";
display:table;
}
&:after {
clear:both;
}
}
.Cols ( @colspan : @default-colspan; @total_cols : @default-total_cols ) {
width: ( @colspan * (100 / @total_cols) ) + 0%;
float: left;
}
Now, let’s check out each elements one by one, as you can see above:
Easy px to em/rem conversion
//
// Variables for em / rem use
//
@base_px: 16; //set to the most common base px size used in browsers, should generally be left at default
@toRems: (1 / @base_px) + 0rem; //allows you to set values as the default px size to target, which is then converted into scalable rem values.
@toEms: (1 / @base_px) + 0em; //same as above, but with em values
The variables that are in the top section of the code set us up for easily scalable em
or rem
values all through the stylesheet, instead of a px
value. However, when we use these values, it allows us to conceptualize our design in pixels. For example, we do not want the header, main or footer elements to be wider than 1200px
, so instead of specifying 1200px
as the max-width
, we have to covert that 1200px
to rems by multiplying it with the @toREms
variable, for example:
1200 * @toRems;
The output value will be 75rem
, which means that if a browser or user sets the default font-size to something different from the default size of 16px
, then the entire site will scale proportionally. You can also do the same to generate em
values, instead of using the @toEms
variables.
.Row() mixin
@default-width: 1200 * @toRems;
.Row ( @width : @default-width ) {
max-width: @width;
width: 100%;
margin: 0 auto;
// clear at the end of container
&:before,
&:after {
content:"";
display:table;
}
&:after {
clear:both;
}
}
As you can see, the first mixin being used is the .Row()
mixin. Instead of using the “.container” classes, you can call this mixin wherever you want an element to be centered with a maximum width. For our classic layout, we call this mixin on the header
, main
, and footer
elements.
The mixin sets a max-width
with a width
of 100%. This will give us basic responsiveness by making the element automatically adjust to fill the available space when the viewport gets smaller than the max-width
value. It also sets the margin
to 0 auto
making the elements to be automatically centered.
Lastly, it adds the pseudo-elements :before
and :after
and also uses them to automatically clear at the end of an element. This is required so that when adding columns inside the element, their float settings will be cleared.
The mixin accepts one parameter, a @width
value:
.Row (@width : @default-width ) {
The value now gets passed to the max-width
property:
max-width: @width;
If the mixin has a width parameter passed, e.g. .Row(40rem)
, the value will then be applied to the max-width
property.
However, if the mixin is called without passing a parameter, i.e. .Row
, then, the default value will be used. That default value is stored in the @default-width
variable, which is set where you see:
@default-width: 1200 * @toRems;
All this means that the default maximum width of any element this mixin is used on will be 1200px
, converted into 1200rem
. Using this mixin allows you to set any element to be centered at your default max-width
, or any other max-width
you may want to apply. What this means is that you can change the width of the entire site by changing the value of the @default-width
variable. Let’s say for example:
@default-width: 800 * @toRems;
.Cols() mixin
@default-colspan: 1;
@default-total_cols: 4;
.Cols ( @colspan : @default-colspan; @total_cols : @default-total_cols ) {
width: ( @colspan * (100 / @total_cols) ) + 0%;
float: left;
}
Because we are not using fixed pixels, all our column widths will have to be percentage-based, because we want our layout to remain completely flexible. To calculate these percentages, we use .cols()
mixin. According to the values you put into the mixin, a simple formula is used to determine the percentage width value that should be applied to the element. The element’s float
value is set to left
, so that columns will sit side by side (remember that we automatically clear floats that are applied to the columns via the .Row()
mixin).
The .cols()
mixin allows you to specify how many columns wide you want your element to be, the same thing you could if you were using the standard CSS grid system. This is normally done by passing a @colspan
parameter through the mixin, or you can set the default for the mixin via the @default-colspan
variable.
But, unlike most CSS grid systems, you have complete control over how many total that value is relative to, rather than having access to only 12 or 16 columns. You can set the total columns by passing a @total_cols
parameter through the mixin, or you can just the mixin default via the @default-total_cols
variable. Sounds cool right?
In the earlier example of a “classic” layout which I gave above, using CSS grid, the content area was set to 9 out of 12 available columns,(3/4), while the sidebar was set to 3 out of 12 columns(1/4). Well, we do not need all 12 columns, and we do not need to break the layout into twelve, all we need is quarters, because we are trying to set the content area to 3/4 width, and the sidebar to 1/4 of the width. So, all we have to do now is set the value of the @default-total_cols
to a variable of 4. For example:
@default-total_cols: 4;
Then, we are going to use the mixins as we did in our “classic” layout above. The mixins assume that you want your columns to have a possibility of 4 total columns. So, for us to set our article element to 3/4 width, you can do this:
article {
.Cols( 3 );
}
then, if you want to set the sidebar to 1/4 width, you can do this:
aside {
.Cols( 1 );
}
Let’s say you want to use a different number of total columns, we can do this by passing different values through the .cols()
mixin. This will allow us to change the widths of the article and aside elements to any we want. Example:
article {
.Cols( 7, 11 ); // sets this element to span 7 of a total 11 columns
min-height: 500 * @toRems;
background-color: #ddd;
}
aside {
.Cols( 4, 11 ); // sets this element to span 4 of a total 11 columns
min-height: 500 * @toRems;
background-color: #eee;
}
Awesome right?
Adding Padding And Margins
When you begin adding content inside your container, you definitely would want to have the ability to control the spacing around it. There are ways you can control your spacing, and the best to use depends on what you are trying to achieve with the specific design you are trying to create.
Adding Padding
One of the easiest ways you can control your spacing is by using the simple addition of padding parameters to both .Row()
and .Cols()
mixins. We are going to adjust our mixin code as shown below:
@default-padding: 0;
.Row ( @width : @default-width; @padding: @default-padding; ) {
max-width: @width;
width: 100%;
margin: 0 auto;
padding: @padding;
// clear at the end of container
&:before,
&:after {
content:"";
display:table;
}
&:after {
clear:both;
}
}
.Cols ( @colspan : @default-colspan; @total_cols : @default-total_cols; @padding: @default-padding; ) {
width: ( @colspan * (100 / @total_cols) ) + 0%;
float: left;
padding: @padding;
}
We can now add padding to our header, article, aside, and footer elements. Note, we are also going to be setting the default box-sizing
property all through the design to border-box
so, padding does not get included in any browser calculation of the width of elements:
* {
box-sizing:border-box;
-moz-box-sizing:border-box;
}
header, footer {
.Row (
@padding: 20 * @toRems;
);
background-color: #ccc;
min-height: 200 * @toRems;
}
main {
.Row;
background-color: #ccc;
}
article {
.Cols (
@colspan: 3;
@padding: 10 * @toRems 20 * @toRems;
);
min-height: 500 * @toRems;
background-color: #ddd;
}
aside {
.Cols (
@colspan: 1;
@padding: 10 * @toRems 20 * @toRems;
);
min-height: 500 * @toRems;
background-color: #eee;
}
This will give us padding around each of our pieces of content, as shown below:
Adding Wrapper Margins
In some cases, the spacing that is intended to be added to a design is on the outside of an element, instead of the inside, for example, when you want a site’s background to show through between the elements. For this to happen, we have to make further additions to bot the .Row()
and .Cols()
mixins.
To get started, let’s allow spacing to be added above and below the elements that have our .Row()
mixin applied to them. This can be done by simply replacing the existing margin
property value with a variable that can be sent as a parameter all through the mixin.
@default-row_margin: 0 auto;
.Row ( @width : @default-width; @padding: @default-padding; @margin: @default-row_margin; ) {
max-width: @width;
width: 100%;
margin: @margin;
padding: @padding;
// clear at the end of container
&:before,
&:after {
content:"";
display:table;
}
&:after {
clear:both;
}
}
As you can see, the default value for the row margin is still set to 0 auto
, therefore, if no parameter is passed, the mixin will still center the element automatically.
If we pass an @margin
value through the mixin:
header, footer {
.Row (
@padding: 20 * @toRems;
@margin: 10 * @toRems auto;
);
background-color: #ccc;
min-height: 200 * @toRems;
}
…we are going to be able to add vertical spacing as see below:
Also, if you do not want your element to center anymore, all you have to do is pass a @margin
value of 0 auto 0 0
to left align an element, or you can do 0 0 0 auto
to right align.
Adding Column Gutters
One of the common features of CSS grid systems is its ability to add gutters between the columns, i.e. margins that apply between each columns but not to the outside of the outermost columns. Once again we can add this functionality with some additions to the Cols()
mixin:
@default-gutter: 0;
.Cols ( @colspan : @default-colspan; @total_cols : @default-total_cols; @padding: @default-padding; @gutter: @default-gutter; @edge: false; ){
@total_gutter: (@total_cols - 1) * @gutter;
@spanned_gutters: (@colspan - 1) * @gutter;
width: ( @colspan * ( (100 - @total_gutter) / @total_cols) ) + @spanned_gutters + 0%;
float: left;
padding: @padding;
.IfEdge (@edge; @gutter);
}
.IfEdge ( @edge; @gutter; ) when (@edge = false) {
margin-right: @gutter + 0%;
}
.IfEdge ( @edge; @gutter; ) when (@edge = true) {
margin-right: 0;
}
Now, the mixin does two extra things;
First, it checks for a value via the new @gutter
parameter and goes on to factor it into the width calculation for the column.
Note: the @gutter
****value should be a number used as a percentage value. For example 2 for a 2% gutter.
Secondly, it goes on to check the new @edge
variable to see is set to true
or false
. If it is set to false, the value of the @guttter
parameter is added to the right margin as a percentage. IF it is set to true, the right margin is set to 0
. This will allow you to specify where a column is at the edge of your layout and therefore, should not have an applied gutter.
In the example I will be showing you below, I will be showing you the effect of this change on the mixin. I have added two extra article
element to our HTML. Also, I have adjusted the LESS for the article
and aside
elements, as shown below;
article {
.Cols (
@colspan: 1;
@padding: 10 * @toRems 20 * @toRems;
@gutter: 1; //include a gutter of 1%
);
min-height: 500 * @toRems;
background-color: #ddd;
}
aside {
.Cols (
@colspan: 1;
@padding: 10 * @toRems 20 * @toRems;
@gutter: 1; //include a gutter of 1%
@edge: true; //this is the column on the edge so don't set a right margin
);
min-height: 500 * @toRems;
background-color: #eee;
}
the result is this;
To allow for control over vertical margins on columns, all we have to do is include some additional parameters in the mixin as shown below:
@default-margin_top: 0;
@default-margin_bottom: 0;
.Cols ( @colspan : @default-colspan; @total_cols : @default-total_cols; @padding: @default-padding; @gutter: @default-gutter; @edge: false; @margin_top : @default-margin_top; @margin_bottom : @default-margin_bottom; ){
@total_gutter: (@total_cols - 1) * @gutter;
@spanned_gutters: (@colspan - 1) * @gutter;
width: ( @colspan * ( (100 - @total_gutter) / @total_cols) ) + @spanned_gutters + 0%;
float: left;
padding: @padding;
.IfEdge (@edge; @gutter; @margin_top; @margin_bottom; );
}
.IfEdge ( @edge; @gutter; @margin_top; @margin_bottom; ) when (@edge = false) {
margin: @margin_top @gutter + 0% @margin_bottom 0;
}
.IfEdge ( @edge; @gutter; @margin_top; @margin_bottom; ) when (@edge = true) {
margin: @margin_top 0 @margin_bottom 0;
}
The top and bottom margin settings can now be included when we use the .Cols()
mixin, as shown below:
article {
.Cols (
@colspan: 1;
@padding: 10 * @toRems 20 * @toRems;
@gutter: 1;
@margin_top: 20 * @toRems;
@margin_bottom: 30 * @toRems;
);
min-height: 500 * @toRems;
background-color: #ddd;
}
aside {
.Cols (
@colspan: 1;
@padding: 10 * @toRems 20 * @toRems;
@gutter: 1;
@edge: true;
@margin_top: 20 * @toRems;
@margin_bottom: 30 * @toRems;
);
min-height: 500 * @toRems;
background-color: #eee;
}
…this would add vertical margins to the columns, as shown in the example below:
Nesting And Increasing Layout Complexity
To add another level of complexity to our example layout, we have to increase the number of article
elements to 6 and place them inside a section
element wrapper, then we can apply our mixins to create the layout in the example below:
I have made changes to the HTML to get the layout above, as you can see below:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Classic Layout</title>
<script type='text/javascript' src='js/modernizr.js'></script>
<link rel="stylesheet" href="css/normalize.css">
<link rel="stylesheet" href="css/classiclayout.css">
</head>
<body>
<header>
<h1>Site Title</h1>
</header>
<main>
<section>
<h1>Latest Articles</h1>
<article>
<h1>Article Title</h1>
<p>...</p>
</article>
<article>
<h1>Article Title</h1>
<p>...</p>
</article>
<article>
<h1>Article Title</h1>
<p>...</p>
</article>
<article>
<h1>Article Title</h1>
<p>...</p>
</article>
<article>
<h1>Article Title</h1>
<p>...</p>
</article>
<article>
<h1>Article Title</h1>
<p>...</p>
</article>
</section>
<aside>
<p>...</p>
</aside>
</main>
<footer>
<p>Example Footer</p>
</footer>
</body>
</html>
We are also going to change the LESS code we are using to control the layouts as shown below:
header, footer {
.Row ( @padding: 20 * @toRems; @margin: 10 * @toRems auto; );
background-color: #ccc;
min-height: 100 * @toRems;
}
main {
.Row;
}
section {
.Cols ( @colspan: 3; @padding: 10 * @toRems 20 * @toRems; @gutter: 1; );
background-color: #ddd;
}
aside {
.Cols ( @colspan: 1; @padding: 10 * @toRems 20 * @toRems; @gutter: 1; @edge: true; );
min-height: 500 * @toRems;
background-color: #eee;
}
article {
.Cols ( @colspan: 1; @total_cols: 3; @padding: 0 20 * @toRems 20 * @toRems 20 * @toRems; @margin_bottom: 20 * @toRems; @gutter: 2; );
background-color: #eee;
&:nth-of-type(3n) {
margin-right: 0;
}
}
As you can see above, the section
element has been set to take up 3 columns of the default of 4 total columns(3/4). Next to the section
is the aside
element, which is set to 1 column of 4(1/4). The section
element acts as a wrapper for the article. Since the article elements are now nested, they can have all the new column width applied to them, and they will now each take up a percentage of their parent element’s interior. Therefore, each is set to 1 column out of 3, with a gutter of 2%.
All third article is identified by using the :nth-of-type(3n)
selector and set to have no right margin/gutter.
Conclusion
We have gone through so many details in this article on how the .Row
and .Cols
mixins work so that you can have an understanding, for you to be able to make further additions and modifications if you want. The result of all we have covered is actually a simple and easy-to-use layout system.
By using just two mixins, you can now create highly complex, but scalable and flexible layouts, without restrictions. You can also use any total number of columns you feel like, width of gutters, and you can nest your grids as much as needed, etc.
Top comments (3)
Most of the CSS grid downsides you mention are simply not true. Instead of bashing it, you should use some time to actually learn how to use it. CSS grid allows you to easily define fully responsive layouts with very little code. Maybe you are mistaken Bootstrap grid layout with CSS grid? Also, using "display: table" and "float" for main page layout might have been a good idea sometime in the last century, but definitely is not nowadays.
Recommended resources to learn CSS grid:
Common Responsive Layouts with CSS Grid
Learn CSS Grid
A Complete Guide to Grid
I'm about to comment the same, it seems the author just showing off his LESS skills to reinvent the grid system instead of understanding and using it. I'm sure even we don't use the css grid system use cases like templates, subgrid etc. most of the times, yet we can achieve what we want in less than 20-30 lines and not 200-300 lines.
Sorry, but floats for layout at this point in time? That just over complicates everything. If you don't want to use grid, use flexbox.
Also, your points on grid are not accurate. In a custom site, all your code would be used. You can adapt the sizes of each grid column, and even then, you can fit grid content into multiple columns or rows. It is very flexible.
Changing the number of columns is possible. Even then, you can make it so items can span multiple columns so you can have different visual column designs without adding a new container (ie. A 12 column can be 1, 2, 3, 4, 6, 7, 12 columns just from where you assign item).
I suggest you look into it some more.
css-tricks.com/snippets/css/comple...
developer.mozilla.org/en-US/docs/L...
developer.mozilla.org/en-US/docs/W...
learncssgrid.com/