Styling Parent Elements in CSS

9.5.2010

I love CSS. Really, I do. However, no matter how much time CSS saves, there are some tasks that are very hard to do. Consider vertical centering. Consider equal height columns. In this article, we consider styling parents.

Update September 2019: This article, written nearly a decade ago, uses pseudo-elements in order to make it appear as though parent elements were styled. CSS level 4 previously contained a ! selector to determine a subject. This would have allowed styling of parents in CSS, without hacks. Unfortunately, that selector was dropped prior to implementation. If you know why this wasn't left in the spec, please contact me. I'd be interested to know.

The Problem: You have a website that involves nested lists. Perhaps they're static, perhaps they're dropdowns. You want to offer a visual cue to which list items have sublists.

The Solution: JavaScript.

The Other Solution: I was curious as to if there was a tour-de-force pure-CSS solution. In fact, with a combination of some selectors, a hack is possible.

A word on hacks: like all hacks, this one might leave you with a feeling of "Gosh, this is neat and clever, but I really wish I didn't have to use it." In some applications, it's more graceful than others. There are probably many cases when the JavaScript would better serve the purpose.

Style Adopted Children

Example:

Bob
Chloe
Her Roomate

If you're using a modern browser (not IE6, IE7, IE8), you should see four items in a top level list: Bob, Mary, Chloe, and Jane. Chloe happens to have a sublist of one item: Her Roomate. Visually, Chloe should be distinct from the others, since she has a sublist. In fact, the background should be shaded light blue and the text becomes dark blue.

Normally this result could be achieved by adding a class="contains-sublist" or some similar class to identify all the LI's with sublists.

But, to keep the markup clean, I want to style the following HTML with no JavaScript, ID's, or classes.

The HTML

<ul>
  <li>Bob</li>
  <li>Mary</li>
  <li>Chloe
    <ul>
      <li>Her Roomate</li>
    </ul>
  </li>
  <li>Jane</li>
</ul>

The CSS

To begin the CSS, the <UL> needs to be styled like a horizontal menu. I've left links out of the <LI>s in order to keep the markup clean. So I'll give each list item a uniform width and height, a bit of a border, and float them all left. In order to ensure no .first is needed, I'm using the :first-child selector.

li {  
  width: 150px;
  min-height: 40px;
  line-height: 40px;
  text-indent: 10px; 
  float: left;
  border: 1px solid #444;
  border-left-width: 0;
}
  
li:first-child {
  border-left-width: 1px;
}

li ul {
  margin: 0 0 0 -1px;
}

So what is an adopted child? This is what I like to call the stylable elements created by using the ::before and ::after selectors.

Both ::before and ::after incur some criticism since they're seen as adding content to documents via CSS. Since HTML is for content and CSS is for style, some believe this creates the same troubles as using tables for styling. This is a sane argument in some cases. However, most often, content: is used to inject stylistic content rather than real content. For example, ::before can be used to add pipes (|) after links in a horizontal menu or adding bullets in an address or adding quotations around a BLOCKQUOTE tag.

Here we're going to create a bit of content and then style it.

In particular, we want to find all submenus (li ul) and add some content before it (li ul::before). Optimally, the content added would be a tag or a blank space, however, the content is rendered literally, and therefore is not parsed as HTML. Therefore, we must add text.

In this example, the content is "Submenu Follows." I don't have a screen reader handy, so I don't know if this effects accessibility by being read out (which might be a good thing). However, it isn't in the rendered HTML, nor accessible via standard element inspectors.

li ul::before {
  content:"Submenu Follows";
}

Now comes the neaky part, we're going to give this content a width and height of 100%. That will make it match the width and height of its parents LI. You can use all the standard CSS properties on this element created by ::before.

Now we need to move the element up. A negative margin suffices nicely. Give it a pleasant background color, a display of block, and a very large negative text indent.

You'll notice that the background color will be overlaying the parent element. In order to solve this, simply appy an opacity value. This will turn the background nicely semi-transparent, and even leaches the color lightly through the text, which is a nice effect. You could probably move the list item text on top of the background via clever z-indices, but I opted not to explore that.

Here's the final ::before CSS. View this document's source for a working copy of the code.

li ul::before {
  background: #009;
  opacity: .3;
  width: 100%;
  height: 100%;
  margin: -40px 0 0;
  text-indent: -9999px;
  display: block;
  content: "Submenu Follows";
}