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

Why is the Hue Circle... Circular?

Last updated:

As you may know from following this blog, I occasionally get obsessed with the math and science of colors. I've been interested in this subject my entire life; it's a perfect blend of subjective experience (color vision) with objective logic (color spaces, color addition, etc). Plus, I'm colorblind (deuteranopic - I can't see green as well as an average person), so it always held an extra frisson of interest for me.

One thing always bothered me as a kid, though, and it took me until only a year or two ago before I finally figured it out: why is the EM spectrum (where all the colors live) a line, while hue is generally represented as a circle? What is the reason that blue "wraps around" to red in every art book, when it doesn't seem to happen in reality? Even rainbows disagree - they go red->purple and then stop.

To answer this properly, we'll have to lay down some groundwork.

Cone Response Curves

Our color vision comes from the cone cells in our retina. We have three of them, each specialized at seeing a particular color. All of our light-sensing is accomplished by nerves that contain a particular chemical from the opsin family which changes shape when it absorbs photons of a particular wavelength. The nerve detects this shape change, and sends the information off to the brain - "I've been hit!".

We have four different opsin molecules in our eyes - rhodopsin in our rod cells, which responds to most of the visual light spectrum, and three specialized photopsins in each of our cone cells, which respond to a much narrower slice of the spectrum. Each one responds most strongly to one particular wavelength, and responds more weakly as the photon it absorbs moves away from that target wavelength, with one peaking at roughly a "red" color, one peaking at a "yellow-green", and one peaking at a "blue". They each overlap significantly, so a photon that triggers one type of cone will also trigger the other cones, to some degree.

Response curves for our four vision cells

