Tab Completion

I'm Tab Atkins Jr, and I wear many hats. I work for Google on the Chrome browser as a Web Standards Hacker. I'm also a member of the CSS Working Group, and am either a member or contributor to several other working groups in the W3C. You can contact me here.
Listing of All Posts

How CSS Handles Errors

Last updated:

CSS was written from the beginning to be very forgiving of errors. When the browser encounters something in a CSS file that it doesn't understand, it does a very minimal freak-out, then continues on as soon as it can as if nothing bad had happened.

There's a very good reason for this! We knew that CSS was going to be expanded over time, with new properties, new values, and other new things added to it. "New" things look exactly like "invalid" things to an old browser, so if you want the language to gracefully handle new stuff, you have to give it smart error-handling.

However, have you ever been curious about exactly how CSS's error-handling works? It's really simple. At any given time, when parsing a CSS stylesheet, the browser is either trying to build an at-rule, a style rule, or a declaration (/property) inside of one of those.

If the browser is in trying to parse a declaration and it encounters something it doesn't understand, it throws away the declaration, then seeks forward until it finds a semicolon that's not inside of a {}, [], or () block.

If the browser is trying to parse a style rule and it encounters something weird, it throws away the style rule, then seeks forward until it finds a complete {} block.

If the browser is trying to parse an at-rule and it encounters something weird, it does both of these - it throws away the at-rule, then seeks forward until it either finds a complete {} block, or a semicolon that's not inside of a {}, [], or () block. (This is because at-rules come both in block form like @media and statement form like @import.)

This error-recovery behavior ignores a chunk of your stylesheet that is small enough to prevent one error (or new feature) from wrecking the display of your page, but large enough that the behavior is predictable and friendly.

Could we do it differently?

For example, say that we changed the behavior to ignore a smaller chunk of your stylesheet - if there's an error inside of a function, it just ignores the function. Then, markup like this:

.foo {
  color: white;
  background: black;
  background: radial-gradient(black, rgba(0,0,0,.5)) transparent;
}

...would no longer work correctly in browsers that don't understand the radial-gradient() function. It would just produce a transparent background. The current behavior ensures that the whole declaration dies, so the preceding background:black wins instead.

Our current behavior may not be ideal in all circumstances, though - for example, it means that if you have a style rule with multiple comma-separated selectors, and one of them isn't recognized, the entire rule dies. This makes it very hard to use prefixed or experimental selectors, as you have to instead pull the selectors apart and repeat the style rule's contents multiple times. It would probably be better to fix this and just kill the one selector. Unfortunately, a lot of content on the web depends on this behavior now (either by accident, or purposely using a nonsensical vendor-prefixed selector to make the rule only target a single browser), so we probably can't change it.

(a limited set of Markdown is supported)