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.

[Note: Thanks to Louis Lazaris and Web Tools Weekly, this post was originally published on October 21, 2015, at http://www.sitepoint.com/pseudo-comments-in-css-or-how-browsers-parse-styles/. It is actually a follow-up (slightly modified) to my earlier post on CSS line rule comments. ]

tl;dr

You can skip to the Best Practice section for when to use and when to avoid pseudo-comments before coming back to the next section.

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, */, such that

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

a rule declaration in comments will be ignored,

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

and 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 rule 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 rule is not applied */
}

And that shows that we can use malformed declarations as…

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:

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 rule 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;
}

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 rule missing a name, colon, or value).

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

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

That’s fixed by adding a semi-colon after the comment, before the next rule (thus the background blue rule 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 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 rule comment (i.e., a single rule is ignored). In the following, the first background rule 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 rule 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 as targeted malformed-ness

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 rule 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 dangerous) than others. And the quoting characters "", and '' are processed differently than the grouping characters (), [], {}.

Quoting characters

The unpaired apostrophe in the second rule below will prevent the subsequent rule 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 rule 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

The grouping characters (), [], {} have more drastic effects, interfering more extensively with the parser’s block recognition rules, and so will “comment” out more than single rules. For the sake of completeness, we’ll examine a few of these.

For example, the appearance of unmatched starting group characters suppresses all subsequent rules 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; rule is processed; all rules and selectors after that in the entire stylesheet will be ignored:

body {
  background: red;

  {  /* *every* rule 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 rules 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 rule that appears. In the following, the background will be gold:

body {
  background: red;

  ]
  background: white;  
  background: blue;
}

A closing brace, }, however, will suppress all rules 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:

  • a body declaration denoted by braces, { ... } (such as @media),
  • a rule declaration closed with a semi-colon ; (such as @charset).

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. That’s because the parser treats a pseudo-commented @import as a Malformed statement, and looks for the next matching brace } in order to complete the next ruleset.

Thus, a pseudo-comment before an @import in a series of @imports will suppress all subsequent @imports and the first rule 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 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 the 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 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 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.”
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:

  • 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 rules, 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.

Back to top

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s