CSS Pseudo-classes & Pseudo-elements
Pseudo-classes target elements in specific states. Pseudo-elements target specific parts of elements.
Pseudo-classes
Link States
/* Unvisited link */
a:link {
color: blue;
}
/* Visited link */
a:visited {
color: purple;
}
/* Hover state */
a:hover {
color: red;
}
/* Active (being clicked) */
a:active {
color: orange;
}
/* Focus (keyboard navigation) */
a:focus {
outline: 2px solid blue;
}
Order: L-V-H-A (Link, Visited, Hover, Active)
Input States
/* Focus state */
input:focus {
border-color: #3498db;
outline: none;
}
/* Disabled */
input:disabled {
background-color: #eee;
cursor: not-allowed;
}
/* Enabled */
input:enabled {
background-color: white;
}
/* Required */
input:required {
border-left: 3px solid red;
}
/* Optional */
input:optional {
border-left: 3px solid gray;
}
/* Valid */
input:valid {
border-color: green;
}
/* Invalid */
input:invalid {
border-color: red;
}
/* Checked */
input:checked {
accent-color: blue;
}
/* Placeholder shown */
input:placeholder-shown {
color: gray;
}
Structural Pseudo-classes
/* First child */
li:first-child {
font-weight: bold;
}
/* Last child */
li:last-child {
border-bottom: none;
}
/* nth-child (position) */
li:nth-child(2) {
color: red;
}
/* nth-child (formula) */
li:nth-child(odd) {
background-color: #f5f5f5;
}
li:nth-child(3n) {
font-size: 1.2em;
}
li:nth-child(3n+1) {
color: blue;
}
/* First of type */
p:first-of-type {
font-size: 1.5em;
}
/* Last of type */
p:last-of-type {
margin-bottom: 0;
}
/* Only child */
.item:only-child {
margin: 0 auto;
}
/* Only of type */
p:only-of-type {
font-style: italic;
}
/* Empty element */
div:empty {
display: none;
}
/* Root element */
:root {
font-size: 16px;
}
/* Not selector */
p:not(.special) {
color: gray;
}
/* Is selector (matches any) */
:is(h1, h2, h3) {
line-height: 1.2;
}
/* Where selector (same as is, lower specificity) */
:where(h1, h2, h3) {
margin-bottom: 0.5rem;
}
/* Has selector (contains) */
div:has(p) {
padding: 1rem;
}
Child Pseudo-classes
/* Any child */
li:nth-child(n) {
/* All items */
}
/* First 3 items */
li:nth-child(-n+3) {
font-weight: bold;
}
/* After first 3 items */
li:nth-child(n+4) {
color: gray;
}
/* Last 3 items */
li:nth-last-child(-n+3) {
border-bottom: none;
}
/* Every other item */
li:nth-child(even) {
background-color: #f5f5f5;
}
/* Every third item */
li:nth-child(3n) {
margin-left: 20px;
}
Pseudo-elements
Content Generation
/* Before content */
h1::before {
content: "★ ";
color: gold;
}
/* After content */
h1::after {
content: " ★";
color: gold;
}
/* With quotes */
blockquote::before {
content: open-quote;
}
blockquote::after {
content: close-quote;
}
/* Empty content (for styling) */
.icon::before {
content: "";
display: inline-block;
width: 20px;
height: 20px;
background-image: url('icon.png');
}
Text Pseudo-elements
/* First line */
p::first-line {
font-weight: bold;
color: #333;
}
/* First letter */
p::first-letter {
font-size: 3em;
float: left;
line-height: 1;
margin-right: 0.1em;
}
/* Selection */
::selection {
background-color: yellow;
color: black;
}
/* Placeholder */
input::placeholder {
color: gray;
font-style: italic;
}
Tooltip Example
.tooltip {
position: relative;
display: inline-block;
cursor: help;
}
.tooltip::after {
content: attr(data-tooltip);
position: absolute;
bottom: 125%;
left: 50%;
transform: translateX(-50%);
background-color: #333;
color: white;
padding: 5px 10px;
border-radius: 5px;
white-space: nowrap;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s;
}
.tooltip:hover::after {
opacity: 1;
visibility: visible;
}
<span class="tooltip" data-tooltip="This is a tooltip">
Hover me
</span>
Quote Marks
q::before {
content: open-quote;
}
q::after {
content: close-quote;
}
blockquote {
font-style: italic;
border-left: 4px solid #3498db;
padding-left: 1rem;
margin: 1rem 0;
}
blockquote::before {
content: open-quote;
font-size: 2em;
color: #ccc;
}
blockquote::after {
content: close-quote;
font-size: 2em;
color: #ccc;
}
Complete Example: List Styling
/* Custom list markers */
ul.custom-list {
list-style: none;
padding-left: 0;
}
ul.custom-list li {
position: relative;
padding-left: 1.5rem;
margin-bottom: 0.5rem;
}
ul.custom-list li::before {
content: "✓";
position: absolute;
left: 0;
color: #27ae60;
font-weight: bold;
}
/* Numbered list with custom format */
ol.custom-list {
list-style: none;
padding-left: 0;
counter-reset: item;
}
ol.custom-list li {
counter-increment: item;
padding-left: 2rem;
margin-bottom: 0.5rem;
}
ol.custom-list li::before {
content: counter(item) ".";
position: absolute;
left: 0;
font-weight: bold;
color: #3498db;
}
Complete Example: Accordion
.accordion-item {
border: 1px solid #ddd;
margin-bottom: 0.5rem;
}
.accordion-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
cursor: pointer;
background-color: #f5f5f5;
}
.accordion-header::after {
content: "+";
font-size: 1.5rem;
font-weight: bold;
transition: transform 0.3s;
}
.accordion-item.active .accordion-header::after {
transform: rotate(45deg);
}
.accordion-content {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease;
}
.accordion-item.active .accordion-content {
max-height: 200px;
}
Complete Example: Custom Checkbox
.checkbox-wrapper {
display: flex;
align-items: center;
cursor: pointer;
}
.checkbox-wrapper input {
display: none;
}
.checkbox-mark {
width: 20px;
height: 20px;
border: 2px solid #ccc;
border-radius: 4px;
margin-right: 10px;
position: relative;
transition: background-color 0.3s, border-color 0.3s;
}
.checkbox-wrapper input:checked + .checkbox-mark {
background-color: #3498db;
border-color: #3498db;
}
.checkbox-wrapper input:checked + .checkbox-mark::after {
content: "✓";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
font-size: 14px;
}
<label class="checkbox-wrapper">
<input type="checkbox" />
<span class="checkbox-mark"></span>
<span>Accept terms</span>
</label>
Best Practices
- Use pseudo-classes for states — Hover, focus, active
- Use structural pseudo-classes — Instead of extra classes
- Use pseudo-elements for content — Decorative content only
- Test keyboard navigation — Focus states are important
- Don’t use for essential content — Screen readers may not see it
Common Mistakes
- Wrong order for link states — L-V-H-A
- Using for essential content — Pseudo-elements aren’t accessible
- Not providing focus styles — Keyboard users can’t navigate
- Overusing :not() — Can become complex
- Forgetting ::before/::after need content — Even if empty