CSS pseudo-classes and pseudo-elements both let you style things that a plain selector can't reach on its own — a pseudo-class targets an element in a particular state (like being hovered or being the first child), while a pseudo-element lets you style a specific part of an element, or even generate new content that doesn't exist in your HTML at all.
They're easy to tell apart once you know the rule: pseudo-classes use a single colon (:hover), pseudo-elements use a double colon (::before). Browsers still accept a single colon for the original pseudo-elements for legacy reasons, but the double-colon form is the modern standard and makes the distinction clear at a glance.
Common Pseudo-classes
Pseudo-classes respond to state or position, without you needing to add a class in JavaScript to track it.
a:hover {
color: #2563eb; /* while the mouse is over the link */
}
input:focus {
outline: 2px solid #2563eb; /* while the input has keyboard focus */
}
li:first-child {
font-weight: bold; /* only the first <li> in its parent */
}
li:nth-child(2n) {
background: #f9fafb; /* every even list item — classic zebra striping */
}
This styles links on hover, gives inputs a clear focus ring for accessibility, bolds the first item in any list, and alternates row background colors without touching the HTML or adding a single class.
Common Pseudo-elements
Pseudo-elements target a sub-part of an element. ::before and ::after are the two you'll use constantly — they insert generated content immediately inside an element, before or after its real content, controlled entirely by CSS.
::selection {
background: #fde68a; /* the highlighted color when a user selects text */
}
p::first-line {
font-weight: bold; /* styles only the first rendered line of a paragraph */
}
A Practical ::before Example
A very common real use of ::before is adding a decorative marker or icon in front of an element without cluttering the HTML with an extra <span>.
<ul class="checklist">
<li>Write the tutorial</li>
<li>Review the code examples</li>
</ul>
.checklist li {
list-style: none;
position: relative;
padding-left: 28px;
}
.checklist li::before {
content: "\2713"; /* a checkmark character */
position: absolute;
left: 0;
color: #16a34a;
font-weight: bold;
}
This renders each list item with a green checkmark in front of it — generated purely by CSS, with no extra markup. The content property is mandatory for ::before/::after to render at all; without it, the pseudo-element is created but stays invisible. This pairs naturally with absolute positioning, since the checkmark needs to sit precisely within the padding space you've carved out on the list item.
A good rule of thumb: reach for a pseudo-class when you need to react to an element's state or position among its siblings, and reach for a pseudo-element when you need to style a fragment of an element's content or inject purely decorative content that has no business living in your HTML.
