A List Apart

Menu

Prefix or Posthack

by Published in Browsers, CSS · 58 Comments

As CSS browser support increases, including impressive strides by the IE9 team, more and more authors are plunging into CSS3. As they do so, they’re facing vendor prefixes—the -*- properties like -moz-border-radius, -webkit-animation, and so on.

Issue № 309

Perhaps inevitably, there’s been some grumbling about these prefixes. There have been calls to drop them, or to collapse all the vendor-specific prefixes into a single prefix like -beta-. The primary pushback is that nobody really wants to write the same thing four or five times in a row just to get, say, rounded corners on an element.

While such grousing is understandable, it is exactly the inverse of what should be happening. We ought to praise vendors for using prefixes, and indeed encourage them to continue. Beyond that, I hold that prefixes should become a central part of the CSS standardization process. I do this not for the love of repetition, but out of a desire to see CSS evolve consistently. I believe that prefixes can actually accelerate the advancement and refinement of CSS.

Look back in horror

To understand why we have vendor prefixes at all, it’s instructive to look back at the box model, which almost killed CSS before the turn of the millennium. Inconsistent box model implementations created a crisis. To escape the danger, we had to build an entirely new behavior on top of a markup feature and invent a whole class of hacks.

For the young whippersnappers in the audience who missed all the fun, what happened was this: In the first round of browsers that supported CSS, Netscape implemented the box model found in the CSS specification. That meant that width and height referred to the width and height of the content area. But Internet Explorer implemented the intuitive box model, which meant that width and height declared the dimensions of the box’s outer border edge.

Whichever of the two you think better, the fact remained that there were two major browsers with large user bases that were completely incompatible with each other. It was the late 1990s, we were fighting like hell to leave behind the morass of “this site best viewed in…” badges, and here we had a situation where a layout that worked fine in one browser could completely fall apart in another.

Compounding the problem was that neither browser could change its behavior to mirror the other. Assume for a moment that the IE team decided to change their CSS support to reflect the specification. To do so would mean that tens, even hundreds of thousands of sites that worked in IE would break—would quite literally fall apart, visually speaking—in the “fixed” version. While the standards community would have applauded the move, the rest of the world would have written off the browser as unusable. And even if the Working Group decided to change the specification to match IE’s behavior, Netscape would then have faced exactly the same problem.

Thus DOCTYPE switching was created. The entire regime of “standards mode” and “quirks mode” was born of this problem. The solutions to other problems were rolled into DOCTYPE switching, but the box model triggered it. Think about it: because two vendors did things differently, browsers now have to maintain two different primary rendering models and choose which to use based on an SGML declaration that says nothing about rendering.

Furthermore, the first wave of CSS hacks were devised to address exactly the same problem. The classic example of the genre gives it away in the title: The Box Model Hack. In fact, the hack itself was based on flaws in syntactical parsing of voice-family values, but nobody ever called it “the voice-family hack.”

The funny part is that this wasn’t the only instance where a box model inconsistency led to trouble. Not long after DOCTYPE switching saved CSS, the Explorer team implemented some features of CSS positioning. One of the properties they implemented was clip. Having learned their lesson with the box model brouhaha, the engineers at Microsoft paid very close attention to the specification and did what it said.

Shortly after they shipped it publicly, the CSS Working Group massively changed the way clip worked. The syntax looked exactly the same, but yielded very different results.

Once more, the specification clashed with the behavior of a publicly available browser (or, if you prefer, vice versa). The eventual resolution was to revert to the earlier behavior and drop the new behavior entirely. That renders clip effectively useless on any element with unpredictable height and width—which is to say, any normal-flow, non-replaced element such as a div or a paragraph. Although other solutions were proposed, they never came to pass, and clip withered away.

Imagine a different outcome

Suppose that instead of implementing clip, the IE team had implemented -ms-clip. In that case, a behavior change in a later specification wouldn’t have been so difficult to overcome. Because a vendor prefix marks a property as “in progress,” it’s much easier for a vendor to go back and change it. Thus the IE team could have changed the way -ms-clip worked in their next release, explaining to developers that they were updating their experimental implementation to match changes to the specification.

Even if they had decided that that was impossible to do, the damage of the “bad” implementation would have been quarantined in the prefixed version of the property. Other vendors could have implemented the new version of clip (using their own prefixes), unaffected by what the IE team had done. A single vendor could not pin the specification and other vendors in place by their actions.

This is the promise that prefixes provide: A way to mark properties as “in progress,” and so not necessarily guaranteed to always act the same in future releases; an out for vendors who need to make those changes; and a defense against bad or premature implementations that happen to ship first. They add sorely needed flexibility to the advancement of CSS.

Of course, we could just say: “When a browser is wrong according to the specification, then they have to change even if it breaks the layout of web sites.” With prefixes, that’s a lot easier to accomplish, thanks to the warning that prefixes embody. Without prefixes, it’s very difficult or even impossible. Microsoft never did change the way it handled width and height in legacy pages—instead, it used DOCTYPE switching to behave differently on new (theoretically more “standards compliant”) pages. It was a useful and necessary trick, but that kind of trick only works once.

Even now we suffer

Lest you think that all this silliness is an artifact of history, here are two cases of inconsistency happening right now:

  • Mozilla and WebKit browsers render box-shadow blurring very differently, and neither fully conforms to the specification. As I write this paragraph, a lengthy and heated debate is raging on the www-style mailing list. At least one, and possibly both, implementations will have to change the way they handle shadow blurring to achieve interoperability. The same holds true for any Microsoft or Opera implementations.

  • Mozilla and WebKit browsers both support gradients, but they use radically different syntaxes to achieve the same basic result. Now imagine a world where the vendors had implemented gradients without the prefixes. You would have three choices:

    1. Pick which browser gets a gradient and which one doesn’t.
    2. Use CSS hacks or browser sniffing to serve up different styles to different browsers.
    3. Walk away from using gradients entirely.

    And there are three choices here only because the gradients use wildly different value syntaxes, thus opening the door to option number one. In a case where two implementations use the same value syntax but have very different effects—as was true with clip—then there are really only the last two options: hack and sniff to send totally different styles, or just walk away.

We’ve seen this movie played out many times over the history of CSS.  There’s no reason to want to see it again. It was bad enough the first dozen times.

Prefix or posthack

But are prefixes really any better?  After all, it’s been said that vendor prefixes are the new CSS hacks. As Aaron Gustafson pointed out in a recent article, this:

-moz-border-radius: 10px 5px;
-webkit-border-top-left-radius: 10px;
-webkit-border-top-right-radius: 5px;
-webkit-border-bottom-right-radius: 10px;
-webkit-border-bottom-left-radius: 5px;
 border-radius: 10px 5px;

…is reminiscent of this:

padding: 10px;
width: 200px;
w\idth: 180px;
height: 200px;
heigh\t: 180px;

In terms of repetition and annoyance, yes, the two are very much alike. But they’re fundamentally different in this way: Prefixes give us control of our hacking destiny. In the past, we had to invent a bunch of parser exploits just to get inconsistent implementations to act the same once we found out they were inconsistent. It was a wholly reactive approach. Prefixes are a proactive approach.

Furthermore, prefixes are a temporary hack. As time goes on and implementations become consistent, browsers will drop the prefixes. From then on, authors will be able to write one line for border-radius instead of six-plus lines of CSS. Without them, we’re just waiting for the next botched implementation that forces us to support it through hacks for years upon years.

That’s why creating a unified prefix, such as -beta- or -w3c-, is at least half a step backwards. It would preserve vendors’ ability to mark properties as “in progress” and make changes as needed. Unfortunately, it would completely rob authors of the ability to excise, or even feed a different value to, one particular browser if it has a botched implementation. From an author’s point of view, a unified prefix is no better than a world without prefixes.

I sometimes feel the same way about pre-processor methods to handle prefixes, whether on the server side (using tools like Less) or client side (any number of JS frameworks). When using these tools, it’s possible to just write border-radius declarations and have the tool expand that into the requisite list of prefixed declarations. On the one hand, they’re a very useful way to reduce typing and keep the authoring neat and clean. On the other, they’re just like a unified-prefixed or unprefixed world: one bad browser implementation away from breaking pages.

