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.