Gutenberg short code for making columns

One of the things I like about Gutenberg (the static site generator used for this blog) is the support for short codes. Short codes are essentially tiny Tera templates you can call in the markdown files for the content. This immediately lead me to look for a way to make short codes for multiple columns of content.

My first attempts looked something like this:

{% columns() %}
{% column() %}
Content in the left column
{% end %}
{% column() %}
Content in the right column
{% end %}
{% end %}

It didn't work. Turns out the short code parser in Gutenberg doesn't support nested short codes. The reason I wanted to nest two short codes is that the essential bit in embedding some columns in the content is that you need two levels of divs: An outer div as a container for the columns, and inner divs wrapping each column.

So, I was stuck. Later, when browsing the Tera docs for an unrelated reason, I was that it has a split method. That gave me an idea. I don't really need an inner short code, I just need a way to tell apart the content for different columns. What I could do is have one short code that splits the body of the short code based on some specified string.

The use of the new columns short code looks like this:

{% columns() %}  

Content in the left column

[[gutter]]

Content in the right column

{% end %}

It splits the body into columns any time it comes across [[gutter]]. One weakness of this is that there is no way to escape [[gutter]] so you can't write about it in a column. There is also no way to specify different column sizes. The code for the short code ended up being short and sweet:

<div class="columns">
  {% set cols = body|split(pat="[[gutter]]") %}
  {% for col in cols %}
    <div class="column">
      {{col}}
    </div>
  {% endfor %}
</div>

Then all that is needed is some CSS to layout the classes. Here is an example using flexbox to be compatible with IE11:

$gutter: 1rem;
$min-viewport-size: 600px;

.columns {
  display: flex;
  flex-direction: column;
}

.column {
  margin-bottom: $gutter;
  flex-grow: 1;
  flex-basis: 0;
  &:last-child {
    margin-bottom: 0;
    margin-right: 0;
  }
}

@media (min-width: $min-viewport-size) {
  .columns {
    display: flex;
    flex-direction: row;
  }
  
  .column {
    margin-bottom: 0;
    margin-right: $gutter;
  }
}

It uses two variables:

  • $gutter: Space between column.
  • $min-col-size: Min viewport width for switching from single column to multi column mode.

If IE11 support doesn't matter, then one could, of course, do something slick with CSS grids:

.columns {
  display: grid;
  grid-gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
}

That is a lot less CSS to write. This one spills columns over onto the next row to avoid them being narrower than 260px. It end up looking like this:

Bacon ipsum dolor amet leberkas ham beef ribs bresaola drumstick frankfurter cow tail. Sirloin cow picanha short loin short ribs turkey prosciutto landjaeger brisket cupim tail pig strip steak. Alcatra turducken meatloaf picanha, venison chuck pork chop ham hock. Strip steak sirloin pork chop biltong. Picanha chuck jowl turkey, doner salami ground round corned beef cow tri-tip ham brisket biltong.
Burgdoggen strip steak frankfurter tenderloin capicola, sirloin ball tip andouille tri-tip rump chicken. Shankle pork belly boudin, turducken jerky andouille bacon tenderloin ham hock capicola jowl filet mignon bresaola short loin. Bacon meatball cow sausage meatloaf. Chicken capicola cow shank frankfurter.
Spare ribs alcatra short ribs doner drumstick biltong ribeye pork chop ball tip frankfurter picanha pork loin short loin tail pancetta. Picanha ground round spare ribs pork belly landjaeger burgdoggen bresaola salami. Pig hamburger strip steak short loin doner. Short ribs t-bone shankle kevin corned beef. Fatback strip steak meatloaf, short loin ground round pancetta sirloin landjaeger. Ham chuck pig andouille ball tip kielbasa cupim sirloin jowl meatloaf chicken shankle.