DEV Community

Michael Puckett
Michael Puckett

Posted on

How and Why I Avoid Magic Numbers in CSS

Introduction

There's a great article called Code smells in CSS by Harry Roberts that first introduced me to the concept of magic numbers in CSS.

When Harry wrote the article in 2012, there weren't too many great solutions for dealing with these problems. But since then, we've gotten Grid and Flex layout, along with CSS custom properties (variables!) so we can now avoid many of the pitfalls.

The basic idea of magic numbers is that writing styles with arbitrary numbers like the following is brittle and unmaintainable:

.container {
    height: 250px;
}
.box1 {
    padding-left: 4px;
    position: relative;
    top: 11px;
}
.box2 {
    padding-left: 2px;
    padding-right: 4px;
    position: relative;
    top: 7px;
}

For one thing, what's so special about these numbers? Even if there were comments, you might not know all that they're doing unless you look at the markup, or how they relate to the other numbers.

Let's say .box1 and .box2 are next to each other.

Focusing on .box2, let's first look at its padding -- 2 on the left and 4 on the right. The left padding is defining the space between the components, and the right side in defining the outer right margin of the page. That's not very conducive to composability. What if we wanted to reverse the order? We would have to sift out the 2 and 4, reverse them, and apply them to the other component. It would be better if the container could manage these details.

The 11px and 7px top values here are vertically centering the boxes inside their container. That's precarious, in case the content changes. There are better ways to achieve this.

Grid Layout

In the example above, with one padding defining the space between components, and the other defining the page margin, we can leave all those details to the grid container. The container gets the following:

.container {
    display: grid;
    padding: 0 4px;
    grid-gap: 2px;
    align-items: center;
    grid-auto-flow: column;
    height: 250px;
}

Now the interior components don't need padding.

Notice also how Grid supports align- and justify- properties that can do the work of centering and aligning, so we can lose the 11px and 7px top value altogether.

(Flexbox also has these alignment properties, but the support for gap is still limited, unfortunately.)

Custom Properties

Any time that we still need to use specific numbers, we can replace them with CSS custom properties: --page-padding: 4px and --gap-width: 2px

Note: I think it's fair to be cautious with a statement like this, and to be conservative when applying this rule in practice. There are potential performance considerations when using CSS custom properties all over the place. But in my experience, the bottleneck in rendering a page hasn't been the CSS.

In keeping with this, we can keep the 250px height as-is, because it's unique.

Similarly, --page-padding might be a better candidate for being a custom property than --gap-width, for example. If the gap between the components is unique, keeping it 2px might be fine.

But what if, when it was originally conceived by the designer, the space between the components was designed to be half of the page padding? If so, you might want to preserve the relationship in case the page padding changes in the future.

You can use calc for this purpose:

.container {
    display: grid;
    padding: 0 var(--page-padding);
    grid-gap: calc(var(--page-padding) / 2);
    align-items: center;
    grid-auto-flow: column;
    height: 250px;
}

Now we're down to just one magic number, and one appropriately-named custom property. In my opinion, that's much easier to reason about and the intent of the code is clearer.

I found a similar article on Dev that goes even more in depth if you want to check it out: https://dev.to/adrianbdesigns/mastering-css-magic-numbers-2297

✌️

Top comments (2)

Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
mpuckett profile image
Michael Puckett • Edited

That’s a really great alternative take! Agree that it can go the opposite direction and hinder readability if you go overboard.

Instead of creating multiple calc functions for various paddings/gaps in the code, I prefer having a standardized set of padding variables. That usually keeps things simple, but requires buy-in from the designer.

In the article I linked there’s a better use for calc, something like: calc(var(--standard-padding) - 1px) for rare exceptions.

Also it may be true that upon initial glance you know fewer specifics about the code (“exactly which number pixels do I push?”), the original intent has been translated into the code, which should make the overall process of making changes easier to reason about.

Appreciate your response!