CSS Pseudo-Classes and Pseudo-Elements: The Complete Styling Guide

CSS Pseudo-Classes and Pseudo-Elements: The Complete Styling Guide

CSS pseudo-classes and pseudo-elements are some of the most underused CSS features — letting you style elements based on state, position, and relationships without JavaScript or extra HTML. :has() (the parent selector) finally arrived in modern browsers, and :is()/:where() slash specificity headaches.

TL;DR: Pseudo-class: :hover, :focus, :active, :nth-child, :not, :has, :is, :where. Pseudo-element: ::before, ::after, ::placeholder, ::first-line. :has() selects parents. :not() excludes. :nth-child(even/odd/3n+1) for patterns. ::before/::after add content without HTML.

State pseudo-classes

/* :hover :focus :active :visited */
button:hover  { background: #1d4ed8; }
button:active { transform: scale(0.98); }
input:focus   { outline: 2px solid #2563eb; outline-offset: 2px; }
a:visited     { color: purple; }

/* :focus-visible — keyboard focus only (not mouse click) */
button:focus-visible { outline: 2px solid #2563eb; } /* Accessibility! */
button:focus:not(:focus-visible) { outline: none; }  /* Remove for mouse */

/* :disabled :enabled :checked :required :optional */
input:disabled { opacity: 0.5; cursor: not-allowed; }
input:invalid  { border-color: #ef4444; }
input:valid    { border-color: #10b981; }

:nth-child and structural selectors

/* :first-child :last-child :only-child :nth-child */
li:first-child { font-weight: bold; }
li:last-child  { border-bottom: none; }
li:nth-child(even) { background: #f8fafc; }
li:nth-child(3n+1) { /* Every 3rd, starting at 1: 1,4,7,10... */ }

/* :nth-of-type: count only matching type */
p:nth-of-type(2) { /* Second 

, ignoring other elements */ } /* :not() — exclude specific elements */ button:not([disabled]) { cursor: pointer; } li:not(:last-child) { border-bottom: 1px solid #e2e8f0; }

:has() — the parent selector

/* Select parent based on child — CSS's biggest missing feature, now here! */

/* Form label if required input inside */
label:has(input:required) { font-weight: bold; }
label:has(input:required)::after { content: ' *'; color: red; }

/* Card with image gets different layout */
.card:has(img) { grid-template-rows: auto 1fr; }
.card:not(:has(img)) { padding: 32px; }

/* Sibling selection with :has + ~ */
/* When checkbox is checked, style following sibling */
:has(input[type=checkbox]:checked) ~ .content { display: block; }

::before and ::after pseudo-elements

/* Add decorative content without extra HTML */

/* Tooltip on hover */
[data-tooltip]:hover::before {
  content: attr(data-tooltip); /* Reads HTML attribute */
  position: absolute;
  background: #1e293b;
  color: white;
  padding: 4px 8px;
  border-radius: 4px;
  white-space: nowrap;
}

/* Counter badges */
.badge::after {
  content: counter(badge-count);
  background: #ef4444;
  border-radius: 50%;
}

/* Decorative dividers */
.section-title::before, .section-title::after {
  content: '';
  flex: 1;
  height: 1px;
  background: #e2e8f0;
}
  • ✅ :focus-visible for keyboard-only focus styles (accessibility)
  • ✅ :has() for parent selection — no more JS for this pattern
  • ✅ :is()/:where() to reduce specificity in complex selectors
  • ✅ ::before/::after for decorative content without HTML
  • ❌ :has() not supported in Firefox before 121 — check caniuse
  • ❌ Excessive use of ::before/after for functional content — use real HTML

External reference: MDN CSS Pseudo-classes.

Recommended Reading

Designing Data-Intensive Applications — The bible of distributed systems and production engineering at scale.

The Pragmatic Programmer — Timeless engineering wisdom every senior developer needs.

Affiliate links. We earn a small commission at no extra cost to you.

Free Weekly Newsletter

🚀 Join 2,000+ Senior Developers

Get expert-level JavaScript, Python, AWS, system design and AI secrets every week. Zero fluff, pure signal.

✓ No spam✓ Unsubscribe anytime✓ Expert-level only

Discover more from CheatCoders

Subscribe to get the latest posts sent to your email.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply