Flexbox: when negative margins save the day
Tags: css
Published: 2020-11-02 08:43:04

Gap management with flexbox is not as easy as it seems. Here is a  simple trick I've been using a lot lately.

# The problem

This is our HTML for this demo:

```html
<article>
  <h1>Hello World</h1>
  <ul>
    <li>HTML</li>
    <li>CSS</li>
    <li>JavaScript</li>
    <li>Front-end dev</li>
    <li>Web</li>
  </ul>
  <p>Lorem ipsum...</p>
</article>
```

It's an article with a list of tags. With some basic CSS, here's what it looks like.

{% codepen https://codepen.io/twhite96/pen/dyXdqOL %} 

We want the list of tags to be a flex container, with the possibility to wrap. Here we go!

```css
ul {
  display: flex;
  flex-wrap: wrap;
}

li {
  margin-right: 2em;
}
```

As you can see, I also set some space after each `<li>` element, `2em` being equal to `32px` for common text (with the advantage of being responsive to the user preferences and the font-size of the item itself).

And here's the result:

{% codepen https://codepen.io/twhite96/pen/WNxMgpp %} 

It may seem good enough, but the devil is in the details.

Look at the bottom right corner of the card: I made it resizable so you can simulate a browser resize.

There are two main issues. Can you spot them?

# The horizontal issue

The first problem is that because of its right margin, the last items goes to the second line too soon.

![The line wraps too soon](https://dev-to-uploads.s3.amazonaws.com/i/v31tzfgwyxah04yzerm3.gif)
<figcaption>Do you notice how there is more space for the last item to go more to the right before the wrapping? Compare that to the space to the left of the first tag.</figcaption>

You could fix that by excluding the last item from the rule:

```css
li:not(:last-child) {
  margin-right: 2em;
}
```

{% codepen https://codepen.io/twhite96/pen/mdEXGvE %} 

But the problem is the same: every other item causes the line to wrap too soon.

![The line still wraps too soon](https://dev-to-uploads.s3.amazonaws.com/i/lkqjq2ipkwwg75sxls7x.gif)
<figcaption>Now the last tag can go farther to the right, but the other ones still cause the line to wrap too soon.</figcaption>

Well, if `margin-right` is not good enough, what about `margin-left` ? Let's try this on every item — except the first one, which should not be preceded by any space.

```css
li:not(:first-child) {
  margin-left: 2em;
}
```

{% codepen https://codepen.io/bcalou/pen/BazrZQv %}

Is it better this time? Take a moment and try to guess what issue it might cause.

Because there is no `margin-right` anymore, the line wraps exactly when it should. But now our problem is elsewhere:

![The new line reveals the `margin-left` of the item](https://dev-to-uploads.s3.amazonaws.com/i/g38dtrm09tmloftslbhu.gif)
<figcaption>The new line reveals the `margin-left` of the item</figcaption>

We can't complain. We told CSS that each item except the first one should have a left margin, and that's what happens.

How nice would it be to exclude every item that is the first one of its row! But there is no magic selector like this:

```css
li:not(:first-flex-row-item) /* Does not exist */
```

Such hypothetical selectors could cause a *CSS circular dependency*. For example, I could say that the first item of a row has a smaller font-size. That could cause the item to go back to the previous line (because of it smaller size), and then it wouldn't be targeted by the selector anymore, and regain its original size, and go back to the second line and... 🤯

Circular dependencies one of the main reasons we don't have [container queries](https://css-tricks.com/lets-not-forget-about-container-queries/) yet. But that's another topic.

# The negative margin trick

So here's how you do it: first off, every item will get a `margin-left`.

```css
li {
  margin-left: 2em;
}
```

{% codepen https://codepen.io/bcalou/pen/yLJKPXy %}

We now have to get rid of the space to the left of the first item, and we can do that with negative margins.

Negative margins are not considered a good practice, and I think that you should avoid them whenever possible, because they can make your code's logic harder to understand.

That being said, they are allowed by the [w3c](https://www.w3.org/TR/CSS2/box.html#margin-properties) and offer a really good browser support.

And in our case, they save the day:

```css
ul {
  display: flex;
  flex-wrap: wrap;
  margin-left -2em;
}

li {
  margin-left: 2em;
}
```

{% codepen https://codepen.io/bcalou/pen/MWeVomw %}

![Blue borders applied on the list element reveal the trick](https://dev-to-uploads.s3.amazonaws.com/i/qkdy7mvnvcp8xi5nvopj.png)
<figcaption>Blue borders applied on the `ul` element reveal the trick</figcaption>

# What about the vertical axis?

Well guess what, it's the same thing!

You can't target every item except the ones that are on the first line.

So you have to give everyone a margin-top.

```css
li {
  margin-left: 2em;
  margin-top: 1em;
}
```

{% codepen https://codepen.io/bcalou/pen/zYBWzdo %}

This causes the whole list to appear lower that what we want.

We could remove the `margin-bottom: 1em` from the title tag to compensate.

![The removal of the title margin (yellow area) would compensate for the new area inside the list](https://dev-to-uploads.s3.amazonaws.com/i/puu6i895b2orm8dafrx9.png)
<figcaption>The removal of the title margin (yellow area) would compensate for the new area inside the `ul` (blue borders)</figcaption>

But I always try to **keep my elements independent of the context**. The list could appear below another element at some point. Or a title could be followed by something that is not a list.

You know, component driven development, design system and all that jazz.

So we just have to use the same trick and apply a negative margin to our list:

```css
ul {
  display: flex;
  flex-wrap: wrap;
  margin-left -2em;
  margin-top: -1em;
}
```

{% codepen https://codepen.io/bcalou/pen/XWKEgEp %}

And here's our final version. It works on every browser correctly supporting this flexbox configuration, including IE11.

# What about the `gap` property?

Posts like this one will become irrelevant once the CSS `gap` property is widely supported.

But that's not the case yet. At the time of writing, its [browser support is only 70%](https://caniuse.com/flexbox-gap). Not that great, compared to the 99% support of flexbox itself — has Safari really become the new IE?

Other modern browsers should show you the same result with the following code, without tricks!

```css
ul {
  display: flex;
  flex-wrap: wrap;
  gap: 1em 2em; /* row-gap + column-gap */
}

/* No more styles on the items */
```

{% codepen https://codepen.io/bcalou/pen/zYBWRaO %}

The sad part is that you can't even detect the support of this property. Consider the following code:

```css
@supports(gap: 1em 2em) {
  /* Cancel the tricks and do the right thing */
}
```

`@supports` queries allow you to apply rules only if the browser understands what's inside the parenthesis.

The problem here is that `gap` is also a property used on grids, with a much better support of 92%. But that does not mean that the property will work for flexbox.

Here's [the issue being discussed by the CSS Working Group](https://github.com/w3c/csswg-drafts/issues/3559).

In the meantime, it's negative margins all the way.

# Now with variables ✨

We can improve our code and make it more generic if we need. I like to separate my BEM/semantic CSS from my utility classes, so I will create a class called `u-flex`.

I'm not a big fan of having style-oriented classes in my `HTML`, so I would probably use a SASS mixin instead, but you get the idea.

Let's use CSS variables, which have a very decent support (95%). In CSS, you can get the opposite of a value by multiplying it by `-1`. Here is an example:

```css
div {
  --size: 2em;
  width: calc(-1 * var(--size)); /* -2em /*
}
```

So here's our utility class:

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

.u-flex > * {
  margin-top: var(--row-gap);
  margin-left: var(--column-gap);
}
```

I like to use the "direct child of any type" selector (`> *`) with flexbox and grid. It relates very well to the parent/child relationship of these features and will work every time.

And here's how you would use it:

```html
<ul class="u-flex">
  <li>HTML</li>
  <li>CSS</li>
  <li>JavaScript</li>
  <li>Front-end dev</li>
  <li>Web</li>
</ul>
```

```css
ul {
  --row-gap: 1em;
  --column-gap: 2em;
}
```

{% codepen https://codepen.io/bcalou/pen/oNLqEQz %}

The power of CSS variables allows us to define different gaps for each targeted element. We could even define default values for the whole document:

```css
:root {
  --row-gap: 1em;
  --column-gap: 2em;
}
```

This way, we only have to change the variables locally when we need to have a different gap value.

# Bad practices

We all know many bad practices: `!important` is another one that comes to my mind. But like negative margins, it also has some relevant use cases.

This negative margins trick is a reminder to me: things you learned to avoid might come in handy some day. It all depends of the context.