Thoughts about React,Web Development,Performance

June 3, 2021

Polyfilling Flex Gap

Giovanni Benussi
@giovannibenussi

gap is a very useful CSS property that allows to set separation or glutter between children elements in a layout.

.container {
  gap: 1rem;
  display: flex;
  flex-wrap: wrap;
  max-width: 22rem;
}

Produces:

gap is a shorthand to set row-gap (vertical gap) and column-gap (horizontal gap) in one declaration.

.container {
  /* Separate declaration for row and column gap */
  row-gap: 1rem;
  column-gap: 2rem;

  /* Shorthand */
  gap: 1rem 2rem;
}

Unfortunately, gap global support is not very complete, specially in Safari. At the time of this writing, it has 71.56% support due primarily to lack of support in Safari (it started supporting gap for flex containers since iOS 14.5 launched on April 26, 2021).

Important: gap support for flexbox containers is lower than on grid containers, which has a global support of 92.9%

  Gap for flexbox containers

Depending on our target users, they may use old browsers. If you check your website usage and notice that an important part of your users use browsers that don't play well with the gap property on flexbox, you may need to use a polyfill instead.

Simulating Flex Gap

Let's start with the following markup:

<div class='flex-gap-4'>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
</div>

We build a container with 8 children. I've ommited styling classes to keep it simple.

Here's the class of the container:

.flex-gap-4 {
  display: flex;
  flex-wrap: wrap;
}

We defined a flex container that allows elements to wrap.

This produces a grid-like view of four elements per row as shown below:

Now, let's try to add a separation between each item within the container. Let's try to add margin between elements by using the Adjacent Sibling Combinator (* + *):

.flex-gap-4 > * + * {
  margin-left: 1rem;
  margin-bottom: 1rem;
}

Here's how it looks:

It doesn't work because when items span more than one column, from the second item on, there's extra margin to the left. This is because we're applying margin to every children that follows another children. You can read more about the Adjacent Sibling Combinator here.

To fix the issue with the extra margin at the left and top, we can add negative margins to the parent like this:

.flex-gap-4 {
  margin-left: -1rem;
  margin-top: -1rem;
}

Now our layout looks as expected:

Final CSS

Now that we have a working layout with gaps, here's how our code looks like:

.flex-gap-4 {
  display: flex;
  flex-wrap: wrap;
  margin-left: -1rem;
  margin-top: -1rem;
}

.flex-gap-4 > * {
  margin-left: 1rem;
  margin-top: 1rem;
}

You can customize the margins whenever you want to simulate row-gap and column-gap using CSS properties:

.flex-gap {
  display: flex;
  flex-wrap: wrap;
  margin-left: calc(-1 * var(--column-gap, 0));
  margin-top: calc(-1 * var(--row-gap, 0));
}

.flex-gap-4 > * {
  margin-left: var(--column-gap, 1rem);
  margin-top: var(--row-gap, 1rem);
}

Downsides

Border

You can't add border that surrounds the content because inner elements have a margin applied to the left.

Nesting

Since we use margins on both the parent and children elements, if we try to have multiple levels of nesting involving our solution, it won't work because we're trying to modify the margin between elements. Below is an example on this that shows how a little margin is added to the elements.

Conclusion

If you can use gap, go ahead and do it. Your code will be easier to manage and less error-prone. If you can't, make sure that you apply a polyfill that is easy to change in the future so you can evaluate it again in, let's say 1 year or whatever makes sense to you!