Pseudo-Classes and Pseudo-Elements
These are powerful CSS selectors that target elements based on their state, position, or let you inject virtual elements into the page without touching the HTML.
1. Pseudo-Classes (:)
A pseudo-class selects an element in a specific state. They all use a single colon prefix.
User Interaction States
a:link { color: blue; } /* Unvisited link */
a:visited { color: purple; } /* Already visited */
a:hover { color: darkblue; } /* Mouse over */
a:active { color: red; } /* Being clicked */
input:focus { outline: 2px solid var(--color-primary); } /* Has keyboard focus */
input:focus-visible { /* Only show ring for keyboard, not mouse click */ }
Structural Pseudo-Classes
/* Child selectors */
li:first-child { font-weight: bold; } /* First child */
li:last-child { border: none; } /* Last child */
li:nth-child(2) { color: red; } /* 2nd child */
li:nth-child(odd) { background: #f5f5f5; } /* Zebra stripes */
li:nth-child(even) { background: white; }
li:nth-child(3n) { color: blue; } /* Every 3rd */
/* Of-type selectors (ignores siblings of other types) */
p:first-of-type { font-size: 1.2rem; }
h2:nth-of-type(2) { color: red; }
Negation and Matching
/* :not() — select everything EXCEPT */
.nav__item:not(.active) { opacity: 0.7; }
input:not([type="submit"]):not([type="checkbox"]) { border: 1px solid #ccc; }
/* :is() — simplify grouped selectors (takes highest specificity of argument) */
:is(h1, h2, h3) { line-height: 1.2; }
/* :where() — same as :is() but specificity is ZERO (great for resets) */
:where(h1, h2, h3, h4, h5, h6) { font-family: var(--font-heading); }
/* :has() — the parent selector (select parent based on child) */
.card:has(img) { padding-top: 0; } /* Card that contains an image */
form:has(input:invalid) .submit-btn { opacity: 0.5; } /* Dim button if form has errors */
Form State Pseudo-Classes
input:required { border-color: orange; }
input:optional { border-color: gray; }
input:valid { border-color: green; }
input:invalid { border-color: red; }
input:disabled { opacity: 0.5; cursor: not-allowed; }
input:checked + label { color: var(--color-primary); } /* Styled checkbox label */
input:placeholder-shown { font-style: italic; }
Visibility and Content
:empty { display: none; } /* Hides elements with no content */
:focus-within { border: 1px solid var(--color-primary); } /* Parent when any child focused */
2. Pseudo-Elements (::)
Pseudo-elements create virtual elements that can be styled, without adding HTML. They use double colon ::.
::before and ::after
The most powerful pseudo-elements. They insert a generated child at the start or end of an element's content. The content property is required (even if empty).
/* Decorative quote marks */
blockquote::before {
content: '"';
font-size: 4rem;
color: var(--color-primary);
line-height: 0;
vertical-align: -1.5rem;
}
/* Icon prefix */
.alert::before {
content: '⚠ ';
}
/* Clearfix hack (still used) */
.clearfix::after {
content: '';
display: table;
clear: both;
}
/* Decorative underline on headings */
h2::after {
content: '';
display: block;
width: 60px;
height: 3px;
background: var(--color-primary);
margin-top: 0.5rem;
}
::before / ::after as Layout Elements
Because they're styled like real elements, you can use flexbox/positioning on them:
/* Glow orb decoration */
.hero::before {
content: '';
position: absolute;
width: 500px;
height: 500px;
background: radial-gradient(circle, rgba(108,99,255,0.2), transparent 70%);
border-radius: 50%;
pointer-events: none;
z-index: 0;
}
Other Pseudo-Elements
::first-letter { font-size: 3rem; float: left; line-height: 1; margin-right: 0.1em; } /* Drop cap */
::first-line { font-variant: small-caps; } /* Style first line of text */
::selection { background: var(--color-primary); color: white; } /* Highlighted text color */
::placeholder { color: var(--color-muted); font-style: italic; } /* Input placeholder */
::marker { color: var(--color-primary); font-size: 1.2em; } /* List item bullets/numbers */
3. Practical Combined Examples
Styled Navigation Active State
.nav__link { position: relative; }
.nav__link::after {
content: '';
position: absolute;
bottom: -4px;
left: 0; right: 0; /* or: left: 50%; right: 50%; + transition to expand from center */
height: 2px;
background: var(--color-primary);
transform: scaleX(0);
transform-origin: center;
transition: transform 0.3s ease;
}
.nav__link:hover::after,
.nav__link.active::after {
transform: scaleX(1);
}
Tooltip with ::before
[data-tooltip] { position: relative; cursor: pointer; }
[data-tooltip]::before {
content: attr(data-tooltip); /* reads from HTML attribute */
position: absolute;
bottom: calc(100% + 8px);
left: 50%;
transform: translateX(-50%);
background: #333;
color: white;
padding: 4px 10px;
border-radius: 4px;
font-size: 0.75rem;
white-space: nowrap;
opacity: 0;
pointer-events: none;
transition: opacity 0.2s;
}
[data-tooltip]:hover::before { opacity: 1; }