Print Stylesheet Approaches: Blacklist vs Whitelist

The "blacklist" is a common approach to print stylesheets. We know that people probably don't need to see our site navigation if they print out an article on our site. So we hide it from print like we would hide it from the screen (display: none;).

Is there a way to reverse that?

The Blacklist Technique

You select all the elements on the page you don't want printed, and put that into some CSS that is applied to the print media. Perhaps in a block at the bottom of your main stylesheet.

@media print {
  .main-navigation, .comments, .sidebar, .footer {
     display: none;
   }
}

Alternatively, you could leave the job mostly the the HTML. You could create a "don't print" class and apply it as needed.

@media print {
  .dont-print {
    display: none;
  }
}
<section id="comments" class="comments dont-print">
</section>

Blacklisting is a common tactic, posted in print stylesheet tip articles all around the internet.

The only trouble with it is that it requires maintenance. HTML changes, so you'll need to maintain either your list of selectors to not print, or the classes in the HTML to be on the right elements. Easy to forget.

The Whitelist Technique

Whitelisting would be the opposite technique. In your print styles, everything would be hidden from print except for elements you explicitly choose.

Don't get your hopes up too much though, it's pretty tricky to pull off. My first thought was to universally hide things, then override with a class.

/* Bad idea #1 */
@media print {
  * {
    display: none;
  }
  .print-me {
    display: block;
  }
}
<main class="main-content print-me">
</main>

There are two problems here:

  1. The display property isn't inherited, so even though you told an element to show itself again, it's child elements will still be hidden by the universal selector selecting and hiding them.
  2. The parent elements of the element you told to show itself may still be hidden.

The latter of which we could maybe solve with...

/* Bad idea #2 */
@media print {
  * {
    display: none;
  }
  .print-me,
  .print-me * {
    display: block;
  }
}

... selecting the child elements and having them show themselves again. But that would make all child elements block-level, which is bad. You don't want your <a>, <em>, <strong>'s and anything else that is inline, inline-block, inline-table, inline-flex, etc to become a block.

That would be harder to maintain than a blacklist.

I considered manually requiring all parent elements to also have "print-me" class, but that will expose sibling elements to being printed when they shouldn't be (don't have the class). I tried fixing that with a

/* Bad idea #3 (addition to previous) */
.print-me ~ *:not(.print-me) {
  display: none;
}

But that doesn't handle previous siblings.

This could be a pretty good use case for parent selectors in CSS, since you could potentially use a parent selector to un-hide a parent element if it contained an element with that specific class name. Theoretically something like *:contains(.print-me) { display: block; }.

There may be a pure CSS solution to this yet. I admittedly didn't spend hours and hours on this. There may be a clever tactic here I'm missing. I know that the visibility property doesn't inherit, so there may be potential there, but that doesn't effect layout like you may want it to. I'm not sure :not() was explored to its fullest potential here either.

If you want to play with ideas, here's a test page for you. It just has a button that toggles a class on the parent of a bunch of content, so you can pretend that class is like print styles and get it to do what you want.

My closest attempt so far is a just give up and use JavaScript attempt. Using jQuery here for easy DOM traversal, the crux of it is automatically applying a class to all parents of an element ensuring they can be printed:

$(".print-me")
  .parents()
  .addClass("js-print-me");

Note I'm using a slightly different class name, so it's not affected by the same rule that selects all descendants to display.

@media print {
  * {
    display: none;
  }
  .print-me,
  .print-me * {
    display: block;
  }
  .js-print-me {
    display: block;
  }
}

There are even sorta-kinda ways to detect a print event, so you could apply/remove a parent class name to get the page in this state when a user goes to print.

This doesn't solve the inline-* problem though (where they go block-level instead of remaining what display value they used to be) which makes it fairly unusable. If you find JavaScript is an acceptable choice here, you could move the job of hiding to JavaScript as well, only applying display: none; after saving what display type it already was, so you could put it back when done. I think jQuery even kinda does this already somehow?

Rumor has it there is some future CSS something-or-other that can handling toggling of visibility in a way that doesn't tie it to layout (and that this is a pretty good use-case).