HTML and CSS course Β· Module 3: CSS Basics
CSS Pseudo-classes and Pseudo-elements
In this lesson4
You want a link to turn gold on hover, every other table row to get a different background, and a hieroglyph before each heading - without extra classes or decorations in the HTML. For this, CSS has pseudo-classes and pseudo-elements.
Mohamed points at the temple paintings: "See how the colors change depending on the light? In CSS, we have pseudo-classes and pseudo-elements that allow changing the appearance of elements depending on their state or adding decorations without changing the HTML!"
Pseudo-classes - Reacting to State
Pseudo-classes allow styling elements depending on their state or position in the document. We write them with a single colon :.
Interactive States
The first group reacts to what the user does: hovering, clicking, or tabbing into a form field:
/* Visited link */
a:visited {
color: #8B7355;
}
/* When cursor is over element */
a:hover {
color: gold;
text-decoration: underline;
}
/* When element is active (clicked) */
button:active {
background-color: darkgoldenrod;
transform: scale(0.95);
}
/* When element has focus (e.g., after clicking Tab) */
input:focus {
border-color: gold;
outline: 2px solid rgba(255, 215, 0, 0.5);
}The order matters: a:visited comes before a:hover. Both rules are equally strong, so the later one wins - if :visited came lower, a visited link wouldn't turn gold on hover. MDN recommends the order :link, :visited, :hover, :active. Also, never remove the :focus outline without another clear style - keyboard users rely on it.
Structural Pseudo-classes
The second group selects elements by their position in the document tree - first, last, or every other one:
/* First child element */
li:first-child {
font-weight: bold;
color: gold;
}
/* Last child element */
li:last-child {
border-bottom: 2px solid gold;
}
/* Every other element (even) */
tr:nth-child(even) {
background-color: #f5f0e6;
}
/* Every other element (odd) */
tr:nth-child(odd) {
background-color: #fff;
}
/* Every third element */
li:nth-child(3n) {
color: crimson;
}
/* Third element from end */
li:nth-last-child(3) {
font-style: italic;
}Elements are counted from 1, not from 0, so nth-child(3n) selects the third, sixth and ninth element, while even and odd mean even and odd. From the earliest, the order is: :first-child, :nth-child(2), :nth-child(3), and finally :last-child.
Modern Pseudo-classes
The third group holds newer tools that shorten long selectors and select elements by what they contain:
/* Negation - all links EXCEPT .special class */
a:not(.special) {
color: #8B6914;
}
/* Grouping selectors - :is() */
:is(h1, h2, h3):hover {
color: gold;
}
/* :has() - parent selector (newest!) */
/* Style article THAT CONTAINS an image */
article:has(img) {
border: 2px solid gold;
padding: 20px;
}
/* Div that contains empty paragraph */
div:has(p:empty) {
display: none;
}The :not() pseudo-class reverses a condition, :is() gathers several selectors into one, and :has() selects a parent based on its content - article:has(img) styles the article, not the image. All major browsers have supported :has() since the end of 2023.
Pseudo-elements - Decorations Without HTML
Pseudo-elements allow styling parts of an element or adding decorative content without changing HTML. We write them with double colons ::. Browsers also understand the old single-colon notation, e.g. :before, but I recommend always writing :: to tell them apart from pseudo-classes.
::before and ::after
They add content before or after the element's content, inside the element. They require the content property: without it, the pseudo-element isn't created at all, even with a color and dimensions.
/* Add hieroglyph before heading */
h2::before {
content: "π ";
color: gold;
}
/* Add arrow after link */
a::after {
content: " β";
color: gold;
}
/* Decorative line under heading */
h1::after {
content: "";
display: block;
width: 100px;
height: 3px;
background: linear-gradient(to right, gold, transparent);
margin-top: 10px;
}In the third example, content: "" is an empty string, but it's enough to create the pseudo-element - dimensions and the gradient do the rest. A full rule is a selector, a pseudo-element and a block with content, e.g. h1::after { content: "β"; }. This content isn't in the HTML, so don't put anything important there.
::placeholder and ::selection
These two pseudo-elements style the hint in an empty form field and text selected with the mouse - fragments you can't point to with any tag.
/* Placeholder style in input */
input::placeholder {
color: #c4a35a;
font-style: italic;
}
/* Selected text style */
p::selection {
background-color: gold;
color: #2c1810;
}The placeholder disappears when the user starts typing, so it doesn't replace the field's label.
::first-line and ::first-letter
The last pair works on a paragraph's text - its first line or first letter, like a decorative initial in a scribe's manuscript.
/* First letter of paragraph - initial effect */
p::first-letter {
font-size: 3em;
float: left;
color: gold;
font-family: serif;
line-height: 1;
margin-right: 5px;
}
/* First line of text */
p::first-line {
font-weight: bold;
color: #8B6914;
}The float: left declaration and a large font-size create an initial that is several lines of text tall.
Practical Applications
Now let's combine both families in real components.
Interactive List with Hover Effects
The first component is a list of pharaohs whose rows get a background and a golden left border on hover:
.pharaoh-list li {
padding: 10px;
transition: all 0.3s ease;
border-left: 3px solid transparent;
}
.pharaoh-list li:hover {
background-color: #f5f0e6;
border-left-color: gold;
padding-left: 20px;
}
.pharaoh-list li:nth-child(even) {
background-color: #faf8f3;
}
.pharaoh-list li::before {
content: "Β» ";
color: gold;
}The transition declaration makes the change smooth - you'll learn transitions in the module on animations. The ::before rule has an empty content for now, so it shows nothing - in the exercise you'll add an arrow.
Buttons with Pseudo-elements
The second component is a button across which a band of light slides on hover, like a ray of sunlight across a golden mask:
.btn-egyptian {
position: relative;
padding: 12px 24px;
background: #8B6914;
color: white;
border: none;
overflow: hidden;
}
.btn-egyptian::before {
content: "";
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(
90deg, transparent, rgba(255,215,0,0.3), transparent
);
transition: left 0.5s;
}
.btn-egyptian:hover::before {
left: 100%;
}The ::before pseudo-element is a gradient band hidden outside the button (left: -100%), which moves to the other side on hover. Note the :hover::before notation - first the pseudo-class, then the pseudo-element.
Tooltips with CSS
The third component is a tooltip whose text the attr() function takes from the data-tooltip attribute in the HTML:
.tooltip {
position: relative;
}
.tooltip::after {
content: attr(data-tooltip);
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
background: #2c1810;
color: gold;
padding: 5px 10px;
border-radius: 4px;
font-size: 14px;
white-space: nowrap;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s;
}
.tooltip:hover::after {
opacity: 1;
}The tooltip is invisible (opacity: 0) and fades in on hover, while pointer-events: none keeps it from catching clicks.
Debugging in DevTools
When a style doesn't work, don't guess. Right-click the element, choose Inspect, find the rules that apply to it in the Styles panel, compare selector specificity and fix the value in the inspector. The :hov button forces the :hover state without moving the mouse. Then copy the working fix into the CSS file, because inspector changes vanish on refresh.
Mohamed summarizes: "Pseudo-classes react to element state, pseudo-elements add decorations. Together they create dynamic interfaces without additional HTML!"
In the editor below, all these techniques work together - hover over the pharaohs' names and click the form fields:
Code for this lesson: index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Pseudo-classes and pseudo-elements</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Pharaohs of Egypt</h1>
<ul class="pharaoh-list">
<li data-tooltip="2589-2566 BCE">Cheops (Chufu)</li>
<li data-tooltip="1332-1323 BCE">Tutankhamun</li>
<li data-tooltip="1279-1213 BCE">Ramesses II</li>
<li data-tooltip="51-30 BCE">Cleopatra VII</li>
<li data-tooltip="1479-1458 BCE">Hatshepsut</li>
</ul>
<h2>Contact the museum</h2>
<form>
<label for="name">Your name</label>
<input type="text" id="name" placeholder="Your name...">
<label for="email">Your email</label>
<input type="email" id="email" placeholder="Your email...">
<button class="btn-egyptian">Send message</button>
</form>
<article>
<p>Ancient Egypt is one of the oldest civilizations in the world. For thousands of years, Egyptians built monumental structures, developed a writing system, and created an advanced culture that fascinates us to this day.</p>
</article>
</body>
</html>Remember: a pseudo-class with one colon reacts to an element's state, and a pseudo-element with two colons adds a decoration to it - like the light changing the colors of the paintings and a scribe's ornate initial.
Docs entries used in this lesson
Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. Which pseudo-class lets you style an element when the mouse cursor is over it?
2. What is required for a ::before or ::after pseudo-element to be visible?
These are 2 of 3 questions for this lesson. Solve the rest in the game.
Hands-on tasks in the game
- Code editor
Uncomment and complete the CSS rules: hover, first-child, nth-child(even), ::before, :focus, ::placeholder, button:hover
- Horizontal ordering
Arrange the elements to create a correct CSS rule with the ::after pseudo-element:
- Click in order
Arrange the :nth-child selectors from selecting the earliest to the latest elements:
- Code editor
Uncomment and complete the CSS rules with pseudo-elements: ::before on h1 and h2, ::after as a decorative line, ::first-letter as a drop cap, ::selection
- Vertical ordering
Arrange the steps for debugging a CSS styling issue:
- Vertical ordering
Arrange the steps for resolving a CSS style conflict (when an element looks unexpected):
- Code editor
Build a web page about Egyptian gods. Use CSS for styling: colors, fonts, box model, display modes (block, inline, inline-block), and descendant selectors.