Formatting Bootstrap Grids with 13+ Columns

I was making a periodic table app today, using rails, vue.js, and bootstrap. I told myself that I could use the practice on bootstrap’s grid system, and sure enough, I ran into things I did not know how to handle; so I fiddled until I made it work.

Here is my story.

Bootstrap Grids

Bootstrap’s got some great column formatting built-in. Once you install it, you can just give divs class names like “row” and “col,” and bootstrap will order them as you need. You can even make different columns different sizes:

<div class="container">
<div class="row">
<div class="col-6">
Big One
</div>
<div class="col-3">
Little One
</div>
<div class="col-3">
Little One
</div>
</div>
</div>

In the above example, the -3 and -6 are not necessary — making the div class = “col” would make the columns automatically evenly sized (this is a new feature of bootstrap 4).

I’ve also added a little CSS to make the outline appear so that the size of the columns is more evident.

You can also create spaces between columns using.offset-md-:

<div class="container">
<div class="row">
<div class="col-6">
Big One
</div>
<div class="col-2">
Little One
</div>
<div class="col-2
offset-md-2">
Little One, Offset
</div>
</div>
</div>

The mathematically-minded may notice that in both of the above examples, my column (and offset) sizes sum to 12. That’s the golden number for bootstrap grids — less than 12, and the columns won’t use the whole page, more than twelve, and they’ll need two rows to fit:

<div class="container">
<div class="row"> <!-- Total: 12 -->
<div class="col-4">1 of 3</div>
<div class="col-4">2 of 3</div>
<div class="col-4">3 of 3</div>
</div>
<div class="row"> <!-- Total: 11 -->
<div class="col-4">1 of 3</div>
<div class="col-4">2 of 3</div>
<div class="col-3">3 of 3</div>
</div>
<div class="row"> <!-- Total: 13 -->
<div class="col-4">1 of 3</div>
<div class="col-4">2 of 3</div>
<div class="col-5">3 of 3</div>
</div>
</div>

You can still use class="col" to get evenly-sized columns of a number by which 12 is not divisible, though, including more than 12 columns. This is important for me, since periodic tables are generally drawn with 18 columns. Unfortunately, though, when the number of columns exceeds 12 (that is, each column is less than a twelfth of the container wide), sizing and offsetting functionalities disappear. Every cell in a periodic table is the same size, so for the purpose of this adventure I do not really care about the former, but the latter is a big deal. The first three rows of my table need to look something like this:

Offsetting is important.

My solution: use ghost columns.

No, it is not elegant. It might even be sloppy. Using Bootstrap to format an 18-column table is a little like buying a house you kind of like and then working up a good sweat through demo and renovation of what the previous owners were just calling a “guest bedroom.” It’s a little grimy, but it beats having to build the entire house yourself.

The Build:

I began by making some ghost columns:

<div class="container">
<div class="row"> <!-- ROW 1 -->
<div class="col">1</div>
<div class="ghost-col">2</div>
<div class="ghost-col">3</div>
<div class="ghost-col">4</div>
<div class="ghost-col">5</div>
<div class="ghost-col">6</div>
<div class="ghost-col">7</div>
<div class="ghost-col">8</div>
<div class="ghost-col">9</div>
<div class="ghost-col">10</div>
<div class="ghost-col">11</div>
<div class="ghost-col">12</div>
<div class="ghost-col">13</div>
<div class="ghost-col">14</div>
<div class="ghost-col">15</div>
<div class="ghost-col">16</div>
<div class="ghost-col">17</div>
<div class="col">18</div>
</div>
<div class="row"> <!-- ROW 4 -->
<div class="col">1</div>
<div class="col">2</div>
<div class="col">3</div>
<div class="col">4</div>
<div class="col">5</div>
<div class="col">6</div>
<div class="col">7</div>
<div class="col">8</div>
<div class="col">9</div>
<div class="col">10</div>
<div class="col">11</div>
<div class="col">12</div>
<div class="col">13</div>
<div class="col">14</div>
<div class="col">15</div>
<div class="col">16</div>
<div class="col">17</div>
<div class="col">18</div>
</div>
</div>

I’ve made row 1 and row 4, since row 4 should just be 18 even cells all the way across. As you can see, my row 1 does not look good.

I want to eventually make the ghosts invisible, but first I want the cells with 1 and 18 in them to look like the corresponding cells on the next row (row 4).

Well, fine. Above, I said that I added some CSS to the .col class to give it visible borders. Here’s some CSS:

<style type="text/css">
.col {
box-shadow:inset 0px 0px 0px 2px #000;
height: 60px;
width: 38px;
background: #ffffff;
padding: 10px;
}
</style>

What happens if I give .ghost-col that same styling? Then the second line of the above block becomes

  .col, .ghost-col {

It looks better, but still wrong:

That’s because my style tags are not the only source of CSS for this thing. Bootstrap is doing something to make my columns fit nicely together (that’s the whole reason I’m using it in the first place), and the automatic styling that it has for .col does not exist for .ghost-col.

So where is this styling?

When you install bootstrap, you do so (usually) by pasting a stylesheet into your file using a <link> tag.

<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">

The full url shown above, will give you something sufficiently illegible to impress your non-tech friends, but if you take out .min, you’ll get something you can read: https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.css

On this page, a CMD+F + “.col {” (for us Mac people — the rest of you, I’m sure you know what to do instead) will show us the formatting we want.

.col {
-ms-flex-preferred-size: 0;
flex-basis: 0;
-ms-flex-positive: 1;
flex-grow: 1;
max-width: 100%;
}

We can paste these lines in as .ghost-col styling (technically we only need the flex-basis and flex-grow here):

<style type="text/css">
.col {
box-shadow:inset 0px 0px 0px 2px #000;
height: 60px;
width: 38px;
background: #ffffff;
padding: 10px;
}
  .ghost-col {
box-shadow:inset 0px 0px 0px 2px #000;
height: 60px;
width: 38px;
background: #ffffff;
padding: 10px;
    flex-basis: 0;            <!-- HERE -->
flex-grow: 1;
}
</style>

I gave .col and .ghost-col their own separate blocks here too. Now we have:

Uhuh? Nice, right?

Okay, now for the invisibility trick. First, delete those numbers in the ghost boxes. We want these boxes free of text.

<div class="ghost-col"></div>

Cool. Now the interior of the boxes is already white, so we just need to get rid of the ghosts’ borders. The hexidecimal for white is #ffffff, (black is #000000), so all we need to do is change our box-shadow setting for .ghost-col:

.ghost-col {
box-shadow:inset 0px 0px 0px 2px #ffffff;
height: 60px;
width: 38px;
background: #ffffff;
padding: 10px;
flex-basis: 0;
flex-grow: 1;
}

And ta-da:

Great. A few minutes later, we’re looking at a bonafide periodic table.

There is one thing, though…

We did way more work than we needed to do. An element can have more than one class, so rather than making a bunch of cells with class="ghost-col", we could have just made 18 columns with class="col" in each row, added another class to the ones we wanted to disappear (class="col ghost"), and given ourselves a much smaller CSS task:

.ghost {
box-shadow: #ffffff;
}

This works just as well, but it’s unideal for my purposes (for yours, it may be fine). I want to be able to grab every cell with a chemical element in it, for future formatting purposes. If I give my ghost cells the class of “col” as well, then each time I do something to the .col styling, I have to undo it for the .ghost styling. I could also be looking forward to a lot more tedium with CSS selectors. Nobody wants that.

So, I did it the long way.