My issues with shorthand properties

The removal of CSS shorthand properties would benefit us all.

At CSSDay this year I was lucky enough to meet and chat for a few minutes with Adam Argyle. He asked me what I would change about CSS if we* were to invent it today and my answer was: "not introduce shorthand properties to the syntax".

*I like to imagine that he meant "we" as in "he and I", not the W3C or the community as a whole.

Since then I've thought a lot about why I said that, and have now tried to write a down defense for this utterly absurd statement.

What are shorthand properties?

Shorthand properties (shorthands) are CSS properties that let you set the values of multiple other CSS properties simultaneously. For example:

Language: css
padding: 1rem 2rem 3rem 4rem;

Instead of:

Language: css
padding-top: 1rem;
padding-right: 2rem;
padding-bottom: 3rem;
padding-left: 4rem;

We've saved three lines of CSS by shortening it. This inheritably sounds like a nice thing, but let me explain why I do not necessarily always think so.

Some shorthands and their issues

MDN states in the documentation on shorthand properties:

Using a shorthand property, you can write more concise (and often more readable) style sheets, saving time and energy.

I do not fully agree with the "readable" part. For experienced CSS developers, then yes, maybe. But my experience with some shorthand properties is that they create more confusion than they solve.

Padding and margin

These are the basics, padding and margin is two of the first properties you learn when starting out.

They are shorthands for:

  • padding-top
  • padding-right
  • padding-bottom
  • padding-left

and

  • margin-top
  • margin-right
  • margin-bottom
  • margin-left

They take up to four values, but also accepts one, two and three values.

Language: css
padding: 1rem 2rem;
margin: 4rem 2rem 3rem;

The values represent directions, but change meaning depending on how many values are added.

  • If there's one value, they represents all four sides of the box: top, right, bottom and left.
  • If there's two values, they represents the two axis of the box: y-axis (top, bottom) and x-axis (right, left).
  • If there's three values, they represents one axis and two sides of the box: top, x-axis and bottom
  • If there's four values, they represents all four sides of the box, but individually: top, right, bottom and left.

Confusing?

Well, not for you of course, you've written these properties hundreds of times and can easily spot what value represents what direction, right?

I've written CSS for 15+ years and I still have to do some mental gymnastics to decrypt code like this:

Language: css
div {
  padding: 1rem 2rem;
  margin: .5rem .75rem 1rem;
}

@media (min-width: 600px) {
  div {
    padding: 1rem 2rem 1.5rem;
    margin: .5rem .75rem 1.25rem;
  }
}

What exactly is being changed by the media query? And what values are still det same?

Most would probably agree that the following is easier to understand as a whole:

Language: css
div {
  padding-top: 1rem;
  padding-bottom: 1rem;
  padding-right: 2rem;
  padding-left: 2rem;
  margin-top: .5rem;
  margin-bottom: 1rem;
  margin-right: .75rem;
  margin-left: .75rem;
}

@media (min-width: 600px) {
  div {
    padding-bottom: 1.5rem;
    margin-top: 1rem;
  }
}

It looks clustered (of course, there's more lines, and each property looks like one another), but here we can clearly see that: "oh, on screens larger than 600px the margin-top value and the padding-bottom value for the div changes". We won't have to repeat the values already declared, like we do if we were to use the shorthand.

You could argue that the original code also could be written like this:

Language: css
div {
  padding: 1rem 2rem;
  margin: .5rem .75rem 1rem;
}

@media (min-width: 600px) {
  div {
    padding-bottom: 1.5rem;
    margin-bottom: 1.25rem;
  }
}

It achieves almost the same, but I still think it's harder to read the first part and actually see what the padding-bottom or margin-bottom value was before the media query change.

OK, I'll budge a little. I'm not entirely against shorthands. I, for one, often find a middle ground. Here's an example (using logical properties):

Language: css
div {
  padding-block: 1rem;
  padding-inline: 2rem;
  margin-block-start: .5rem;
  margin-block-end: 1rem;
  margin-inline: .75rem;
}

@media (min-width: 600px) {
  div {
    padding-block-end: 1.5rem;
    margin-block-end: 1.25rem;
  }
}

Yes, padding-block, padding-inline and margin-inline are shorthands, but look at the properties I use to overwrite them with. I think it's a difference in overwriting one of the y-axis values on padding-block: 1rem with padding-block-end: 1.5rem, than writing the whole shorthand over again like this: padding-block: 1rem 1.5rem.


Flex

OK, enough about basic box model properties. Let's look at the shorthand flex.

Language: css
div {
  flex: 1 0 auto;
}

Now tell me: what do the three values represent?

I can't count how many times I've seen developer write flex: 1 0 auto without knowing what it does, just because it "works".

Let's break it down. flex is a shorthand for:

  • flex-grow
  • flex-shrink
  • flex-basis

Like padding and margin it has this weird magical ability to mean something different depending on how many values you've written. No wonder CSS is confusing to so many people. The flex property is really hard to read:

  • flex: 1 is actually flex: 1 1 0%
  • flex: 1 0 is actually flex: 1 0 0%
  • flex: 1 auto is actually flex: 1 1 auto
  • flex: auto is actually flex: 1 1 auto

The flexbox model is a tough concept to grasp when you're starting out, and mysterious properties like these doesn't help. In my opinion this shorthand adds little to no value.


Gap

When we're on the subject of the flexbox model: the gap property can be nifty, but it's also really unnecessary as it only covers two other properties:

  • column-gap
  • row-gap

I try to use these two separately instead of gap. Often we add a gap to something when we actually just want to add column-gap.

Take a flexbox component with flex-direction: row. We want to create a space (gap) between the items within, which lays horizontally:

Language: css
nav {
  display: flex;
  flex-direction: row; /* is the initial value, but added here for clarity */
  gap: 2rem;
}

gap is perfectly fine in this situation, because the items within never wraps onto a new row, meaning that gap and column-gap does the same job.

However, if we were to add flex-wrap: wrap to our component, the gap value of 2rem suddenly impacts the component in a way we didn't intend. If the items wraps onto a new row, a gap will appear between the items vertically.

2rem is what we wanted between the items horizontally, but not vertically.

We could add two values to gap, but I like to explicitly set column-gap and row-gap instead:

Language: css
nav {
  display: flex;
  flex-direction: row; /* is the initial value, but added here for clarity */
  flex-wrap: wrap;
  column-gap: 2rem;
  row-gap: 1rem;
}

And I think this is clearer for everyone. There's an intent behind our chosen properties.


Background and Font

background is another shorthand that do more harm than good. It's a shorthand for:

  • background-attachment
  • background-clip
  • background-color
  • background-image
  • background-origin
  • background-position
  • background-repeat
  • background-size

I bet few actually write their background styles like this:

Language: css
background: no-repeat center/80% url("../img/image.png");

The same goes for font, which is a shorthand for:

  • font-family
  • font-size
  • font-stretch
  • font-style
  • font-variant
  • font-weight
  • line-height

I've never even seen this in a codebase:

Language: css
font: italic small-caps bold 16px/2 cursive;

Again; being clear about what you're trying to achieve is a good thing, and shorthands do not add that clarity.


Border and Border Radius

Border

Here's where I get hypocritical. I use the border and border-radius shorthand properties all the time.

I'd like to defend border a bit, because it's a shorthand of these properties:

  • border-width
  • border-style
  • border-color

Unlike padding and margin, the three values you can apply here are different from one another. If you write border: 1px dashed blue you could argue that it's easier to "guess" their meaning, as:

  • 1px has to be the border-width (because it's a number value followed by a unit)
  • dashed has to be the border-style (if you already know names of different border styles, that is)
  • blue has to be the border-color (because it's a color)

These rules are easy for experienced developers to understand, but they can still be confusing for beginners. Writing border styles without shorthands would require you to write 12 lines of code, so I'm lenient to pardon it:

Language: css
border-top-width: 1px;
border-top-style: dashed;
border-top-color: blue;
border-right-width: 1px;
border-right-style: dashed;
border-right-color: blue;
border-bottom-width: 1px;
border-bottom-style: dashed;
border-bottom-color: blue;
border-left-width: 1px;
border-left-style: dashed;
border-left-color: blue;

text-decoration is also one that I'd miss.

Border Radius

border-radius is more like padding and margin in the sense that it has four possible values (actually eight values, but let's not get into that), in this case representing the corners of the box, declared clockwise from the top left corner to the bottom left corner. It's a shorthand for:

  • border-top-left-radius
  • border-top-right-radius
  • border-bottom-right-radius
  • border-bottom-left-radius

The need for setting a value for each corner individually is rare. I would argue that most components with curved corners have an equal curve on all four sides, like so:

Language: css
button {
  border-radius: 1rem;
}

However, if I do find myself in a position where for example only two of the corners should be curved I would rather write this:

Language: css
.weird-button {
  border-top-left-radius: 1rem;
  border-bottom-right-radius: 1rem;
}

In conclusion

Some shorthand properties are hard to read, like font and background, but there exists almost a consensus between developers not to use them. Other properties are more common, like padding and margin, border and border-radius, and the familiarity we have to them makes them easier to understand at a glance. flex, background and font are not one of these.

CSS is a weird language. There's just a lot of syntax to remember, and there's so many different ways to write the same thing. Don't get me started with shorthand values that can be placed in different spots, and still yield the same result, e.g. border:

Language: css
border: 1px solid black;
/* Does the same as: */ 
border: solid black 1px;

People find CSS hard to learn and get into, and I think shorthands are partly to blame.

Another issue is the amount of "magic" styling that happens with both the targeted DOM elements and its children when you apply styling. It's hard for beginners to know that when you apply display: flex to an element, you also add the following:

Language: css
.something {
  display: flex;
  /* Initial properties you'll also kind of get: */
  flex-direction: row;
  flex-wrap: nowrap;
}

Actually, you don't "add" it, its kind of always there as a default, but not applied before the display property changes. I know, we need defaults in CSS, but for newcomers it just feels like "magic" that you sometimes have to (and sometimes not, you never know) fight against.

Wrapping it up

We are instructed to write clear, readable code. In Javascript and other languages you are encouraged to name your variables index or counter instead of i or c. No point in being clever if it becomes hard to read. Same goes for CSS: I think we should write explicit properties as much as possible instead of using shorthands.

Clarity trumps cleverness.