Practical Dark Magic with Chained CSS Pseudoselectors

snape | chained css pseudoselectors

CSS pseudoselectors become very helpful when you can’t touch a site’s underlying PHP.

Today’s article comes from my experience working with small business WordPress clients. These clients often come to you with a huge mass of PHP code that you can’t change. For example, they’re trying to combine a social sharing plugin and a page builder theme, and it’s almost working but they can’t manage to fix one or two display details. “Throw everything out” is not an option, and neither is attempting to wrangle with either the theme’s or the plugin’s spaghetti code.

These types of projects make the purist in me shudder, but since good clients with real needs often find themselves with these types of setups, it’s certainly best to know how to help them. CSS pseudoselectors are an area of web development that becomes very helpful when you can’t touch the underlying PHP—so today, let’s learn a bit of CSS dark magic.

Intro to Pseudoselectors

CSS pseudoselectors significantly expand the power of CSS to intelligently select specific elements on a webpage, and to make types of changes that wouldn’t be possible otherwise.

Pseudo Classes and Pseudo Elements

I use “CSS pseudoselectors” as a general term for anything in CSS that looks like this: :selector. These “things” come in two flavors: pseudo classes and pseudo elements.

Pseudo Classes

Pseudo classes select elements on the page based on things that are true about those elements. Here are two very well-known examples:

a:visited {
  color: purple;
}

This will turn any previously visited link purple. The previously visited bit, :visited, is the pseudo class.

li:first-child {
  background-color: #aaa;
}

This will set a light gray background for any li element that is the first among its siblings—in other words, the :first-child of its parent (which should be an ol or ul element).

Pseudo Elements

Pseudo elements are a bit more complex, and quite powerful: They actually let you create content on a webpage using CSS, which isn’t otherwise possible. The most common example is :after:

a:after {
  content: " click me!"
  font-size: .8em;
}

This puts a small “click me!” text directly after every link on your site—which I don’t recommend. :after has a fraternal twin, :before, which functions similarly but places content before instead of after the selected element.

These pseudo elements have a lot of uses, from clearfix hacks to icon fonts, so they’re well worth understanding.

Read Up!

If this article is news to you so far, I recommend you read and go through the simple demos on W3 Schools. Even if you understand pseudoselectors, I also highly recommend checking out the full list of pseudo classes and pseudo elements, as there are quite a few I wasn’t aware of!

Fancier CSS Pseudoselectors: :not, :nth-child

CSS3 brought with it some very helpful newer pseudo classes, such as :not and :nth-child:

:not(p) {
  color: #000;
}

This turns the text black of anything that is not a paragraph (a p element).

li:nth-child(3) {
  display: none;
}

This hides and removes from the page’s layout flow any li that is the third child of its parent.

These newer CSS3 pseudo classes work on anything newer than IE8, the last truly horrible major browser of the modern era. Now that IE8 finally has less than 1% market share, we can start to use these with more confidence.

Again, if this is news so far, there’s some good reading to do. This Smashing Magazine article is a good start.

Chaining CSS Pseudoselectors

We can chain CSS pseudoselectors to make fairly intricate changes.

Here, we’re going to talk about chaining—combining—pseudoselectors to make fairly intricate changes. This is what lets us solve lots of problems that we’d normally need either JavaScript or access to a site’s PHP to address.

I’ve made up an example using WPShout’s nav menu. The example’s silly, but it mirrors a lot of actual client requests. Let’s say I thought the following changes to WPShout’s nav menu would bring a lot more traffic:

get_reading

Here’s the thing, though: In this pretend example, WPShout’s nav menu is generated by a badly written theme with a bunch of buried and impossible-to-talk-to template files—so editing the PHP is far more trouble than it’s worth. All we’ve got is the markup that the theme generates, and the ability to edit the site’s CSS.

What We’ve Got to Work With: Menu Markup

Here’s a simplified version of the markup we see on the front end (using Chrome’s Developer Tools or similar):

<ul>
	<li class="nav-item">
		<a href="https://wpshout.com/articles/">Articles</a>
	</li>
	<li class="nav-item">
		<a href="https://wpshout.com/links/">Links</a>
	</li>
	<li class="nav-item">
		<a href="https://wpshout.com/about/">About</a>
	</li>
	<li class="nav-item">
		<a href="https://wpshout.com/mailing-list/">Newsletter!</a>
	</li>
</ul>

Our CSS with Chained Pseudoselectors

Here’s the CSS in full:

.nav-menu .nav-item:first-child:after {
    content: " (get reading!)";
}

.nav-menu .nav-item:not(:first-child):not(:last-child):after {
    content: " (also check out!)";
}

.nav-menu .nav-item:after {
  color:#fff;
  font-size: .6em;
  padding-left: .2rem;
}

.nav-menu .nav-item:last-child:after {
  content: " (join!)"
}

.nav-menu .nav-item:nth-child(3) > a, .nav-menu .nav-item:nth-child(3):after {
	color: #aaa;
}

Commentary

The code above exists to demo a few ways of complexly combining CSS pseudoselectors to target specific elements, somewhat like you could with a true programming language such as JavaScript.

Chaining Pseudo Classes and Pseudo Elements

The first thing to notice is that you can chain pseudo classes like :first-child with pseudo elements like :before and :after, as in this line:

.nav-menu .nav-item:nth-child(3):after {
	color: #aaa;
}

This line targets the following elements:

  1. Elements that are inside an element with class nav-menu,
  2. That have class nav-item, and
  3. That are the first child of their parent.

The rule then uses a pseudo element to talk to any content that is :after each targeted element. The result is that the specified content gets a light gray (HTML hex #aaa) text color.

Complex Selection with CSS3 Pseudo Classes

Let’s take a look at this line:

.nav-menu .nav-item:not(:first-child):not(:last-child):after {
    content: " (also check out!)";
}

This selects the following elements:

  1. Elements that are inside an element with class nav-menu,
  2. That have class nav-item,
  3. That are not the first child of their parent, and
  4. That are not the last child of their parent.

The rule then puts content :after each element. The result is that the specified content, (also check out!), appears after the second and third of our four elements.

Downsides of this Approach

This approach is powerful, but it’s best used as a last resort, when a site’s PHP codebase is inaccessible or otherwise or not worth fixing. Remember, everything we did above could have been accomplished much more simply by adding plain old CSS classes where appropriate.

The specific downsides to relying heavily on CSS pseudoselectors include:

  1. It’s fragile. In the example above, just about every style we wrote would be ruined by the inclusion of a fifth menu element.
  2. It’s opaque. Adding lots of content from a theme’s stylesheet will confuse potential developers (including possibly you). CSS stylesheets are nobody’s first guess when a piece of mystery content shows up on the page.
  3. It’s not perfectly supported. Again, browsers are getting better, but you’re in a lot safer waters with simple old HTML classes talking to CSS style declarations than you are with chained CSS3 pseudoselectors.

So again, this is for projects where the PHP code is out of reach, and where something needs to work fairly quickly. This happens quite often in the real world, but it’s definitely a step removed from the mindset that typically produces graceful, future-proof code.

Now You Know Dark Magic!

Hopefully the power of chained pseudoselectors is starting to make sense: you can use only CSS to target very specific elements in a page of HTML markup, and do all kinds of things to them—even add specific content to the page.

I’ve also covered my bases with a warning that these tricks are second- or third-resort alternatives to being able to edit a page’s markup directly—but I’d be lying if I said this hasn’t been helpful for addressing multiple client needs.

Thanks for reading!

Image credit: Snape's True Love


1 Response

Pingbacks