All of our color vision comes from the amount of nerve response we get from these three types of cells. If our brain receives a strong red response, a medium green response, and a weak blue, it knows it's looking at red. If red and green are both moderately high, but blue is still weak, it's looking at yellow. (The wavelength of yellow light lies between red and green, so it's near the peaks of both red and green cone response curves.) Etc.

Spectral Colors

Now we know enough to talk about spectral vs non-spectral colors. A spectral color is the color we see when light of a single visible wavelength hits our eye. For example, if we shine light with a wavelength of about 560nm, you'll see it as yellow.

A non-spectral color, on the other hand, is any color we see that isn't created by a single wavelength of light, but rather by a combination. For example, white is produced by receiving light of multiple wavelengths across the spectrum, so all the cones get activated roughly the same amount; no single wavelength of light can ever produce that kind of response.

Non-spectral colors don't really "exist", in terms of "what colors exist in the light spectrum", but they often mimic spectral colors. For example, if you shine a combination of red and green light, your eyes will record a roughly equally high response from the red and green cones, and a low response from the blue cones. This is exactly the same as the response that spectral yellow produces, so this combination (red + green) gives us non-spectral yellow. This is how you see yellow on your computer monitors, which only have red, green, and blue pixels. Non-spectral colors aren't precisely the same (the green light component of non-spectral yellow, for example, activates the blue cones a little bit more than spectral yellow does), but it's close enough to fool our eyes most of the time.

In fact, you can produce non-spectral mimics of all the spectral colors between the peak of the red curve and the peak of the blue curve, by just using two lights, one with a slightly higher wavelength and one with a slightly lower wavelength. You can think of it as "averaging" the two wavelengths. (You can probably produce mimics a bit further out from each with some effort, but it would be more complicated.)

Now What About Purple?

So, now we can phrase our question a bit better - why does spectral purple (wavelength ~400nm) look like non-spectral purple (combination of ~440nm and ~650nm)? The "averaging" explanation doesn't make any sense; it tells us that we should be seeing some sort of non-spectral green. (Actually, the "averaging" explanation only works for wavelengths that are relatively close - blue and red are too far apart for it to work.) So what's the deal?

Most response-curve graphs you find are fairly simplistic. They show each cone's responses as a simple hill - smoothly climbs, peaks, then smoothly descends to zero. The graph I posted earlier in this post, though, shows a slightly more accurate view. It's not super-obvious, but the red and green cones' responses jump up a little bit again, over in the blue-light region. This means that spectral purple doesn't just activate the blue cones, it also slightly activates the red and green cones (mostly the red, iiuc).

This finally explains why mixing blue and red light also looks purplish - it produces the same cone responses! In other words, non-spectral purple only exists through a weird quirk of biology, because your red and green-sensing cones have an irregular response curve; it's not inherent in the physics of light (unlike many of the other non-spectral colors, which the "averaging" argument generally explains).

This also explains why magenta doesn't show up in the rainbow. Spectral purple, at best, only produces a weak red response. It's impossible to get a spectral color that produces both a strong blue and a strong red response. That can only happen in non-spectral purple, where you can easily ramp up the red intensity and get a strong response. Probably about 2/3rds of the space between red and blue on hue circles is purely non-spectral.

So, finally, mystery solved. 10-year-old me would have been ecstatic to hear this kind of explanation, because I virtually never see this topic discussed in articles about color vision.

Postscript: Imaginary Colors

I've sometimes seen the phrase "imaginary color" used to refer to colors that only exist as non-spectral colors, which no single wavelength of light can possibly produce. Most of the "purple" range, then, is "imaginary".

("Imaginary color" is also sometimes used for a different concept that touches on another weird quirk of color vision: colors like "reddish green" are difficult or impossible to imagine. It can also describe colors like "supergreen" that you can only see by tricking your eyes. There's a biological reason for these!)

Unfortunately, humans don't have very many imaginary colors, with our three simple cones. They give us 7 broad categories of colors, based on what cones are most activated: high red, high green, high blue, high red+green, high green+blue, high red+blue, and high everything. Of these, only the last two are imaginary, giving us the maroons and the grays (including white). We just can't see any more - every other combination either produces something close to a spectral color, or moves outside of our visual spectrum entirely.

If we had more types of cones, though, we'd have more imaginaries, too. Most birds have a fourth cone that senses UV light, out past blue. This gives them several more imaginaries, such as red+UV and green+UV, plus several three-cone combinations. Pigeons are believed to have five cones, as do several insects, some eels, and a handful of other animals. And then, of course, there's the mantis shrimp, which has 16 types of cones, and can detect polarization of light.

If you gained new cones that peaked between your existing cones, some non-spectral colors would no longer mimic spectral ones. For example, if we had a yellow cone, red+green light would produce an imaginary color, rather than looking like spectral yellow, because our eyes would have different responses in the two situations.

This postscript is kinda rambly, so I guess my main point is: COLORS ARE COOL AND INTERESTING.

2014 State of Element Queries

Last updated:

About a year ago, I talked about Element Queries and why they were difficult to make work. It's about time for an update.

Short version: Not much has changed. Naive element queries still suffer from circularity issues, and there's been no real effort to specify anything to get around it. However, Shadow DOM seems well-placed as a way to get an EQ feature into the web.

What Are Element Queries Again?

An "element query" is like a media query, specifically the 'width'/'height' MQs, but they apply to the width/height of the element's container, rather than of the screen or device.

Why Were Element Queries Problematic Again?

Element Queries (let's just call them EQs from here on) suffer from basic circular dependency issues.

Here's a simple example: Say you have an element which is normally 100px wide, but it uses an EQ to say that if its container is less than 200px wide, the element becomes 500px wide. (This is silly, but bear with me.) If the container is explicitly sized (width: 150px; or the like), this is fine, but if the container is sized to its contents (float: left;, for example), then you have a circular dependency: if the element is 100px wide, the container is 100px wide, but that trigger the EQ, so the element is 500px wide, so the container is 500px wide, but that disables the EQ, so the element is 100px wide, so the container is 100px wide, etc.

What About Them <iframe>s?

A funny detail of this is that EQs already exist, at least in some form. If you use a MQ on a page displayed in an <iframe>, the MQ is resolved against the size of the <iframe> element. This is exactly an EQ, if you treat the <iframe> as the "container" in the above explanations.

This avoids the problems outlined above because an <iframe> cannot depend on the size of its contents. An <iframe> is only ever explicitly sized; if it doesn't have anything explicit, it defaults to being 300px wide and 150px tall. The contents never affect its size in any way, and so the circularity problem never comes into effect - if you get super wide because the <iframe> is skinny, nobody cares; the <iframe> just sprouts a scrollbar.

How Can We Get Around This?

Of course, you don't want to use <iframe>s all over your page. It would be nice if we could somehow establish the same sort of situation while keeping all our content in the page together.

The first thought people usually have is to create some CSS value, perhaps a display value or something like that, which makes the element no longer pay attention to its contents. Unfortunately, this doesn't work well - since you don't know which elements are "independent" until you apply CSS, you might do too much work on initial layouts before you figure out that you're all wrong. Also, since the boundaries aren't immediately clear (and in fact can shift over time, if the property starts/stops applying to various elements), it's somewhat difficult to understand what elements are allowed to use EQs.

Better would be something that would alert browsers to the "independent" nature of the element before they start applying CSS. For example, an HTML attribute would work for this. Unfortunately, it's annoying to have to sprinkle presentation-based attributes over your page just to get your styling to work right.

I think that we've got a good opportunity to make this less bad with Web Components, though. They already represent a useful "boundary" in the form of the shadow DOM, so the potential confusion of not knowing which element your EQ is applying to would go away - it always applies to your host element, assuming it's marked as "independent".

While Level 1 of the Shadow DOM specs are stabilizing right now, I think this is something reasonable to aim for as a Level 2 feature. You'd be able to set some switch when defining your custom element, or perhaps set it per ShadowRoot, which would make your component's layout independent of its contents, just like <iframe>. Then, EQs would work inside the shadow, referring to the size of the host element. Outside of a shadow, or in a non-"independent" shadow, EQs would just always be false.

So that's the state of Element Queries in 2014. We're not really any closer to having them than we were in 2013, but there's light on the horizon for a possible good solution.

Basic Category Theory Terms

Last updated:

Magma: A set of objects that can be "added" or "combined" in some way, so that if you add any two of them the result is also in the set. That's it.

Semigroup: A magma whose "addition" operation obeys associativity: (a + b) + c must give the same answer as a + (b + c). For example, strings form a semigroup with concatenation as their "addition" operator: ("foo" + "bar") + "baz" = "foobarbaz", etc. So do the integers under the min() operation.

Monoid: A semigroup with a "zero" element: something that, when "added" to any element in the group, produces that other element again. For example, the empty string is the "zero" element in the string/concat monoid. The integers with min() aren't a monoid, but they become one if you add infinity as the "zero" element. Lots of interesting things are monoids; it's a very simple and useful constraint to satisfy. If you satisfy "monoid", you get a free version of the reduce function using your operation.

Group: A monoid where every element has an opposite or "negative" version, so that if you "add" an element and its opposite, you get the "zero" element. Strings with concatenation are not a group - concatenation only ever produces larger strings, not smaller ones, so you can't ever take a string like "foo" and add another string to it to get the empty string. On the other hand, integer/addition is a group - the opposite of each member is just the negative integer corresponding to it.

An Alternate Path to a Group

There's an alternate way to get from magma to group. You add different properties along the way, but when they're all combined they add up to the same thing that the previous path did. However, these properties are harder for most things to satisfy, and so they're not generally as useful; most of the things you use that'll satisfy any of these do so because they're all the way to a group already.

Quasigroup: This one's a little subtle. It's a magma, but the operation has the property that you can transform any element of the set to any other element by combining it with an appropriate third element. It has to do so whether the first element is the left or right argument, but the element you choose to combine it with can be different in the two cases.

The integers with subtraction form a quasigroup - if I want to transform "2" into "5", I can do it with 2 − -3 = 5 and 7 − 2 = 5. (So do the integers with addition, but they satisfy so many of these definitions that they're not very interesting to talk about.) On the other hand, strings with concatenation are not a quasigroup - while you can transform some strings, like "foo" to "foobar", you can't generally do it both ways (nothing satisfies the equation X + "foo" = "foobar"), and there are some strings you can't transform between at all.

Loop: A quasigroup with a "zero" element, which transforms an element into itself regardless of which side it starts on. Integer/subtraction is not a loop: X − 0 = X, which is what we want, but 0 − X = -X, which isn't what we want (and no other number satisfies either side, obviously.) I can't come up with a good example of something that's a loop but not a group right now.

Group: Ah, back to group. Along this path, a group is a loop with associativity.

Going Further

Abelian or Commutative X: Any of the above types might have an operation which is commutative - it doesn't matter which order the arguments are. Integer/min is an abelian monoid, because min(1, 10) = 10 and min(10, 1) = 10.

CSSOM Value API Proposal Dump

Last updated:

This is a basic sketch of a CSSOM Values API, where rather than returning strings for everything, we use proper JS objects. No more parsing of "5px" for you!


  1. Must faithfully and directly reflect the property grammar in an obvious way. While specialized APIs might be useful to expose on the side, we want to be able to interact with properties in a generic way to help tooling and teaching.
  2. Must gracefully handle the kinds of property changes that the CSSWG commonly makes. DOM Level 2 Style failed this, for example, because it made shorthands return null - we pretty regularly make complex properties into shorthands, and this would break code.
  3. Must respect JS idioms. No bullshitty Java-style APIs, even if they would be more efficient. Those are just horrible, and people rightfully complain whenever we do them.
  4. Must be efficient, particularly when used in a rAF animation - we shouldn't need to create several objects (which become garbage) each iteration just to update a property.

Common Types of Property Changes

As CSS grows and changes, we alter and extend existing properties in various ways. There are a handful of common ways that have emerged over the years, though, that we must be sure to handle well in the API:

  1. Breaking apart a complex property into a shorthand with sub-properties.
  2. Turning a property with a single value into a list-valued property that accepts a comma-separated list.
  3. Adding more terms to the grammar of an existing property, such as taking a property that accepts a <length> and making it <length> || keyword, so it goes from 1 value to 1 or 2 values.

Proposal Sketch

We define an additional accessor for style data, provisionally called .css. Hanging off of this are mutable properties, one for each CSS property. (Dunno if we want to allow both camelCase and dash-case, or stick with only camelCase.)

The value of each property is an array containing one or more CSSValue objects. This has to be an array at all times, even for properties that only take a single value, like 'width', because 'width' might take two values in the future and we have to be ready for that to happen. The ordering of what values go in what indexes are defined by the spec and stable over time. Call this array a value list.

If you assign a CSSValue directly to the property, rather than to an index in the array, it's treated identically to if you'd wrapped it in an array. That is, el.css.width = 5px; and el.css.width = [5px]; are the same thing, for convenience.

The CSSValue objects (described more below) are going to be "value objects" from JS, which is a new kind of thing halfway between primitives (like numbers and strings) and objects. They're fully immutable, and it's planned that you can get syntax support for simple numeric ones, so that just appending a suffix to your number automatically makes a value object - using var x = 10px; in your JS will be equivalent to saying var x = CSS.px(10);.

For example, here's box-shadow: = "none";
// [] = "1px 1px 3px red";
// [1px, 1px, 3px, 0px, CSS.color(red)]

If a property is list-valued, the array also has a .l property on it, which contains an array of those value lists. If you operate directly on the indexes of the value list, it's identical to operating on the first element of the .l property + unsetting all the others.

That is:

el.css.boxShadow[0] = 5px;

sets the horizontal offset of the first boxShadow and unsets all the other box shadows, identical to:

el.css.boxShadow.l[0][0] = 5px;
el.css.boxShadow.l.length = 1;

Issue: using a value list is kinda clumsy. It would be better to use named values, but that requires even more work for every property, plus more bikeshedding, and is still weird for things with a single value currently like 'width'. Maybe we can do both? Expose the value as an array, and some properties also hang individual components off of named values. Drawing from Anne's old proposal, maybe hang the named-components object off of a .m (for map) property.

Issue: .l is a crappy name. We can probably come up with better. But if we use named subvalues, we want to make sure that anything we attach to every value won't be clash with a useful name. A single letter like l seems promising for that.

Issue: Should the length of the value list and the lists of value lists be frozen? So you can manipulate values, but can't assign to indexes that don't exist? That would make things a bit saner, probably. After all, how do we serialize things if you just assign straight to el.css.backgroundImage.l[50] = ...;? Some list-valued properties may have a "null value" to use in that situation, but not all of them do. On the other hand, if we ever do the "all list-valued properties are shorthands for indexed longhands" thing, you'll be able to directly set "background-image-50" to something, so maybe we'll just have to add null values to everything anyway.

Issue: How should we handle partial updates that are invalid until completed? Just rely on the style flushing machinery to batch changes and only validate when they're flushed?

CSSValue Objects

CSSValue objects hold all of the terminal values, like <length> or <color>. The most important thing to know about them is that they're immutable. (This'll be accomplished either by deep-freezing them upon construction, or hopefully by making them from JS value objects.)

Immutability helps with efficiency, because it means the engine can cache values and reuse them in different places without you knowing - rather than your script creating a thousand different object that all represent "10px", it creates a single immutable one and uses it in a thousand places. (JS value objects let us override == and make this explicitly work.)

Immutability also cuts out a ton of potential problems from mutability. Every length, for example, is relative to the element its on, the property its on, and even the index of the value list it's in. For example, the length value in el.css.width is sometimes relative to the element's own width, for resolving percentages. What happens when you rip it off and assign it to a different property? This, and lots of related issues, disappear completely when you have immutable values.

As a consequence of this, assigning to a property value doesn't use the actual object you assigned (unless it can undetectably share). So:

el.css.width = 10em;
print(el.css.width.px); // null
print(el.css.width.em); // 10
print(el.computedCSS.width.px); // 160

el.css.width = el.computedCSS.width;
print(el.css.width.px); // 160
print(el.css.width.em); // null

(In this case, computed lengths are always "absolutized" in CSS, so we take the absolute length as the appropriate intent. Alternately, we could perhaps have computed values and beyond remember what unit their associated declared value was created with, when that's relevant. But that's a lot of difficulty and has some weird edge cases.)

CSSValue Subclasses

Each value type in CSS has a CSSValue subclass, like CSSLengthValue, CSSTimeValue, or CSSColorValue. Here's some example IDL for CSSLengthValue, one of the more complicated ones:

[Constructor(DOMString dimension),
 Constructor(double val, DOMString unit)]
interface CSSLengthValue : CSSValue { 
  readonly attribute double? px;
  readonly attribute double? pt;
  readonly attribute double? in;
  readonly attribute double? cm;
  readonly attribute double? mm;
  readonly attribute double? em;
  readonly attribute double? ex;
  readonly attribute double? vw;
  readonly attribute double? vh;

(In addition to the direct constructors, there will be short constructor functions on the CSS object as well. Rather than new CSSLengthValue(5, 'px'), you can just write CSS.px(5).)

Depending on exactly how you obtain a length, some of the properties will be null, because the browser doesn't know how to interconvert between them.

For example, constructing a pixel length directly will let you ask for the size in pt or in, but not in em:

var l = 400px;
print(l.px) // 400
print( // 300
print( // approximately 4.16666666
print(l.em) // null
print(l.vw) // null
print(l.percent) // null

Pulling a value directly off of a declared value is the same:

el.css.width = 400px;
print(el.css.width.px) // 400
print(el.css.width.em) // null
el.css.width = 25em;
print(el.css.width.px) // null
print(el.css.width.em) // 25

However, a computed value would have more fields filled in:

el.css.width = 400px;
print(el.computedCSS.width.px) // 400
print(el.computedCSS.width.em) // 25 (assuming font-size:16px)
print(el.computedCSS.width.vw) // 40 (assuming 1000px viewport)
print(el.computedCSS.width.percent) // null (needs layout info)

We'll need at least the following interfaces:

  • CSSLengthValue (.px, .em, .percent, etc)
  • CSSTimeValue (.s, .ms)
  • CSSAngleValue (.deg, .rad, .turn, etc)
  • CSSFrequencyValue (.hz, .khz)
  • CSSResolutionValue (.x, .dpi, etc)
  • CSSKeywordValue (.value + a stringifier?)
  • Strings are just strings
  • CSSURLValue (Would be cool if we could just use URLValue, the planned immutable/value object version of the URL interface)
  • CSSColorValue (.red, .green, .hue, etc)

Dealing With Functions

After some though, I think functions should be special-cased for each instance. We could expose a generic interface for them, similar to what we do for properties, but I don't think it'll be very useful, and the argument for a shared generic "shape" among functions is weak, unlike for properties, because other types of values are already custom.

So this means each function probably maps to a unique interface. They don't have to - all the color functions (rgb(), hsl(), etc) will all map to the CSSColorValue interface, for example - but in general they will.

They should expose their values in whatever way makes the most sense for that function. For example, the image() function will expose a list of urls/colors, plus some metadata things.

They'll also be immutable, for all the same reasons as the other values. This might call for an easy way to “mutate” one piece of a function (provide a “keypath” and a value, and return a new object with the indicated piece replaced?), but we can skip that until we're sure it's needed.

For example, here's what 'attr()' might look like:

interface CSSAttrFunctionValue : CSSValue {
  readonly attribute DOMString attribute;
  readonly attribute DOMString type;
  readonly attribute sequence<CSSValue> fallback;

And how it could be used:

el.dataset.width = "100";
var attr = CSS.attr('data-width', 'px', [50px])
el.css.width = attr;
print(el.css.width) // [CSS.attr(...)]
print(el.computedCSS.width) // [100px]

print(CSS.convert(attr, el)) // 100px

Note how the attr() function is preserved in the declared value, but disappears in the computed value, or when associated with an element.


There are a few drawbacks to this approach.

Unless we use JS value objects (which don't exist yet), we can't do math directly on values. We can't add together two lengths:

var x = 5px;
var y = 10px;
var z = x + y; // NaN
var z = CSS.px(x.px + y.px); // success!

Nor can we do in-place updating:

el.css.width += 5px; // wrong
el.css.width = CSS.px(el.css.width.px + 5); // success!

If we wait for value objects, which I'm told we'll get Real Soon Now®, then both of these will work nicely.

Either way, though, we still won't be able to do something like:

el.css.width.px += 5;

Since the length is immutable, you can't ever assign directly to one of its members. We can't fix this without going fully mutable, which I'm not willing to do. I think it's easy enough to just construct a px value and add that to the 'width' property directly that I'm not too concerned about this.

Converting Between Values

Like I said earlier, if you construct a CSS.px(5) value, the .em property will be null, because you can't convert between px and em unless you know the font-size. In general, full interconversion requires associating the value with an element, a property, and in some cases an index into the property. (For example, a percentage in background-position is relative either to the width or the height, depending on where it appears.)

So, we need a convenient way to obtain these conversions. I think we can do something like:

CSS.convert(value, element, optional property, optional index)

This would return a new value of the same type, with all possible things filled in, based on the information you give. Example:

var x = 32px;
print(x.px);     // 32
print(x.em);     // null
print(x.percent) // null

el.css.fontSize = 16px;
var y = CSS.convert(x, el);
print(y.px)      // 32
print(y.em)      // 2
print(y.percent) // null

el.parentElement.css.width = 400px;
var z = CSS.convert(x, el, 'width');
print(z.px)      // 32
print(z.em)      // 2
print(z.percent) // 8

Single Line Comments (//) in CSS

Last updated:

Hi haters! This article is half informative, half tongue-in-cheek. Remember that I'll delete your ass like it wasn't a thing if you're rude!

CSS uses the same "block comment" syntax as the C-like languages - you start a comment with /*, and end it with */.

However, CSS is missing the "line comment" syntax that those languages have, where everything from // to the end of the line is commented out.

People have asked for this syntax to be added repeatedly, but unfortunately our hands our mostly tied - CSS minifiers don't know about line comments, so if we added it and the minifier removed all the linebreaks (as they tend to do), the line comment would accidentally comment out the entire rest of your stylesheet!

That said, CSS does actually already allow you to use //, after a fashion. It's not quite a line comment, but a next construct comment.

That is, whenever you use //, the next CSS construct - either declaration or block - will be "commented out". For example:

.foo {
  width: auto;
  //height: 500px;
  background: green;

Here, the // commented out the height declaration.


//@keyframes foo {
  from, to { width: 500px; }
  50% { width: 400px; }
@keyframes bar {
  from, to { height: 500px; }
  50% { height: 400px; }

Here, the // commented out the first @keyframes declaration.

Note, though, that if you try to use // just for writing comments into your stylesheet, you have to be careful - raw text isn't a CSS construct, so it'll look past that and comment out the next construct in your page:

// Do some stuff.
.foo { animation: bar 1s infinite; }
/* Whoops, the .foo block is commented out! */

Update: Whoops, got my own CSS syntax wrong. Sorry, winkybarf ({};), you're no good.

You can avoid this by ending your text comment with a {} (if you're outside of a rule) or a ; (if you're around some declarations), to let the CSS engine know that you're just kidding around:

// Do some stuff {}
.foo { animation: bar 1s infinite; }
/* Success! */

The astute among you will have noticed (or already known) that using // like this isn't really "commenting out" anything at all. Instead, it's just putting an invalid value into the stylesheet and relying on CSS's error-recovery rules to kill the next construct on the page and then recover gracefully. Because CSS's error recovery is well-defined, you can rely on every browser that implements it correctly to work in the expected way.

That said, this is still potentially a useful trick for those who really don't like having to go to the end of a line to add those */ markers, like me. ^_^