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:
padding: 1rem 2rem 3rem 4rem;
Instead of:
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.
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:
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:
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:
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):
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
.
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 actuallyflex: 1 1 0%
flex: 1 0
is actuallyflex: 1 0 0%
flex: 1 auto
is actuallyflex: 1 1 auto
flex: auto
is actuallyflex: 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:
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:
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:
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:
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 theborder-width
(because it's a number value followed by a unit)dashed
has to be theborder-style
(if you already know names of different border styles, that is)blue
has to be theborder-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:
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:
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:
.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
:
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:
.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.