
CSS Selectors
To target elements in the HTML document with CSS, we use selectors. There are a lot of options available to help you select a wide range of elements or very specific elements in certain states.
Selectors are a powerful tool and we will look at them in some detail as the different options available can help with both web page performance and making your CSS more maintainable.
For example, you can use a selector to target the first letter of a heading, like you might expect to see in a medieval book:
h1::first-letter {
font-size: 5rem;
}
Or you could use a selector to invert the colors of every odd paragraph in an article:
p {
color: white;
background-color: black;
}
p:nth-of-type(odd) {
color: black;
background-color: white;
}
We will explore a variety of the options available to us when creating selectors.
Element, ID, and Class
Three commonly used selectors are:
- Element type: For example, to select all p elements in an HTML document, we use the p selector in a CSS ruleset. Other examples are h1, ul, and div.
- A class attribute: The class selector starts with a dot. For example, given the HTML snippet <h1 class="heading">Heading</h1>, you could target that element with the .heading selector. Other examples are .post and .sub-heading.
- An id attribute: The id selector starts with a hash symbol. For example, given the HTML snippet <div id="login"> <!-- login content --> </div>, you could target this element with the #login selector. Other examples include #page-footer and #site-logo.
The Universal Selector (*)
To select all elements throughout an HTML document, you can use the universal selector, which is the asterisk symbol (*). Here is an example snippet of CSS that is often added to web pages; a value is set on the html element and then inherited by all descendant elements:
html {
box-sizing: border-box;
}
*, *:before, *:after {
box-sizing: inherit;
}
Using the inherit keyword and the universal selector, we can pass a value on to all the descendants of the html element. This snippet will universally apply the border-box model to all elements and their pseudo-elements (that's the reason for :before and :after). You'll learn more about the box model and layout in the next chapter.
Attribute Selectors
Attribute selectors let you select elements based on the presence of an attribute or based on the value of an attribute. The syntax is square brackets, [], with the suitable attribute inside. There are several variations that you can use to make matches:
- [attribute] will select all elements with an attribute present; for example, [href] will select all elements with an href attribute.
- [attribute=value] will select all elements with an attribute with an exact value; for example, [lang="en"] will select all elements with a lang attribute set to en.
- [attribute^=value] will select all elements with an attribute with a value that begins with the matching value; for example, [href^="https://"] will select all elements with an href attribute beginning with https://, which links to a secure URL.
- [attribute$=value] will select elements with an attribute with a value that ends with the matching value; for example, [href$=".com"] will select all elements with an href attribute that ends with .com.
- [attribute*=valule] will select elements with an attribute with a value that has a match somewhere in the string; for example, [href*="co.uk"] will select all elements with an href attribute matching .co.uk. http://www.example.co.uk?test=true would be a match, as would https://www.example.co.uk.
Pseudo-classes
To select an element when it is in a particular state, we have several pseudo-classes defined. The syntax of a pseudo-class is a colon, :, followed by a keyword.
There are a great number of pseudo-classes, but most developers' first experience of them is when styling links. A link has several states associated with it:
- When an anchor element has an href attribute, it will have the :link pseudo-class applied to it.
- When a user hovers over the link, the :hover pseudo-class is applied to it.
- When the link has been visited, it has the :visited pseudo-class applied to it.
- When the link is being clicked, it has the :active pseudo-class applied to it.
Here is an example of applying styling to the various pseudo-class states of an anchor element:
a:link, a:visited {
color: deepskyblue;
text-decoration: none;
}
a:hover, a:active {
color: hotpink;
text-decoration: dashed underline;
}
In the following figure, we can see the first link with the :link or :visited styles applied and the second link with the :hover or :active styles applied:

Figure 1.24: Link with and without the hover state
The cascade can cause some issues with styling links. The order in which you specify your CSS rules for each state of the link is important. If, for example, we applied the a:hover rule before the a:link rule in the previous example, we would not see the hover effect. A mnemonic exists for remembering the order: love-hate. The l is for :link, the v is for :visited, the h is for :hover, and the a is for :active.
Some other useful pseudo-classes for selecting elements in a particular interactive state include :checked, :disabled, and :focus.
There are several pseudo-classes that help us select a pattern of children nested under an element. These include :first-child, :last-child, :nth-child, :nth-last-child, :first-of-type, :last-of-type, :nth-of-type, and :nth-last-of-type.
For example, we can use :nth-child with an unordered list to give a different style to list items based on their position in the list:
<style>
ul {
font-family: Arial, Helvetica, sans-serif;
margin: 0;
padding: 0;
}
li {
display: block;
padding: 16px;
}
li:nth-child(3n-1) {
background: skyblue;
color: white;
font-weight: bold;
}
li:nth-child(3n) {
background: deepskyblue;
color: white;
font-weight: bolder;
}
</style>
<!-- unordered list in HTML document -->
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
<li>Item 5</li>
<li>Item 6</li>
<li>Item 7</li>
</ul>
The following figure shows the result. The :nth-child pseudo-class gives you a lot of flexibility because you can use keywords such as odd and even or functional notation such as 3n - 1:

Figure 1.25: Using the :nth-child pseudo-class
Pseudo-elements
Pseudo-element selectors are preceded by two colons (::) and they are used to select part of an element. The available pseudo-elements include ::after, ::before, ::first-letter, ::first-line, ::selection, and ::backdrop.
These pseudo-elements give us a handle we can use to add stylistic elements without adding to the HTML document. This can be a good thing if the pseudo-element has no semantic value and is purely presentational, but it should be used with care.
Combining Selectors
What makes CSS selectors particularly powerful is that we can combine them in several ways to refine our selections. For example, we can select a subset of li elements in an HTML document that also has a .primary class selector with li.primary.
We also have several options, sometimes called combinators, for making selections based on the relationships of elements:
- To select all the li elements that are descendants of a ul element, we could use ul li.
- To select all the li elements that are direct children of a ul element with the primary class, we might use ul.primary > li. This would select only the direct children of ul.primary and not any li elements that are nested.
- To select an li element that is the next sibling of li elements with the selected class, we could use li.selected + li.
- To select all of the li elements that are the next siblings of li elements with the selected class, we could use li.selected ~ li.
The following figure shows the difference between using li.selected + li and li.selected ~ li:

Figure 1.26: Selecting the next adjacent sibling compared to selecting all of the next siblings
Let's try out some of the selectors we've learned about in an exercise.
Exercise 1.07: Selecting Elements
In this exercise, we will differentiate list items by styling the odd items. We will use a class selector to style a selected item and a next-siblings combinator to style the elements after the selected item.
The steps are as follows:
- Open the chapter_1 folder in VSCode (File > Open Folder…) and we will create a new plain text file by clicking File > New File. Then, save it in HTML format, by clicking File > Save As... and enter the File name: Selectors.html. Now, copy the following simple web page with a ul list element and nine list items:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Selectors</title>
</head>
<body>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
<li>Item 5</li>
<li>Item 6</li>
<li>Item 7</li>
<li>Item 8</li>
<li>Item 9</li>
</ul>
</body>
</html>
- So that we can style a selected item differently, we will add a selected class to the fifth list item:
<li class="selected">Item 5</li>
- Next, we will add a style element to the head element with the following CSS:
<head>
<meta charset="utf-8">
<title>Selectors</title>
<style>
ul {
font-family: Arial, Helvetica, sans-serif;
margin: 0;
padding: 0;
}
li {
display: block;
padding: 16px;
}
</style>
</head>
This will remove some of the default styling of the unordered list in the browser. It will remove margins and padding on the list and set the font style to Arial (with Helvetica and sans-serif as a fallback).
- Next, we will style the odd list items with the :nth-child pseudo-class. We can use the odd keyword for this. With this style, any odd list item will have a blue background and white text:
li:nth-child(odd) {
background-color: deepskyblue;
color: white;
font-weight: bold;
}
This gives us the stripy effect that we can see in the following figure:
Figure 1.27: Stripy list using :nth-child(odd)
- We can style the selected class selector:
li.selected {
background-color: hotpink;
}
This overrides the striped effect for the selected item as seen in the following figure:
Figure 1.28: Stripy list with a selected item
- Finally, we will style the odd list items after the selected item using the all-next-siblings combinator:
li.selected ~ li:nth-child(odd) {
background-color: orange;
}
If you now right-click on the filename in VSCode on the left-hand side of the screen and select open in default browser, you will see the following web page in your browser:

Figure 1.29: Combining selectors to style a list
Style sheets can have a large number of style rules and combinations of selectors. It is good to understand why one rule takes precedence over another one. This is where CSS specificity comes in.