Pseudo-comments in CSS (Or, How Browsers Parse Styles)

The CSS spec does not mention it, but you can mimic C-style and/or Unix-style line comments in CSS files (with some caveats). Others have written about them before (see in particular, SitePoint’s Web Foundations post covering CSS Comments). The present post examines them in further detail.

CSS Comments

CSS parsers, per the spec, officially bless one style for comments, the multi-line comment from C-style languages, which uses a start token, /*, and an end token, */, as follows:

/*
characters between, and including, the start and
end tokens are ignored by the parser,
*/

And so a rule declaration in comments will be ignored:

body {
background: red;
/*
background: white;
*/
}

A block declaration in comments will be ignored:

/*
body {
background: red;
}
*/

In each of those examples, we are using the comment syntax intentionally to instruct the parser to ignore the content.

However, we can do that by accident, as with malformed declarations, such as

body {
background: red /* missing semi-colon */
background: blue;
}

In this example, neither background declaration is applied because of the missing semi-colon. The parser scans for the next semi-colon, determines the entire two-line statement is malformed, and so ignores the entire lexed content. The same thing happens if we leave out the property value altogether:

Pseudo-comments

We’ll refer to these as “pseudo-comments” because, properly speaking, these are not comments that terminate at an end-of-line character. Instead they work by malforming the input that follows them, even on subsequent lines. And this is due to the error handling process for Rule sets, declaration blocks, and selectors:

“the whole statement should be ignored if there is an error anywhere in the selector, even though the rest of the selector may look reasonable in CSS 2.1.”

In the following example, taken from the spec, the second ruleset is ignored due to the presence of the invalid “&” character in the selector:

Rather than use just any character, though, stick with C and Unix convention, and use either # or //:

// background: ignored;
# background: ignored;

Semi-colons

Semi-colons are the end tokens of rule declarations. Thus, they cannot “comment” text that follows them. In spec-speak, the parser treats a dangling semi-colon as a malformed declaration (a declaration missing a name, colon, or value).

As shown earlier, when regular multi-line comments are malformed, that is, when start and end tokens are not balanced around a ruleset or declaration, the subsequent declaration or ruleset is ignored by the parser. The following will in effect “comment” out both background declarations because the parser will search for the next end-of-declaration token (the semi-colon) for the affected declaration:

body {
background:
background: blue; /* both lines ignored */
}

That’s fixed by adding a semi-colon after the comment, before the next declaration (thus the background blue declaration will be applied):

Inline vs. next-line placement

This is where the “pseudo” enters into the term “pseudo-comment.” It may be reason enough not to call these “comments” at all as they break from the end-of-line convention of C or Unix-style line comments.

A pseudo-comment placed on its own line will suppress a declaration on the next line. In the following, the background will be blue:

Even a “minified” version of a CSS selector with an inline pseudo-comment will behave as a single-declaration comment. In the following, the first background declaration is ignored due to the presence of the comment token, #, recognized by the parser as terminating at the next semi-colon, and the second background declaration is recognized as well-formed and therefore applied (in this case, blue will be applied to the body background):

