Which reaction do you get by looking at the following code?
#elem {
-moz-box-shadow: 0 0 10px gray;
-webkit-box-shadow: 0 0 10px gray;
-o-box-shadow: 0 0 10px gray;
-ms-box-shadow: 0 0 10px gray;
box-shadow: 0 0 10px gray;
}
Whenever you see some extremely verbose CSS like the above, written with all vendor prefixes (plus the unprefixed version), all of which look exactly the same, it really brings some
rage mixed feelings. On one hand, you feel glad to have found a web developer who went out of their way to use experimental CSS features in the “right” way. On the other hand, you feel sad that we are asking them to do this.
Much has been said about how CSS prefixes do more harm than good, and a lot of people have wanted to get rid of them, but there hasn’t been any proposal that didn’t cause (worse) shortcomings, allowed vendor experimentation to continue without disrupting the web, or offered an easy transition path. I’ll present here a proposal that I believe fixes all the existing problems with prefixes, makes it really easy to transition from experimental to recommended, and actually improves various aspects of web development and browser support for features that are on an standardization path.
The feature is simple, and there’s a TL;DR at the end, but let’s design it step by step to make sure we’ve covered everything.
(Let me here preemptively say, in true Graydon style: disregard the syntax of the proposal, focus only on the idea. The ideal syntax can be discussed later)
Feature unlocking
The basic idea is that, in order to use non-standard CSS features, instead of using prefixes on every declaration of that property, you only use it once at the top of the file to indicate that you’re willing to unlock that feature as implemented by that engine. For instance, the example at the top would become:
@-vendor-unlock {
box-shadow: gecko, webkit, trident, opera;
}
...
#elem {
box-shadow: 0 0 10px gray;
}
That means that the actual property declarations do not need to use the prefix, but the browser engine will still
only accept the declaration if it has been explicitly unlocked. With that, when the feature reaches the status in which a vendor would like to drop the prefix (that is, by convention, candidate recommendation), all it needs is to start accepting the declaration unconditionally (that is, independent of being unlocked),
without breaking every website that was using it before (unless the syntax changes, but more on that later).
Here’s a list of the properties that this approach bring:
- Feature experimentation is still not unintentionally exposed to the web
- It’s easier for web developers to use, which should encourage usage and testing for more than one engine
- It makes it explicit for web developers that they are dealing with an experimental feature (instead of the “engine-only” feeling of prefixes)
- When it’s time to make the feature a standard, websites don’t have to break and the engines don’t have to support the legacy syntax for a painfully long period of time
- It allows the creation of developer tools for web developers (and browser developers) to force support a feature and thus test their website with a different engine
- Browser vendors can start shipping new features with this approach without breaking existing support, and it doesn’t require a multi-year transition
- Users will benefit from quicker multi-browser support by websites
- It makes it easier for new engines to enter the market
Versioning
One problem that already exists with vendor prefixes, but that is not solved with the above suggestion, is syntax or feature changes. The whole purpose of the prefixes is so that vendors can experiment with feature support without making the promise that it won’t ever change. In practice, though, once a feature has been widespread enough, most vendors are tied to that and the decision to change the syntax or not is heavily influenced by that. While we are designing the new approach, we can also fix that! With feature versioning, web developers can explicitly target a version of the implementation, like so:
@-vendor-unlock {
box-shadow: gecko/v1, webkit/v2, trident/v2, opera/v1;
}
This gives us:
- Different implementations of a feature can exist at the same time
- When the engines are tending towards an unified version (say 1st version of a feature in gecko was different than 1st in webkit, but they match at version 2), it’s easier for developers to use it
- When there’s a change in the syntax or behavior of a feature, the browser engine can choose to either gracefully support the previous version or not, on a case by case decision whenever it makes sense to do so. This is currently not possible since there’s no way to tell which version the webpage is expecting to use.
Syntax mismatches
The last problem is when two matching versions does not exist between browsers. That is, it’s not possible to write a single declaration that will work on different engines. In that case, there’s no way around having multiple property declarations, and an override must be provided. To do that, the vendor prefix plus the version is used, and the feature still has to be unlocked, which guarantees that there’s no reason to just use the “good old prefix” except for mismatching reasons. Also, the prefix should be used to the engine that is probably leaning in the wrong direction than what will be made the standard.
As an example, let’s imagine a property called foobar that webkit and gecko implements, with two size arguments (<left> and <right>), but each takes on a different order.
@-vendor-unlock {
foobar: gecko/v1, webkit/v1;
}
#elem {
foobar: 10px 5px;
-webkit-foobar-v1: 5px 10px;
}
Now, let’s say that webkit decided to change the order to match gecko’s, and the other engines followed. Then the only change required is to add the other engines and another entry for the second version of webkit:
@-vendor-unlock {
foobar: gecko/v1, webkit/v1, webkit/v2, opera/v1, trident/v1;
}
#elem {
foobar: 10px 5px;
-webkit-foobar-v1: 5px 10px;
}
With this change, the webpage supports the experimental versions of
foobar, plus the standardized (unprefixed) version whenever it reaches candidate recommendation, plus the differing version 1 in webkit if it wants to.
TL;DR
Browser vendor prefixes are polluting the web so much while making web development harder, which is the exact opposite of the two main reasons it exists. The main idea to fix that is that the property declarations should be written unprefixed, while an explicit unlock and versioning is instead used to activate these features on browser engines. An example would be:
@-vendor-unlock {
border-radius: gecko/v2, webkit/v1, opera/v1, trident/v1;
}
...
#elem {
border-top-left-radius: 5px;
border-top-right-radius: 10px;
}
Note that with this example I also implied that the unlocking mechanism can be used for whole features, not only direct property names, such that you could use “css-animations” to unlock all the animation-delay, animation-timing-function, @keyframes, etc.
P.S.: I’ll be at FOSDEM this weekend if anyone is interested in discussing this idea during the conference.