;

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 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:

body {
  background:
  background: blue; /* this declaration is not applied */
}

And that shows that we can use malformed declarations as…

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:

h1, h2 {color: green }
h3, h4 & h5 {color: red } /* <= ignored */
h6 {color: black }

Again, in the following, the second and third declarations are ignored due to the presence of extra characters in the background property name:

body {
  background: red;
  xbackground: white;    /* property name is not recognized */
  y background: blue;    /* property name is not well-formed */
}

A quick tour around the English language keyboard shows the following special characters will act as single-line declaration comments:

selector {
  ~ property-name: ignored;
  ` property-name: ignored;
  ! property-name: ignored;
  @ property-name: ignored;
  # property-name: ignored;
  $ property-name: ignored;
  % property-name: ignored;
  ^ property-name: ignored;
  & property-name: ignored;
  * property-name: ignored;
  _ property-name: ignored;
  - property-name: ignored;
  + property-name: ignored;
  = property-name: ignored;
  | property-name: ignored;
  \ property-name: ignored;
  : property-name: ignored;
  < property-name: ignored;
  . property-name: ignored;
  > property-name: ignored;
  , property-name: ignored;
  ? property-name: ignored;
  / property-name: ignored;
}

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):

body {
  background: ;          /* ignored */
  background: blue;      /* processed */
}

The effect is the same with a pseudo-comment on a line missing its semi-colon:

body {
  background: # red   /* ignored */
  background: blue;   /* also ignored */
}

and corrected by restoring the semi-colon:

body {
  background: # red;  /* ignored */
  background: blue;   /* processed */
}

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:

body { 
  //
  background: white !important;  /* ignored */
  background: blue;
}

A pseudo-comment placed after a valid declaration on the same line will suppress a declaration on the next line. In the following, the background will be white rather than blue:

body {
  background: white; // next line is ignored... 
  background: blue !important;
}

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

// body {
  background: white !important;
}

or next-line:

//
body {
  background: white !important;
}

Pseudo-comments work by taking advantage of the spec’s Rules for handling parsing errors. In effect, they work by exploiting their malformed-ness.

Unknown values

“User agents must ignore a declaration with an unknown property.”

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):

body {
  background: red;
  'background: white;  /* ignored */
  background: blue;    /* also ignored */
}

However, a third declaration after the apostrophe will be processed (thus the background will be gold):

body {
  background: red;
  'background: white;  /* ignored */
  background: blue;    /* also ignored */
  background: gold;    /* processed */
}

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:

body {
  background: red;

  (
  background: white;
  background: blue;
  background: fuchsia;
  )

  background: gold;
}

A closing comma or bracket will suppress only the next declaration that appears. In the following, the background will be gold:

body {
  background: red;

  ]
  background: white;  
  background: blue;
}

A closing brace, }, however, will suppress all declarations to the end of the ruleset. In the following, the background will be red:

body {
  background: red;

  }
  background: white;
  background: blue;
}

At-rules

At-rules have two forms:

Pseudo-comments on body-block at-rules behave the same as for selectors (i.e., the entire at-rule is ignored).

Pseudo-comments applied to at-rules with body blocks

For at-rules containing body blocks, such as @keyframes, @media, @page. and @font-face, the entire at-rule ruleset is ignored when the at-rule is preceded by a pseudo-comment, whether inline

// @media (min-width: 0) {
  body {
    background: white !important;
  }
}

or next-line:

//
@media (min-width: 0) {
  body {
    background: white !important;
  }
}

Pseudo-comments applied to at-rules without body blocks

At-rules without blocks, such as @charset and @import, provide a fascinating exception to inline pseudo-comment behavior.

An at-rule with a pseudo-comment after the keyword will be ignored:

/* the pseudo-comment before url()
   suppresses the entire @import */
@import // url('libs/normalize.css');

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:

// @import url('libs/normalize.css');

/* NONE of these loads because previous statement is
   processed as a malformed statement, and the parser
  looks for the next matching braces. */
@import url('libs/normalize.css');
@import url('libs/example.css');
@import url('libs/other.css');
@import url('libs/more.css');
@import url('libs/another.css');
@import url('libs/yetmore.css');

The fix for this is surprisingly simple: just add an empty body block after the comment @import

// @import url('libs/normalize.css');

{}  /* now, the next import will load */

@import url('libs/normalize.css');

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:

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

body { background: blue; }   /* this whole block will not be processed either! */

That can be rescued with outer quotes, either inside the braces

@comment { 
  "I'm not processed in any way."  }  /* fixed */

body { background: blue; }   /* this block will work */

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.

source

Compressed mode will normally strip out all comments, unless the comment is preceded by /*!.

However, you can use a single-character pseudo-comment, such as # and the output will contain the commented line.

body {
   # background: red; 
}

Less

Both block-style and inline comments may be used.

source

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.

/*!
 * This will appear in the output.
 */

source

Best Practice

“Readability counts.”
https://www.python.org/dev/peps/pep-0020/‘>Zen of Python

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:

Use pseudo-comments:

Avoid pseudo-comments: