3 reasons why CSS shorthand properties are bad — and how to fix them

JavaScriptUI — DevBlog #5

Bence Meszaros
6 min readJun 4, 2024
tl;dr

As with many features in CSS, at first glance, shorthand properties look like simple and powerful additions to the language. But when you dig deeper into how they actually work you realize that they create more problems than they are trying to solve.

Here are the biggest ones.

No. 1 — They are oxymorons

In programming, a property is a single key that is associated with a single value. There is no such thing as a “shorthand” property. If we want to associate multiple values with a single property, then we need to wrap them in a data structure or use a method instead. Both of these approaches are well understood and straightforward, but since CSS refuses to introduce proper data structures and proper methods into the language, it ended up reinventing the wheel with an entirely new concept.

We have this:

#ermahgerd {
padding: 2px 4px 6px;
}

Even though we already had all these to choose from:

ermahgerd.padding = ["2px", "4px", "6px"]; //using an array
ermahgerd.padding = {top: "2px", left_right: "4px", bottom: "6px"}; //using an object
ermahgerd.padding = "2px 4px 6px"; //using a string
ermahgerd.padding("2px", "4px", "6px"); //using a method

No. 2 — They are ambiguous

Even simple shorthand properties like padding and margin are confusing. This is because shorthand properties allow us to use them with less values than they represent. Using padding for example with four values is unambiguous, but what about using them with one, two or three values? Does that mean that we wish to set only the first few values and keep the rest as default, or does that mean some values are duplicated? And if duplicated then how exactly?

The problem is not that there are no rules for these scenarios in CSS, but that these rules are ad hoc. Take for example the case of three values for padding. In this scenario one of the three values has to be duplicated but which one and how?

//order is always top, right, bottom, left

//duplicate the first value
padding: (2px) 2px 4px 6px
padding: 2px (2px) 4px 6px
padding: 2px 4px (2px) 6px
padding: 2px 4px 6px (2px)

//duplicate the second value
padding: (4px) 2px 4px 6px
padding: 2px (4px) 4px 6px
padding: 2px 4px (4px) 6px
padding: 2px 4px 6px (4px) //this is the official

//duplicate the third value
padding: (6px) 2px 4px 6px
padding: 2px (6px) 4px 6px
padding: 2px 4px (6px) 6px
padding: 2px 4px 6px (6px)

Even without changing the order of these values, we already have 12 options to choose from. The CSS specs chose one, but there is nothing specific about this choice, it is arbitrary.

We are left with memorizing ad hoc rules, increasing our cognitive load and confusion during development. How many times have you had to recall these doodles from your memory?

Doodles from the “Tricky edge cases” section of shorthand properties on MDN

No. 3 — They are grossly underpowered

Probably the biggest issue with shorthand properties is that they are extremely limited. Take a look at these examples:

#ermahgerd {
padding: 5px 5px 3px;
transform: translateX(10px) rotate(10deg) translateY(5px);
transition: margin-right 2s ease-in-out .5s, padding-top 4s ease-in-out .3s;
}

The more stuff we try to shoehorn into a single property without proper data structures, the worse the overall performance and clarity will be. Shorthands like padding are already a stretch, but properties like transform and transition are beyond reason.

Take for example the transition property. Not only are we assigning multiple values to a single property, we even fake a nested data structure with yet another ad hoc solution. Funnily enough, this too is child’s play using proper programming paradigms.

So instead of this:

#ermahgerd {
transition: margin-right 2s ease-in-out .5s, padding-top 4s ease-in-out .3s;
}

We should have one of these (and many more):

ermahgerd.transition = [
["margin-right", 2, "ease-in-out", 0.5],
["padding-top", 4, "ease-in-out", 0.3]
];

ermahgerd.transition = {
marginTop: [2, "ease-in-out", 0.5],
paddingTop: [4, "ease-in-out", 0.3]
};

ermahgerd.transition({
marginTop: [2, "ease-in-out", 0.5],
paddingTop: [4, "ease-in-out", 0.3]
});

In addition, in case of both the transition and transform properties we cannot even interact with individual values through browser APIs, we are forced to parse the full string to get a single value and stringify and concatenate all values to set a single value. This is extremely cumbersome.

But it gets even worse. If you want to set transition in raw HTML, or do the same by setting the .style property manually through the browser API, you’ll need to flatten 5 levels of object hierarchy into a single string (or 6, if you count units as an additional level). It is one of the most bizarre things I have ever seen in coding:

<div style="transition: margin-right 2s ease-in-out .5s, padding-top 4s ease-in-out .3s;">
div.style = "transition: margin-right 2s ease-in-out .5s, padding-top 4s ease-in-out .3s;";

This would be as simple as this, using basic programming concepts:

div.style.transition.marginRight = [2, "ease-in-out", 0.5];
div.style.transition.paddingTop = [4, "ease-in-out", 0.3];

And a bonus point: They have bad names

It is no secret that CSS is terrible at naming its stuff and shorthand properties are no exception.

For some reason, instead of corner-radius, CSS uses the border-radius property name. Not only is this name fully inaccurate (we round the entire element, not just its border), but it also suggests that the border-radius property has something to do with the border shorthand which it doesn’t and it wouldn’t even be possible (four sides vs four corners).

Another problematic area is the transition, transform, translate names that are mostly correct but quite confusing, especially for non-native speakers. But in theory, we could apply transformations directly to the elements, without combining them inside the transform property, and we could apply transitions directly to properties, without combining them inside the transition property. This would reduce confusion regarding these names.

The solution: JavaScriptUI

In JavaScriptUI we use getter/setter methods to configure Views. This is helpful, since method arguments already provide an unambiguous way to deal with multiple and/or missing values. Take a look at these examples:

//paddingLeft is not set (default or previous value)
component.padding(2, 4, 6);
component.padding({top: 2, right: 4, bottom: 6});

Unfortunately, JavaScript doesn’t support named arguments, but if it did, that would be the cleanest solution:

component.padding(top: 2, right: 4, bottom: 6);

Notice that numeric values are without units. This is because each numeric value has an implicit default unit to simplify working with them. But it is still very much possible to specify units other than the default as you’ll see in the next example.

Further expanding on this concept, we can introduce getter/setter methods for individual transformations in a similar fashion:

component
.translateX(10)
.rotate(CSS.deg(10))
.translateY(5);

//or if you still prefer the combined approach:
component.transform({
translateY: 10,
rotate: CSS.deg(10),
translateY: 5
});

And we can add transitions too:

component
.marginRight(0, 2, "ease-in-out", 0.5)
.paddingTop(0, 4, "ease-in-out", 0.3);

//or
component
.marginRight(0, [2, "ease-in-out", 0.5])
.paddingTop(0, [4, "ease-in-out", 0.3]);

//or with (fake) named arguments
component
.marginRight({value: 0, transition: [2, "ease-in-out", 0.5]})
.paddingTop({value: 0, transition: [4, "ease-in-out", 0.3]});

Notice that transitions are bundled with properties instead of being bundled inside a separate transition property. This makes transitions clear and easy to work with.

Outro

This concludes our article. If you found it interesting please support my work by clapping, commenting and sharing my stuff with others.

Thanks for reading and have a great day.

⬅️ DevBlog #4 — CSS position: fixed is terrible, here’s why

--

--