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.
[…] CSS Pseudo-Classes and Pseudo-Elements […]