🤯 50% Off! 700+ courses, assessments, and books

Relational and Attribute Selectors in CSS3

The following is an extract from our book, HTML5 & CSS3 for the Real World, 2nd Edition, written by Alexis Goldstein, Louis Lazaris, and Estelle Weyl. Copies are sold in stores worldwide, or you can buy it in ebook form here.

CSS3 Selectors

CSS selectors are at the heart of CSS. Without selectors to target elements on the page, the only way to modify the CSS properties of an element would be to use the element’s style attribute and declare the styles inline, which is awkward and unmaintainable. So we use selectors. Originally, CSS allowed the matching of elements by type, class, and/or ID. This required adding class and ID attributes to our markup to create hooks and differentiates between elements of the same type. CSS2.1 added pseudo-elements, pseudo-classes, and combinators. With CSS3, we can target almost any element on the page with a wide range of selectors.

In the descriptions that follow, we’ll be including the selectors provided to us in earlier versions of CSS. They are included because, while we can use CSS3 selectors, selectors that predate CSS3 are also part of the CSS Selectors Level 3 specification and are still supported, as CSS Selectors Level 3 expands on them. Even for those selectors that have been around for quite some time, it’s worth going over them here, as there are some hidden gems in the old spec that few developers know. Note that all modern browsers, including IE9 and above, support all CSS3 selectors.

Relational Selectors

Relational selectors target elements based on their relationship to another element within the markup. All of these are supported since IE7+, and in all other major browsers:

Descendant combinator (E F)

You should definitely be familiar with this one. The descendant selector targets any element F that is a descendant (child, grandchild, great grandchild, and so on) of an element E. For example, ol li targets li elements that are inside ordered lists. This would include li elements in a ul that’s nested in an ol, which might not be what you want.

Child combinator (E > F)

This selector matches any element F that is a direct child of element E—any further nested elements will be ignored.
Continuing the example, ol > li would only
target li elements directly inside the
ol, and would omit those nested inside a
ul.

Adjacent subling, or next sibling selector (E + F)

This will match any element F that shares
the same parent as E, and comes
directly after E in the markup. For example, li + li will target all li elements except the first li in a given container.

General subling, or following sibling selector (E ~ F)

This one’s a little trickier. It will match any element F that shares the same parent as any E and comes after it in the markup. So, h1 ~ h2 will match any h2 that follows an h1, as long as they both share the same direct parent—that is, as long as the h2 is not nested in any other element.

Let’s look at a quick example:

<article>
  <header>
    <h1>Main title</h1>
    <h2>This subtitle is matched </h2>
  </header>
  <p> blah, blah, blah …</p>
  <h2>This is not matched by h1 ~ h2, but is by header ~ h2</h2>
  <p> blah, blah, blah …</p>
</article>

The selector string h1 ~ h2 will match the first h2, because both the h1 and h2 are children, or direct descendants, of the header. The next h2 you’ll see in the code snippet doesn’t match, since its parent is article, not header. It would, however, match header ~ h2. Similarly, h2 ~ p only matches the last paragraph, since the first paragraph precedes the h2 with which it shares the parent article.

Note: Why is there no “parent” selector?

You’ll notice that up to this point there has been no “parent” or “ancestor” selector, and there’s also no “preceding sibling” selector. The performance of the browser having to go backwards up the DOM tree, or recurse into sets of nested elements before deciding whether or not to apply a style, prevented the ability to have native “up the DOM tree” selectors.

jQuery included :has() as an ancestral selector. This selector is being considered for CSS Selectors Level 4, but has yet to be implemented in any browser. If and when it is implemented, we will be able to use E:has(F) to find E that has F as a descendant, E:has(> F), to find E that has F as a direct child, E:has(+ F), to find E that directly precedes a sibling F, and similar.

Looking through the stylesheet for The HTML5 Herald, you’ll see a number of places where we’ve used these selectors. For example, when determining the overall layout of the site, we want the three-column divs to be floated left. To avoid this style being applied to any other divs nested inside them, we use the child selector:

main > div {
  float: left;
  overflow: hidden;
}

As we add new styles to the site over the course of the next few chapters, you’ll be seeing a lot of these selector types.

Attribute Selector

CSS2 introduced several attribute selectors. These allow for matching elements based on their attributes. CSS3 expands upon those attribute selectors, allowing for some targeting based on pattern matching. CSS Selectors Level 4 adds a few more:

E[attr]

Matches any element E that has the attribute attr regardless of the attribute’s value. We made use of this back in Chapter 4 to style required inputs; input:required works in the latest browsers, but input[required] has the same effect and works in IE7 and IE8 as well.

E[attr=val]

Matches any element E that has the attribute attr with the exact value val. While not new, it’s helpful in targeting form input types; for instance, targeting checkboxes with input[type=checkbox].

E[attr|=val]

Matches any element E whose attribute attr either has the value val or begins with val-. This is most commonly used for the lang attribute. For example, p[lang|="en"] would match any paragraph that has been defined as being in English whether it be UK or US English with &lt;p lang="en-uk"> or &lt;p lang="en-us">.

E[attr~=val]

Matches any element E whose attribute attr has within its value the full word val, surrounded by whitespace. For example, .info[title~=more] would match any element with the class info that had a title attribute containing the word “more,” such as “Click here for more
information.”

E[attr^=val]

Matches any element E whose attribute attr starts with the value val. In other words, the val matches the beginning of the attribute value.

E[attr$=val]

Matches any element E whose attribute attr ends in val. In other words, the val matches the end of the attribute value.

E[attr*=val]

Matches any element E whose attribute attr matches val anywhere within the attribute. It is similar to E[attr~=val], except the val can be part of a word. Using the same example as before, .fakelink[title*=info] {} would match any element with the class fakelink that has a title attribute containing the string info, such as “Click here for more information.”

In these attribute selectors, the value of val is case-sensitive for values that are case sensitive in HTML. For example, input[class^="btn"] is case sensitive as class names are case sensitive, but input[type="checkbox"] is not case sensitive, as the type value is case-insensitive in HTML.

The value does not have to be quoted if the value is alphanumeric, with some exceptions. Empty strings, strings that begin with a number, two hyphens, and other quirks need to be quoted. Because of the exceptions, it’s a good idea to make a habit of always including quotes for those times when you do need them.

In CSS Selectors Level 4, we can have case insensitivity by including an i before the closing bracket, E[attr*=val i].