A Specificity Battle! (and other trickery)

Avatar of Francisco Dias
Francisco Dias on (Updated on )

DigitalOcean provides cloud products for every stage of your journey. Get started with $200 in free credit!

The following is a guest adventure by Francisco Dias from HubSpot. I saw Francisco and Cris Necochea give this as a quick, fun presentation at the Show & Tell at CSS Dev Conf this year. He graciously agreed to prepare it for a blog post!

19 ways to override a background color

Starting with pretty much the lowest specificity selector:

div {
  background: black;
}

There are an awful lot of ways to one-up the existing specificity without repeating the same trick.

Click through each step to see the spies force their color (black or white) on top of the previous color. When the specificity one-upping stops, there are still more tricks up their sleeve!

(The Spy shape is created from a clip-path on the div in the demo to make it more fun and Spy-vs-Spy like. If the browser you’re currently in doesn’t support clip-path, you’ll just see white or black boxes.)

See the Pen Spy Vs Spy by Francisco Dias (@FranDias) on CodePen.

What the heck is going on here? The text inside the demo Pen above has a bit more information on each step, but just as a quick overview, here are the stages:

Step 1: Element Selector

0, 0, 0, 1

div {
  background: black;
}

Step 2: Two Element Selectors

0, 0, 0, 2

body div {
  background: white;
}

Step 3: Three Element Selectors

0, 0, 0, 3

html body div {
  background: black;
}

Step 4: Class Selector

0, 0, 1, 0

.spy {
  background: white;
}

Step 5: Class + Element Selector

0, 0, 1, 1

div.spy {
  background: black;
}

Step 6: Element + Class + Pseudo-Class Selector

0, 0, 2, 1

div.spy:only-of-type {
  background: white;
}

Step 7: Stacked Classes

0, 0, ∞, 0

.spy.spy.spy.spy.spy.spy.spy.spy.spy {
  background: black;
}

Step 8: ID Selector

0, 1, 0, 0

#spy {
  background: white;
}

Step 9: ID + Attribute Selector

0, 1, 1, 0

#spy[class^="s"] {
  background: black;
}

Step 10: Combining Many Above…

0, 1, 3, 3

html body div#spy[class="spy"]:first-of-type.spy {
  background: white;
}

Step 11: Inline Style

1, 0, 0, 0

<div class="spy" id="spy" style="background: black;"></div>

Step 12: !important

Kind of like [1, 0, 0, 0] on a per-property basis (can override an inline style).

div {
  background: white !important;
}

Step 13: !important on inline style

Kind of like [∞, 0, 0, 0] on a per property basis, which no CSS can override.

<div class="spy" id="spy" style="background: black !important;"></div>

Step 14: box-shadow trickery

Some properties paint on top of others. box-shadow paints on top of background.

div {
  box-shadow: inset 0 9001rem 0 white;
}

Step 15: Invert Filter

div {
  -webkit-filter: invert(1);
          filter: invert(1);
}

Step 16: Pseudo Element Overlay

div::after {
  content: "";
  height: 9001px;
  width: 9001px;
  background: black;
  top: 0;
  left: 0;
  position: absolute;
}

Step 17: !important again

All the specificity battles could be fought again, including the per-property battle using !important;, so let’s end that here.

div:after {
  background: white !important;
}

Step 18: @keyframes trickery

!important isn’t animateable, so the animation can override it.

@keyframes white {
  to {
    background: black;
  }
}

div:after {
  animation: white 1s linear;
  animation-play-state: paused;
  animation-delay: -1s;
  animation-fill-mode: forwards;
}

Step 19: Coloring the Content

Putting a huge block character over everything flips the color again!

div:after {
  content: "█";
  line-height: 0;
  color: white;
  font-size: 9001px;
}

A gif of the demo in large-screen-Chrome: