CSS Pseudo-Classes and Pseudo-Elements

Style what plain selectors cannot. Learn CSS pseudo-classes for state and position, hover, focus, nth-child versus nth-of-type, not, and pseudo-elements like before, after, first-letter, and selection.

Some of the most useful styling in CSS targets things you cannot reach with an ordinary selector: the moment a button is hovered, the first letter of a paragraph, the second item in a list, the text a user has selected. Two features handle these cases. Pseudo-classes style an element based on its state or position, written with a single colon, and pseudo-elements style a specific part of an element or inject new content, written with a double colon. The two are easy to confuse, so a good way to keep them straight is the colon count and the question each answers: a pseudo-class asks “what state is this element in?” while a pseudo-element asks “which part of this element do I want?” This article covers both, the gotcha that catches everyone with child selectors, and when to reach for each.

Pseudo-classes for state

The most common pseudo-classes respond to user interaction. :hover applies while the pointer is over an element, which is the bread and butter of interactive styling.

button:hover {
background: red;
color: white;
}

:focus applies when an element gains focus, by being clicked or tabbed into, which matters most for form fields and is important for keyboard accessibility.

input:focus {
border: 2px solid blue;
outline: none;
}

:active applies in the instant an element is being clicked, useful for a pressed-button effect, and :checked targets a checkbox, radio button, or toggle when it is selected, letting you style the chosen option.

input:checked {
outline: 2px solid green;
}

There is also :disabled for inputs that cannot be interacted with, commonly dimmed to signal they are inactive. Together these state pseudo-classes are how a static stylesheet becomes responsive to what the user is actually doing.

Structural pseudo-classes, and the one gotcha

A second group selects elements by their position among their siblings. :first-child and :last-child target the first and last child of a parent, handy for trimming the top or bottom of a list or section.

p:first-child {
font-weight: bold;
}
p:last-child {
font-style: italic;
}

:nth-child(n) is the flexible one, selecting an element by its position, so li:nth-child(2) is the second list item, and it also accepts patterns like 2n for every even child.

li:nth-child(2) {
color: red;
}

Here is the gotcha that trips up almost everyone, the difference between :nth-child and :nth-of-type. The first counts every child of the parent regardless of element type, while the second counts only elements of the matching type. So p:nth-child(2)means “this element, if it is the second child overall and that child is a paragraph,” whereas p:nth-of-type(2) means “the second paragraph, no matter what other elements sit between them.”

p:nth-of-type(2) {
color: green;
}

If you have a heading followed by paragraphs, p:nth-child(2) and p:nth-of-type(2) will select different elements, and not realising this is the cause of countless “why is the wrong thing styled?” moments. When you mean “the nth paragraph,” reach for :nth-of-type.

Excluding with :not

The :not() pseudo-class selects everything except what matches the selector inside it, which is a clean way to apply a style broadly while carving out exceptions.

p:not(.special) {
color: gray;
}

This greys out every paragraph except those with the special class, saving you from writing a rule for everything and then overriding it.

Pseudo-elements for parts and inserted content

Pseudo-elements, with their double colon, style a piece of an element or generate new content. The two you will use most are ::before and ::after, which insert content immediately before or after an element’s own content. The crucial requirement is the content property: without it, even set to an empty string, ::before and ::after produce nothing at all.

h1::before {
content: "Chapter ";
}
h1::after {
content: " (draft)";
}

This prepends and appends text to every h1 purely through CSS, with no change to the HTML, which is perfect for decorative markers, labels, and icons. Other pseudo-elements style typographic parts of text. ::first-letter styles just the opening letter, the classic drop-cap effect, and ::first-line styles the first line of a block however it happens to wrap.

p::first-letter {
font-size: 3em;
color: red;
}

And ::selection styles the text a user has highlighted, letting you brand even that small detail.

::selection {
background: yellow;
}

A practical example

These combine naturally on a single element. A button might transition its background on hover and carry generated content on each side, all from CSS.

button {
background: blue;
color: white;
padding: 10px 20px;
border: none;
transition: background 0.3s ease;
}
button:hover {
background: red;
}
button::before {
content: "Buy ";
}
button::after {
content: " now";
}

The pseudo-class handles the interactive colour change, and the two pseudo-elements wrap the button’s label with extra text, demonstrating both features cooperating on one element.

When to use which

The split is clean once you hold onto the state-versus-part distinction. Reach for pseudo-classes, with their single colon, when you are responding to interaction such as :hover:focus, and :checked, selecting by position such as :first-child and :nth-child, or excluding with :not. Reach for pseudo-elements, with their double colon, when you are inserting content with ::before and ::after, styling text parts with ::first-letter and ::first-line, or customising the highlight with ::selection.

Conclusion

Pseudo-classes and pseudo-elements let you style what plain selectors cannot. Pseudo-classes target an element’s state or position with a single colon, covering interaction, structural selection, and exclusion, with the standing trap that :nth-childcounts all children while :nth-of-type counts only the matching type. Pseudo-elements target a part of an element or generate content with a double colon, where ::before and ::after always need a content value to appear. Keep the colon counts and the state-versus-part question in mind, and these selectors give you precise control over interaction, structure, and detail without touching your HTML.

See you soon.

View Comments (1)

Leave a Reply

Prev Next

Subscribe to My Newsletter

Subscribe to my email newsletter to get the latest posts delivered right to your email. Pure inspiration, zero spam.

Discover more from Discuss Data Science, Machine Learning and Analytics

Subscribe now to keep reading and get access to the full archive.

Continue reading