Playing with variable flex grid

Playing with variable flex grid

In my recent experiments, I've been using Bootstrap 5 grid to create some layouts. And while it works perfectly, it has two problems:

  • It's a bit verbose
  • It's not exactly lightweight (51kb minified)

On top of that, it's using utility classes all over the places, a bit like Tailwind, and I'm not a big fan of having lots of classes everywhere. Actually, I believe that utility classes should be html attributes in an ideal world. So instead of having something like...

<div class="row gx-xl-5">
  <div class="col-md">
    <div class="mt-5 mb-3">

I'd like to see something like

<div grid="md" gx="5">
  <div>
    <div mb="3" mt="5">

And while you can certainly use javascript to iterate over elements and do something with these attributes, it wouldn't solve the main issue: you would still rely on classes at the end of the day.

You could also target these attributes in css, but you would have to target each possible value like

[mb="1"] { margin-bottom: 1rem }
[mb="2"] { margin-bottom: 2rem }

etc... when in reality, we should be able to use the value of the attribute itself.

[mb] { margin-bottom: calc(1rem * attr(mb)); }

But the attr() is only working for the content property so far. Maybe one day...

A strange idea

Now maybe if we mix two things, we can get something interesting:

  • Custom elements
  • Css variables

By using these two, it's easy to simulate the usage of the attr() function on attributes. Simply watch properties, and set inline css variables matching the value. What's even better, is that you can add some logic to it (only watch specific attributes, aliases certain properties (like start => flex-start).

With this, you can create a grid system that avoids a lot of boilerplate code

So we end up with something like this

flex-grid {
  --xs: 1;
  --sm: 1;
  --md: 1;
  --xl: 1;
  --xxl: 1;
  --gx: 1;
  --gy: 1;
  --align: stretch;
  --justify: center;
  --direction: row;
  --wrap: wrap;
  --mw: 10ch;
  --unit: 1.5rem;
  position: relative;
  display: flex;
  flex-direction: var(--direction);
  flex-wrap: var(--wrap);
  justify-content: var(--justify);
  align-items: var(--align);
  margin-top: calc(-1 * var(--unit) * var(--gy));
  margin-left: calc(-1 * var(--unit) * var(--gx));
}
flex-grid > * {
  box-sizing: border-box;
  display: var(--xs-d, "block");
  order: var(--o, 2);
  max-width: var(--w, 100%);
  min-width: var(--mw, 10ch);
  flex: var(--grow, 1) 0 0%;
  align-self: var(--align, "auto");
  margin-top: calc(var(--unit) * var(--gy));
  margin-bottom: 0;
  margin-left: calc(var(--unit) * var(--gx));
  margin-right: 0;
}
flex-grid > *[col] {
  flex: 0 0 auto;
  width: calc(100% / var(--col) - (var(--unit) * var(--gx)));
}

As you can see, it uses the calc function and css variables in order to compute column sizes. No need for actual classes like col-4, col-md-4 etc... you can do

<flex-grid gx="3">
  <div col="2">1 of 2</div>
  <div col="2">2 of 2</div>
</flex-grid>

Isn't that neat? I think so!

You can see the full demo in the codepen below.

Results

With this, we end up with a similar feature set to Bootstrap Grid. It works on all modern browsers. And it weighs only 4kb + 2kb of JS... so that seems like a worthy trade off even if I'm not sure about the actual performance of using js to set inline vars and using calc everywhere. If you find out any issue with this approach, let me know!

Everything is available here: github.com/lekoala/flex-grid