3 reasons why CSS shorthand properties are bad — and how to fix them
JavaScriptUI — DevBlog #5
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?
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.