Skip to content

Satisfying My Designer’s Need For Column-Based Grids

Published: at 05:05 AM

Table Of Content

Requirements

It’s pretty common for designers to base their design on column-based grids, Bootstrap blessed us with container, .container-fluid and col-lg – blurry memories. And because it’s too easy, our fellow designers like complicating things by introducing requirements like:

And usually they don’t acknowledge wider screens like TVs or developers’ monitors 😅 (but don’t tell them plz).

Gimme the CSS already!! – First Batch

Okay, okay! I get it, my dev life is boring to you, fine – it is to a lot of people frankly. There you go:

<div class="grid">
  <div class="column span-1 tablet:span-2 desktop:span-4">…</div>
  <div class="column span-1 tablet:span-2 desktop:span-4">…</div>
  <div class="column span-1 tablet:span-2 desktop:span-4">…</div>
</div>

<style>
  .grid {
    --column-count: 4;
    display: grid;
    /* That’s the magic */
    grid-template-columns: repeat(var(--column-count), 1fr);
    gap: 1rem; /* apply a gap if need be */
  }

  @media (min-width: 600px) {
    .grid { --column-count: 8; }
  }
  @media (min-width: 1200px) {
    .grid { --column-count: 12; }
  }

  .column { --span: 1; grid-column-end: span var(--span); }
  .span-2 { --span: 2; }
  .span-3 { --span: 3; }
  .span-4 { --span: 4; }
  /* etc. */
  .tablet\:span-2 { --span: 2; }
  .tablet\:span-3 { --span: 3; }
  /* etc. */
  .desktop\:span-2 { --span: 2; }
  .desktop\:span-3 { --span: 3; }
</style>

Making It Progressive

In the flavor upper, you need to specify the column span for every viewport unless it’s 1, boring. There is a way to specify the column size for mobile and change it only for wider viewports:

<div class="grid">
  <div class="column 1/4">…</div>
  <div class="column 1/4 tablet:3/8">…</div>
  <div class="column 1/4 tablet:3/8 desktop:5/12">…</div>
</div>

<style>
  .grid {
    --column-count: 4;
    display: grid;
    grid-template-columns: repeat(var(--column-count), 1fr);
    gap: 1rem; /* apply a gap if need be */
  }

  @media (min-width: 600px) {
    .grid { --column-count: 8; }
  }
  @media (min-width: 1200px) {
    .grid { --column-count: 12; }
  }

  .column {
    --column-size: 0;
    --span: calc(var(--cell-size) * var(--cell-count));
    grid-column-end: span var(--span);
  }
  /* selector for `column 1/4 */
  .column.\31 \/4 { --column-size: 1/4; }
  .column.\32 \/4 { --column-size: 2/4; }
  .column.\33 \/4 { --column-size: 3/4; }
  .column.\34 \/4 { --column-size: 4/4; }
  /* etc. */
  .column.tablet\:1\/8 { --column-size: 1/8; }
  .column.tablet\:2\/8 { --column-size: 2/8; }
  /* etc. */
  .column.desktop\:1\/12 { --column-size: 1/12; }
  .column.desktop\:2\/12 { --column-size: 2/12; }
</style>

You can even go attribute-based

I ❤️ attribute-based style for their expressiveness – imo it’s like classes with options. The best thing is that you can add intellisense yourself!

<div grid>
  <div grid-col="1/4">…</div>
  <div grid-col="1/4 tablet:3/8">…</div>
  <div grid-col="1/4 tablet:3/8 desktop:5/12">…</div>
</div>

<style>
  [grid] {
    --column-count: 4;
    display: grid;
    grid-template-columns: repeat(var(--column-count), 1fr);
    gap: 1rem; /* apply a gap if need be */
  }

  @media (min-width: 600px) {
    [grid] { --column-count: 8; }
  }
  @media (min-width: 1200px) {
    [grid] { --column-count: 12; }
  }

  [column] {
    --column-size: 0;
    --span: calc(var(--cell-size) * var(--cell-count));
    grid-column-end: span var(--span);
  }
  /* See footer notes for attribute selectors */
  [grid-col^="1/4"] { --column-size: 1/4; }
  [grid-col^="2/4"] { --column-size: 2/4; }
  [grid-col^="3/4"] { --column-size: 3/4; }
  [grid-col^="4/4"] { --column-size: 4/4; }
  /* etc. */
  [grid-col~="tablet:1/8"] { --column-size: 1/8; }
  [grid-col~="tablet:2/8"] { --column-size: 2/8; }
  /* etc. */
  [grid-col$="desktop:1/12"] { --column-size: 1/12; }
  [grid-col$="desktop:2/12"] { --column-size: 2/12; }
</style>

Source: MDN

[attr^=value] represents elements with an attribute name of attr whose value is prefixed (preceded) by value.
Examples: [attr^="value"]
– Will match: <el attr="value" />
– Won’t match: <el attr="no value" />

[attr$=value] represents elements with an attribute name of attr whose value is suffixed (followed) by value.
Examples: [attr$="value"]
– Will match: <el attr="value" />
– Won’t match: <el attr="value invalid" />

[attr~=value] represents elements with an attribute name of attr whose value is a whitespace-separated list of words, one of which is exactly value.
Examples: [attr~="value"]
– Will match: <el attr="some value" /> or <el attr="this value is great" />
– Won’t match: <el attr="no-value is great" />

[attr*=value] represents elements with an attribute name of attr whose value contains at least one occurrence of value within the string.
Examples: [attr*="value"]
– Will match: <el attr="no-value-is-great" />