CSS frameworks are nice, but...

Over the years we have worked with different frameworks, to add basic layout or themes to our projects. These so called "boilerplates" are often very detailed and deliver quite some weight.

The goal of a framework is, to speed up the development process, while delivering the best compatibility. The drawback is a big code base. Since frameworks are often modular and allow configuration, the integration can take longer than expected. Extra steps might be required, when you want to customize the framework or only need a small part of it.

Who uses bootstrap anymore?

Thankfully, a lot of developers have started writing their own boilerplates. Some are based on popular frameworks, others are written from scratch but they usually have one thing in common. The frameworks deliver static CSS and require a preprocessor, to change attributes.

A simple grid framework might look something like this:

CSS framework

  $columns: 12;
$gutter: 12px;
$padding: 10px;

.column {
  flex-basis: calc(100% / #{$columns});
  margin: 0 $gutter;
  padding: 0 $padding;

  @for $i from 2 through 12 {
    &._#{$i} {
      flex-basis: calc(100% / #{$columns * $i});
    }
  }
}

.grid, .row {
  display: flex;
  flex-wrap: wrap;
}

.row {
  margin: 0 ($padding + $gutter) * -2;
}

Usage

  <div class="grid">
  <div class="column _4"></div>
  <div class="column _4"></div>
  <div class="column _2"></div>
  <div class="column _2"></div>
</div>

To change any attribute in this framework, we need to compile a new version. Using CSS variables, we don't need this step and can remove a majority of the code.

Let's look at something very similar using CSS variables.

CSS framework

  :root {
  --columns: 12;
  --gutter: 12;
  --padding: 10;
}

.column {
  --colspan: 1;
  flex-basis: calc(100% / var(--columns) * var(--colspan));
  margin: 0 calc(var(--gutter) * 1px);
  padding: 0 calc(var(--padding) * 1px);
}

.grid, .row {
  display: flex;
  flex-wrap: wrap;
}

.row {
  margin: 0 calc((var(--gutter) + var(--padding)) * -1px);
}

Usage

  <div class="grid">
  <div class="column" style="--colspan: 4"></div>
  <div class="column" style="--colspan: 4"></div>
  <div class="column" style="--colspan: 2"></div>
  <div class="column" style="--colspan: 2"></div>
</div>

As you can see I chose to set the colspan as an inline style instead of giving the element a classname. There are three reasons for this choice.

  1. inline styles allow a real scope
  2. the cascade will break the grid logic
  3. no cascading (unreliable) classnames (less code)

Writing a lightweight grid framework

Let's take the code from above and think about improvement. Both, the SCSS version and the CSS version produce the same logic but our plain CSS version does not need to be recompiled. We can simply import it and overwrite the :root variables. This is where the cascade comes in handy.

  @import "path/to/grid.css";
:root {
  --gutter: 10;
  --padding: 10;
}

Nesting grids

The examples above follow a very simple logic.

  1. .grid provides the outer element
  2. .column defines the width of an element relative to its parent
  3. .row opens a new grid and resets the gutter and padding.

This means, if we have a grid inside a column with a width of 4, we are actually allowing our grid to be split into 48 columns. Often this is not what we are aiming for

The code below will probably seem familiar.

  <div class="grid">
  <div class="column _12"> 12 columns </div>
  <div class="column _6"> 6 columns</div>
  <div class="column _6">
    <div class="row">
      <div class="column _12"> 6 columns</div>
      <div class="column _6"> 3 columns</div>
      <div class="column _6"> 3 columns</div>
    </div>
  </div>
</div>

You might agree, that this can cause layout issues and there is no real solution to this problem. We have accepted this issue and learned to use grids in this way.

CSS variables can be a big help here. By simply adding one line to our CSS, we can tell a row how many columns are available. This is something that would usually require javascript or large amounts of code and configuration.

  .row {
  --columns: var(--colspan);
}

We can now define columns relative to the outer .grid instead of the parent element.

  <div class="grid">
  <div class="column" style="--colspan: 12"> 12 columns </div>
  <div class="column" style="--colspan: 6"> 6 columns</div>
  <div class="column" style="--colspan: 6">
    <div class="row">
      <div class="column" style="--colspan: 6"> 6 columns</div>
      <div class="column" style="--colspan: 3"> 3 columns</div>
      <div class="column" style="--colspan: 3"> 3 columns</div>
    </div>
  </div>
</div>

Minus minus grid

A while ago I created a very similar grid, which is available on github and npm.

The name is obviously a pun ✨🦄✨

Minus minus grid

--grid
minus-grid

This grid offers some extra features and handles the entire logic including responsive behavior. If you are interested you should give it a try. Just include this code in your next pen:

  @import "https://cdn.rawgit.com/pixelass/minus-grid/v1.2.0/dist/index.css";

.grid {
  --column-margin: 5;
  --column-padding: 5;
}


Conclusion

CSS variables are very powerful and offer some logic we haven't been able to use until now. I am very excited to see how variables will influence open source projects and frameworks.

Before attempting to make use of this technique check the browser support: http://caniuse.com/#feat=css-variables

Browser support (time of writing)

Edge is working on the feature.

Browser support (Edge and IE: no support)


7,107 15 72