The advantage is that if something goes haywire, any author can go back, disable the pre-processor, and write out the prefixes by hand. Alternatively, the pre-processor can be updated to handle the problem. Either way it’s a little more cognitive overhead for the author, but not too much.

The downside is more philosophical, but it’s no less important for that: By hiding the prefixed properties behind a processor, authors may forget that what they’re using is experimental and subject to change.  Cognitively, they may start to treat what they’re using as settled and stable when it may be nothing of the kind.

Make prefixes really matter

I believe so firmly that vendor prefixes are a good thing that I’m prepared to take the next logical step: Vendor prefixes should be made more central to the standards process. They should be required of newly implemented properties and should be the mechanism by which interoperability is declared.

Here’s what I mean: Suppose someone invents a new property called text-curl. Immediately, three vendors implement it. Each of them should be required to add a vendor prefix to their implementation. Thus, we’d see things like this:

h1 {
 -webkit-text-curl: minor;
 -moz-text-curl: minor;
 -o-text-curl: minor;
 text-curl: minor;
 }

Over time, the vendors refine their implementations in response to bug reports and clarifications by the Working Group. Eventually, the Working Group decides that two of the three are fully interoperable. Those implementations then get to support the bare text-curl. The third does not.

At that point, authors might decide to simplify their styles like so:

h1 {
 -webkit-text-curl: minor;
 text-curl: minor;
 }

Instead of hacks proliferating over time, they’re peeling away. Eventually, we’ll only need a single text-curl line.

So what happens when a new implementation debuts? It uses the prefix in its first release, no matter how many interoperable implementations already exist. That might mean that we’d have to go back and change the CSS to say:

h1 {
 -ms-text-curl: minor;
 text-curl: minor;
 }

Then, as soon as the Working Group deems the implementation of -ms-text-curl interoperable, the prefix can be dropped in the next release of IE. At that point the CSS can be reduced to a single, unprefixed line. Again, the number of hacks dwindles over time.

Of course, each of those vendors will continue to support the prefixed properties, so even if we don’t prune the prefixed lines, each supporting browser will recognize the unprefixed property and use it (since it comes after the prefixed declaration). For any browser that implements a prefixed version that doesn’t manage to get to an unprefixed state, its own prefixed property will still work. Even if the CSS is never touched again, it will continue to function.

Having said that, return for a moment to the time when the Working Group said that two implementations were interoperable and could thus drop the prefixes. That serves two purposes. First, as I said before, it marks a property as having enough interoperability to allow progress in the standards process.

But the other thing it does—and this is arguably more important—is force the vendors and the Working Group to work together to devise the tests necessary to determine interoperability. Those tests can then guide those who follow, helping them to achieve interoperable status much faster. They could literally ship the prefixed implementation in one public beta and drop the prefix in the next.

This reverses the way things are done now. As it stands, the process is set up so that when any CSS module reaches the Candidate Recommendation stage, vendors can drop the prefixes from properties in that module. But that just opens us up to the possibility of another botched implementation and a future of hacks to work around the error.

As proposed here, a module would be permitted to reach Candidate Recommendation once all of its properties had at least two unprefixed implementations in the wild. Any implementations that came after would start prefixed and drop the prefix once they had proven, in the wild, that their prefixed implementation matched the existing unprefixed implementations. Instead of being a minor gamble, unprefixed properties would come as close to a guarantee as anything we’ve seen to date.

Conclusion

If the history of web standards has shown us anything, it’s that hacks will be necessary. By front-loading the hacks using vendor prefixes and enshrining them in the standards process, we can actually fix some of the potential problems with the process and possibly accelerate CSS development.

So the next time you find yourself grumbling about declaring the same thing four times, once for each browser, remember that the pain is temporary. It’s a little like a vaccine—the shot hurts now, true, but it’s really not that bad in comparison to the disease it prevents. And in this case, you’re being vaccinated against a bad case of multi-year parser hacking and browser sniffing. We suffered through that long plague once already. Prefixes will, if used properly, ward off another outbreak for a long time to come.

58 Reader Comments

Load Comments