body { // background: red !important; background: blue; }

Selectors

The same rule-based behavior applies to selectors.

An entire selector ruleset is ignored when the selector is preceded by a pseudo-comment, whether inline

Unknown values

A declaration containing an unrecognized property name will not be evaluated, as, for example, the comment property in the following body ruleset:

body {
comment: 'could be text or a value';
}

Illegal values

“User agents must ignore a declaration with an illegal value.”

The color property defined below is ignored because the value is a string rather than a value or color keyword:

body {
color: "red";
}

Malformed declarations and statements

“User agents must handle unexpected tokens encountered while parsing a declaration [or statement] by reading until the end of the declaration [or statement], while observing the rules for matching pairs of (), [], {}, “”, and ”, and correctly handling escapes.”

body {
-color: red;
}

Declarations malformed by unmatched pairs of (), [], {}, "", and '' are more comprehensively ignored (and therefore more dangerous) than others. And the quoting characters "", and '' are processed differently than the grouping characters (), [], {}.

Quoting characters

The unpaired apostrophe in the second declaration below will prevent the subsequent declaration in the ruleset from being processed (thus, the background will be red):

In sum, you can’t terminate a single quoting character on its own line.

Grouping characters

In general, grouping characters (), [], {} should be avoided as pseudo-comments because they have more drastic effects in that they interfere more extensively with the parser’s block recognition rules, and so will “comment” out more than single declarations. For the sake of completeness, we’ll examine a few of these.

For example, the appearance of unmatched starting group characters suppresses all subsequent declarations to the end of the stylesheet (not just the ruleset). This is true of commas, brackets, and braces.

In the following, only the background: red; declaration is processed; all declarations and selectors after that in the entire stylesheet will be ignored:

body {
background: red;
{ /* *every* declaration that follows will be ignored,
including all subsequent selectors, to the
end of the stylesheet. */
background: white;
color: aqua;
margin: 5px;
...
}

When grouping characters are matched, the grouped and subsequent ungrouped declarations in the ruleset will be suppressed. In the following, the background will be red, not gold:

But a pseudo-comment that precedes an at-rule suppresses both the import and the first rule or selector after the import. This is because the parser treats a pseudo-commented @import as a malformed statement, and looks for the next matching braces in order to complete the next ruleset.

Thus, a pseudo-comment before one @import in a series of @import rules will suppress all subsequent @import rules and the first declaration or selector after the last import:

This is fun for debugging, but that behavior is peculiar enough that you should avoid the pseudo-comments approach to at-rules without body blocks, and use the multi-line syntax instead.

At-rules and Unknown at-keywords

“User agents must ignore an invalid at-keyword together with everything following it, up to the end of the block that contains the invalid at-keyword, or up to and including the next semicolon (;), or up to and including the next block ({…}), whichever comes first”

We can illustrate all that by using an unknown at-keyword, @comment, as a custom at-rule alternative to the multi-line syntax. For example, the following at-rule is parsed to the closing brace, }, determined to be malformed, and then ignored:

@comment {
I'm not processed in any way.
}

That looks harmless and readable at first, but due to the presence of the apostrophe in I'm, we’ve reintroduced the quoting character problem (i.e., you can’t terminate the single quoting character on its own line). That means, a subsequent at-rule or selector will also be ignored if our custom @comment’s body is closed on its own line, because the rule’s declaration is malformed by the presence of the apostrophe in I'm:

Or by leaving off the braces and instead terminating the pseudo-comment with a semi-colon, either inline:

@comment "I'm not processed in any way.";
body { background: blue; } /* this works */

or next-line

@comment
"I'm not processed in any way.";
body { background: blue; } /* this works */

Pre-processors

The various CSS pre-processors support similar multiline and single-line comments.

Sass

Sass supports standard multiline CSS comments with /* */, as well as single-line comments with //. The multiline comments are preserved in the CSS output where possible, while the single-line comments are removed.

Less

It is not clear (to me, at least) whether Less will suppress these comments or print them to the output. From StackOverflow posts, it appears Less will aggregate line-comments at block level.

Stylus

Stylus also supports multiline /* */ and single-line comments //, but suppresses these from the output if the compress directive is enabled. If you always want multiline comments to print to the output, use Multi-line buffered comments.

Multi-line comments which are not suppressed start with /*!. This tells Stylus to output the comment regardless of compression.

Best Practice

Comments can make obscure code more readable, but readability depends on more than one convention. Pseudo-comments in CSS are less about readability than about playing against convention (aka, the parser).

If you find you need to use pseudo-comments:

Stick to the C and Unix convention and use either // or # for the pseudo-comment delimiter.

Place pseudo-comments on the same line before the item to be ignored.

Use whitespace to separate the pseudo-comment delimiter from the intended rule ~ e.g. # background: ignored;.

Use pseudo-comments:

Use pseudo-comments for debugging, notably when using an interactive CSS edit panel, such as Chris Pederick’s Web Developer extension (chrome, firefox, opera).

Use pseudo-comments to prevent individual declarations, selectors, or at-rules with bodies from being processed.

Avoid pseudo-comments:

Avoid pseudo-comments for use with textual descriptions and at-rules without bodies (e.g., @import) ~ use multi-line/* ... */ comments instead.

Avoid the quoting characters '', "" ~ they are hard for human eyes to scan and cannot be terminated on their own line.

Avoid the grouping characters (), [], {} ~ they introduce more complicated scanning (and cannot be terminated on their own line).

Avoid pseudo-comments in production code ~ though not “harmful”, they are merely extra bytes at that point.

David Kaye has been a front-end developer since 1999, deeply interested in JavaScript, and (since 2006) test-driven development. He has worked at large firms such as Charles Schwab, The Gap, and Blue Shield of California, as well as smaller startup-sized companies including Glassdoor.