Fundamentals of Web Application Development · DRAFTFreeman

Selectors & Rulesets

In CSS, styles are applied to elements through rules. In the previous code showing embedded styles, you saw an example of a ruleset which contains multiple rules grouped in a block enclosed by curly braces ({}). The code listing below shows various parts that make up a ruleset.

A ruleset starts with a selector, the mechanism by which certain elements are targeted for application of the given rules.

selector { /* <-- ruleset, enclosed in curly braces */

  property-1: value1; /* <-- single rule definition */
  property-2: value2;
  /* ... */
  property-n: valuen;
}

In the below example, we’re telling the browser to select all p elements and give them a font size of twice the document’s root font size, in red Arial font, with a 2px wide solid blue border around each.

p {
  font-size: 2rem;
  color: red;
  font-family: 'Arial';
  border: 2px solid blue;
}

Simple Selectors

In this section we will will go over some of the more commonly used selectors. Read through the below descriptions of selectors in example code snippets. Selector descriptions are presented as code comments inside of each selector’s respective block.

Universal

The universal selector, a.k.a. wildcard selector, is simply an asterisk (*). It selects all normal elements.

* {
  /* matches all elements */
}

Type (Element)

The type selector, also commonly referred to as an element selector, simply uses the tag name of an HTML element to target all elements of that type. For example,

p {
  /* matches all <p> (paragraph) elements */
}

table {
  /* matches all <table> elements */
}

Class

A class selector starts with a dot (.) immediately followed by a class name to target. Used alone, a class selector will target all elements which have that class, regardless of their element type.

.foo {
  /* matches all elements with class "foo" */
}

/* equivalent to */

*.foo {
  /* ... */
}

ID

An id selector starts with a hash mark (#) immediately followed by the id of the element to target.

#bar {
  /* matches the element with id="bar" */
}

Attribute

As discussed before, HTML elements can optionally have attributes, such as the href attribute on links:

<p>Here is a <a href="http://wikipedia.org">link to Wikipedia</a></p>

The attribute selector lets us target CSS rulesets to elements based on the existence or value of attributes on elements, optionally using some rudimentary substring matching. The attribute selector is contained within square brackets ([]), consisting of the name of the attribute optionally followed by a matching operator and value.

Take a look at the following attribute selectors and their descriptions, and try to figure out which ones would apply to the Wikipedia link in the example above.

a[href] { /* <a> elements with an href attribute, whatever its value */ }

a[href="wiki"] { /* ... href attribute exactly equal to "wiki" */ }

a[href^="wiki"] { /* ... starts with (or equal to) "wiki", e.g. "wiki123" */ }

a[href$="wiki"] { /* ... ends with (or equal to) "wiki", e.g. "123wiki" */ }

a[href*="wiki"] { /* ... contains substring "wiki", e.g. "12wiki34" */ }

a[href~="wiki"] { /* ... space-split attribute includes "wiki" exactly,
                         e.g. "some wiki string" */ }

/* add an `i` flag on the end of any of these
   to make the search case-insensitive */

a[href="wiki" i] { /* ... case-insensitive exact match, e.g. "WIki" */ }
show answer

Answer: Only the selectors on lines 1 and 9 apply.

How might this change if instead href="wikipedia.org"?

Be mindful, though, if working with boolean attributes:

<div someAttr="false"></div>

On this div element, the (fictional) attribute someAttr is not set to the boolean false, but to the string "false". As such, the following selector will match:

div[someAttr] {
  /* matches <div someAttr="false"> */
}

Combinators

Simple selector combinators offer a mechanism to combine multiple simple selectors into one more specific selector that can be used on a ruleset when we want to target elements more exactly than single selectors alone would allow. The type of combinator used defines the relationship between the simple selectors that it combines.

In all cases, though intermediate elements affect whether the entire selector applies, the rules are applied only on the elements ultimately matching the rightmost simple selector.

General Descendant

Combine selectors with whitespace in order to target general descendants – children, grandchildren, etc.

p a {
  /* all <a> elements inside of <p> elements */
}
<p>
  Text. <a>Targeted.</a> <span><a>Also targeted.</a></span>
</p>

Direct Child

Combine selectors with a single right angle bracket (>) to target only direct children (not including grandchildren, etc.).

p > a {
  /* all <a> which are children of <p> elements */
}

p>a {
  /* whitespace around the > sign is not significant, fyi */
}
<p>
  Text. <a>Targeted.</a> <span><a>Not targeted.</a></span>
</p>

General Sibling

Select an element if it follows a sibling that matches a given selector. (Two elements are siblings if they share the same parent element).

img ~ p {
  /* all <p> elements that occur after an <img> sibling */
}
<div>
  <p>Not targeted</p>
  <img />
  <div></div>
  <p>Targeted</p>
  <p>Targeted</p>
</div>

Adjacent Sibling

Select an element if it immediately follows a sibling matching a given selector.

img + p {
  /* all <p> elements that immediately follow an <img> element */
}
<div>
  <p>Not targeted</p>
  <div></div>
  <img />
  <p>Targeted</p>
  <p>Not targeted</p>
</div>

Pseudo-classes

A pseudo-class is an addendum that can be attached to a simple selector using a single colon followed by the name of the pseudo-class. In the examples below, we will use a substitute simple selector E to demonstrate the pseudo-class selectors. Note that there is not any whitespace between a simple selector and its pseudo-class(es).

Structural pseudo-classes

Structural pseudo-classes allow for matching elements based on their position in the document in ways that cannot otherwise be targeted using simple selectors and combinators alone.

:*-child

The :*-child pseudo-classes match elements based on their position and relationship to their sibling elements.

E:first-child {
  /* matches an element which is the first child of its parent */
}

E:last-child {
  /* ... the last child of its parent */
}

E:only-child {
  /* ... the only child of its parent */
}

There is a special :nth-child(An+B) functional-notation selector which takes a formula as a single argument. It will match elements based on their order in the document following a pattern established by this formula. It is best explained by the official spec:2

This syntax is referred to as the An+B notation, and represents an integer step (A) and offset (B). It indicates the An+Bth elements in a list, for every positive integer or zero value of n, with the first element in the list having index 1 (not 0).

For values of A and B greater than 0, this effectively divides the list into groups of A elements (the last group taking the remainder), and selecting the Bth element of each group.

The An+B notation also accepts the even and odd keywords, which have the same meaning as 2n and 2n+1, respectively.

Distinguishing between even and odd element positions is a common use case for this mechanism. A typical scenario where this is used is for making striped table rows – that is, for a given table, we want the background color of each row to alternate between light and dark, making it easier to read values aligned across many wide rows.

tr:nth-child(2n) {
  background-color: white;
}

tr:nth-child(2n + 1) {
  background-color: lightgrey;
}

There is also an :nth-last-child(An+B) pseudo-class which works similarly to the :nth-child() selector, except it counts elements in reverse: the last element is at index 1, the next-to-last element is at index 2, and so on.

:*-of-type

The :*-of-type pseudo-classes act much like the :*-child pseudo-classes, except instead of counting all siblings, it only considers siblings of the same element type (element tagname).

E:first-of-type {
  /* E elements which are the first of their type amongst their siblings */
}

E:last-of-type {
  /* ... last of their type amongst their siblings */
}

E:only-of-type {
  /* E elements that have no siblings of their same type */
}

E:nth-of-type(An + b) {
  /* see `An + B` notation above */
}

E:nth-last-of-type(An + b) {
  /* see `An + B` notation above */
}

:empty

The pseudo-class :empty matches an element that has no children and no content.

Dynamic pseudo-classes

Dynamic pseudo-classes are concerned with element states that might not necessarily be derivable from the document tree structure itself. These are relatively transient as they can change with user interaction, and may be unique to the type of element on which they apply. For example, <input> elements have an available pseudo-class of :disabled which will match when the input is disabled, and <a> elements have an available pseudo-class of :visited which will match when the user has already visited the linked resource.

a:visited {
  /* links that have been visited */
}

div:hover {
  /* <div> element when the mouse is over it */
}

input[type='checkbox']:checked {
  /* checked checkboxes */
}

#myButton:enabled:disabled {
  /* valid selector, but will never match! ...
     ... because :enabled and :disabled are mutually exclusive */
}

Negation pseudo-class

The negation pseudo-class :not() uses functional notation to accept a simple selector whose selection result will be negated.

a:not([href^="https://"]) {
  /* matches all links that do NOT explicitly use https */
}

div > *:not(p):not(ol) {
  /* matches all children of a <div> EXCEPT <p> and <ol> elements */
}

Pseudo-elements

Selector groups

Multiple selectors can be grouped onto one ruleset by separating them with commas (,) which effectively function as logical ors. For example, the selector

p.highlight,
div > .highlight {
  /* rules */
}

may be read as, “target all elements matching (p element with class highlight) or (any element with class highlight that is a direct child of a div element)“.

Another way of thinking about selector groups (and the way they are explained by the spec) is that “a comma-separated list of selectors represents the union of all elements selected by each of the individual selectors in the list.”3


Application of style rules

Style rules are applied to their targeted elements cumulatively – that is, a single element that matches the selectors to multiple rulesets will have the style rules from all of the matching rulesets applied to it. For example, with the following CSS,

div {
  color: green;
}

*.smallText {
  font-size: 10px;
}

and the following element in the document,

<div class="smallText">Lorem ipsum dolor sit amet…</div>

the element will have text that is both green and 10 px in size. Because it matches both selectors, the rules from both rulesets are applied.

In the next chapter, we will see how the browser handles conflicting rules that prescribe different values for the same style property.