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 div
s to be floated left. To avoid this style being applied to any other div
s 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 <p lang="en-uk">
or <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]
.