CSS Pseudo-Classes and Pseudo-Elements
Pseudo-classes and pseudo-elements allow you to target the state or specific parts of an element without adding classes or IDs in the HTML. This is a very powerful CSS feature for interactions and decoration.
Pseudo-Class
A pseudo-class uses a single colon (:) followed by the pseudo-class name. A pseudo-class targets a specific state of an element.
1. :hover
Applied when the user hovers the cursor over an element.
.btn {
display: inline-block;
padding: 12px 30px;
background-color: #3498db;
color: white;
text-decoration: none;
border-radius: 8px;
font-weight: 600;
transition: all 0.3s ease;
border: 2px solid #3498db;
}
.btn:hover {
background-color: transparent;
color: #3498db;
transform: translateY(-3px);
box-shadow: 0 6px 20px rgba(52, 152, 219, 0.3);
}
.card {
background: white;
border-radius: 12px;
padding: 25px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
}
.card:hover {
transform: translateY(-8px);
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.15);
}
.nav-link {
color: #333;
text-decoration: none;
padding: 8px 16px;
border-radius: 5px;
transition: background-color 0.3s ease;
}
.nav-link:hover {
background-color: #f0f0f0;
color: #3498db;
}
2. :active
Applied when an element is being clicked (while the mouse button is pressed).
.btn:active {
transform: translateY(1px);
box-shadow: 0 2px 8px rgba(52, 152, 219, 0.2);
}
.link:active {
color: #e74c3c;
}
/* The correct order: L-V-H-A (Link, Visited, Hover, Active) */
a:link { color: #3498db; }
a:visited { color: #9b59b6; }
a:hover { color: #2980b9; }
a:active { color: #e74c3c; }
3. :focus
Applied when an element receives focus (usually when clicked or tabbed to).
input:focus {
border-color: #3498db;
outline: none;
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.2);
}
textarea:focus {
border-color: #27ae60;
outline: none;
box-shadow: 0 0 0 3px rgba(39, 174, 96, 0.2);
resize: vertical;
}
.btn:focus {
outline: 3px solid rgba(52, 152, 219, 0.5);
outline-offset: 2px;
}
/* Accessible focus styles */
.nav-link:focus {
background-color: #e3f2fd;
color: #1565c0;
outline: 2px solid #1565c0;
outline-offset: 2px;
}
4. :visited
Applied to links that have already been visited.
a:visited {
color: #9b59b6;
text-decoration: none;
}
a:visited:hover {
color: #8e44ad;
text-decoration: underline;
}
/* Reset visited style for navigation */
nav a:visited {
color: white;
}
5. :first-child and :last-child
Targets the first or last element within its parent.
/* First element in a list */
ul li:first-child {
font-weight: 700;
color: #3498db;
border-top: none;
}
/* Last element in a list */
ul li:last-child {
border-bottom: none;
margin-bottom: 0;
}
/* Styling the first and last card */
.card-list .card:first-child {
border-top-left-radius: 12px;
border-top-right-radius: 12px;
}
.card-list .card:last-child {
border-bottom-left-radius: 12px;
border-bottom-right-radius: 12px;
}
/* First paragraph in a section */
.section p:first-child {
font-size: 1.2rem;
font-weight: 500;
color: #2c3e50;
}
<ul>
<li>First Item (Bold, Blue)</li>
<li>Second Item</li>
<li>Third Item (Border-bottom removed)</li>
</ul>
6. :nth-child()
Targets elements based on their position or a specific pattern.
/* Styling alternating rows (zebra stripes) */
table tr:nth-child(even) {
background-color: #f8f9fa;
}
table tr:nth-child(odd) {
background-color: #ffffff;
}
/* First row */
table tr:nth-child(1) {
background-color: #3498db;
color: white;
font-weight: 700;
}
/* Every 3rd element */
.grid-item:nth-child(3n) {
border-right: none;
}
/* 2nd element from the end */
li:nth-last-child(2) {
color: #e74c3c;
font-weight: 600;
}
/* Alternating pattern: 1, 4, 7, 10... */
.item:nth-child(3n+1) {
background-color: #e8f4fd;
}
/* Even elements in a list */
.list-item:nth-child(even) {
background-color: #f5f5f5;
padding: 15px;
border-radius: 5px;
}
/* Odd elements in a list */
.list-item:nth-child(odd) {
background-color: #ffffff;
padding: 15px;
border-radius: 5px;
}
7. :not()
Targets all elements except those that match a specific selector.
/* All links except those in the footer */
footer a {
color: #999;
font-size: 0.9rem;
}
/* All paragraphs except the first one */
p:not(:first-child) {
color: #666;
line-height: 1.8;
}
/* All buttons except disabled ones */
.btn:not(:disabled):hover {
background-color: #2980b9;
transform: translateY(-2px);
}
/* All inputs except submit */
.form input:not([type="submit"]) {
border: 2px solid #ddd;
padding: 10px 15px;
border-radius: 5px;
}
/* List items without a specific class */
.list li:not(.special) {
color: #666;
font-size: 0.95rem;
}
<p>First paragraph (Not affected by :not)</p>
<p>Second paragraph (Selected by :not(:first-child))</p>
<p>Third paragraph (Selected by :not(:first-child))</p>
8. :checked
Applied to a checkbox or radio button that is currently checked.
/* Custom checkbox */
input[type="checkbox"]:checked + label {
color: #27ae60;
font-weight: 600;
}
input[type="checkbox"]:checked + label::before {
content: "✓ ";
color: #27ae60;
}
/* Custom radio */
input[type="radio"]:checked + label {
color: #3498db;
font-weight: 600;
}
/* Checkbox styling */
input[type="checkbox"] {
width: 20px;
height: 20px;
accent-color: #3498db;
cursor: pointer;
}
input[type="checkbox"]:checked {
background-color: #3498db;
border-color: #3498db;
}
<input type="checkbox" id="terms" checked>
<label for="terms">I agree to the terms and conditions</label>
<input type="checkbox" id="newsletter">
<label for="newsletter">Subscribe to the newsletter</label>
<input type="radio" name="plan" id="free" checked>
<label for="free">Free Plan</label>
<input type="radio" name="plan" id="pro">
<label for="pro">Pro Plan</label>
9. :disabled
Applied to elements that are disabled.
input:disabled,
select:disabled,
textarea:disabled {
background-color: #f5f5f5;
color: #999;
cursor: not-allowed;
border-color: #e0e0e0;
}
.btn:disabled {
background-color: #bdc3c7;
color: #7f8c8d;
cursor: not-allowed;
transform: none;
}
.btn:disabled:hover {
background-color: #bdc3c7;
box-shadow: none;
transform: none;
}
<input type="text" disabled value="Disabled input">
<button class="btn" disabled>Disabled Button</button>
Pseudo-Element
A pseudo-element uses two colons (::) followed by the pseudo-element name. A pseudo-element creates a virtual element that does not exist in the HTML.
1. ::before
Inserts content before the element's content.
/* Icon before the title */
h2::before {
content: "▸ ";
color: #3498db;
font-weight: 700;
}
/* Quote marks */
blockquote::before {
content: """;
font-size: 4rem;
color: #3498db;
opacity: 0.3;
position: absolute;
top: -20px;
left: -10px;
font-family: Georgia, serif;
}
/* Label before the input */
label.required::before {
content: "* ";
color: #e74c3c;
font-weight: 700;
}
/* Card number */
.card-number::before {
content: "No. ";
color: #999;
font-weight: 400;
}
2. ::after
Inserts content after the element's content.
/* Link external indicator */
a[target="_blank"]::after {
content: " ↗";
font-size: 0.8em;
vertical-align: super;
}
/* Separator */
.menu-item + .menu-item::before {
content: "|";
color: #ddd;
margin: 0 10px;
}
/* Clearfix */
.clearfix::after {
content: "";
display: table;
clear: both;
}
/* Tooltip */
.tooltip {
position: relative;
cursor: help;
}
.tooltip::after {
content: attr(data-tooltip);
position: absolute;
bottom: 125%;
left: 50%;
transform: translateX(-50%);
background-color: #333;
color: white;
padding: 8px 12px;
border-radius: 5px;
font-size: 0.85rem;
white-space: nowrap;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
}
.tooltip:hover::after {
opacity: 1;
visibility: visible;
}
<a href="https://example.com" target="_blank">External Link</a>
<div class="tooltip" data-tooltip="This is a tooltip">Hover me</div>
3. ::first-line
Targets the first line of a block element.
p::first-line {
font-weight: 700;
color: #2c3e50;
text-transform: uppercase;
}
.article::first-line {
font-size: 1.1em;
line-height: 2;
}
4. ::first-letter
Targets the first letter of a block element.
/* Drop cap effect */
p:first-of-type::first-letter {
font-size: 4rem;
font-weight: 700;
color: #3498db;
float: left;
line-height: 0.8;
margin-right: 10px;
margin-top: 5px;
font-family: Georgia, serif;
}
/* Title with a large first letter */
h1::first-letter {
font-size: 150%;
color: #e74c3c;
}
5. ::selection
Targets the portion of text being selected by the user.
::selection {
background-color: #3498db;
color: white;
}
/* Special selection for code blocks */
code::selection {
background-color: #2c3e50;
color: #ecf0f1;
}
/* Selection for headings */
h1::selection,
h2::selection {
background-color: #e74c3c;
color: white;
}
6. ::placeholder
Targets placeholder text on an input.
input::placeholder {
color: #999;
font-style: italic;
font-size: 0.95rem;
}
textarea::placeholder {
color: #aaa;
font-size: 1rem;
}
/* Placeholder with animation */
input:focus::placeholder {
color: #3498db;
opacity: 0.7;
}
<input type="text" placeholder="Enter your name...">
<textarea placeholder="Write your message here..."></textarea>
Practice: Form States with Pseudo-Classes
/* Form Styling */
.form {
max-width: 500px;
margin: 40px auto;
padding: 30px;
background: white;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #2c3e50;
}
/* Required field indicator */
.form-group label.required::after {
content: " *";
color: #e74c3c;
}
/* Input default */
.form-group input {
width: 100%;
padding: 12px 15px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 1rem;
transition: all 0.3s ease;
}
/* Focus state */
.form-group input:focus {
border-color: #3498db;
outline: none;
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.15);
}
/* Placeholder styling */
.form-group input::placeholder {
color: #aaa;
font-style: italic;
}
/* Error state */
.form-group.error input {
border-color: #e74c3c;
}
.form-group.error input:focus {
box-shadow: 0 0 0 3px rgba(231, 76, 60, 0.15);
}
/* Success state */
.form-group.success input {
border-color: #27ae60;
}
.form-group.success input:focus {
box-shadow: 0 0 0 3px rgba(39, 174, 96, 0.15);
}
/* Disabled state */
.form-group input:disabled {
background-color: #f5f5f5;
color: #999;
cursor: not-allowed;
}
/* Checkbox group */
.checkbox-group {
display: flex;
flex-direction: column;
gap: 10px;
}
.checkbox-group label {
display: flex;
align-items: center;
gap: 10px;
cursor: pointer;
font-weight: 400;
}
/* Custom checkbox */
.checkbox-group input[type="checkbox"] {
width: 20px;
height: 20px;
accent-color: #3498db;
cursor: pointer;
}
/* Checked state label */
.checkbox-group input[type="checkbox"]:checked + span {
color: #27ae60;
font-weight: 600;
}
/* First and last item in form */
.form-group:first-of-type {
margin-top: 10px;
}
.form-group:last-of-type {
margin-bottom: 0;
}
<form class="form">
<div class="form-group">
<label class="required">Email</label>
<input type="email" required placeholder="[email protected]">
</div>
<div class="form-group error">
<label class="required">Password</label>
<input type="password" required placeholder="Minimum 8 characters">
</div>
<div class="form-group success">
<label>Username</label>
<input type="text" value="johndoe">
</div>
<div class="form-group">
<label>Bio</label>
<input type="text" disabled value="Temporarily disabled">
</div>
<div class="form-group checkbox-group">
<label>
<input type="checkbox" checked>
<span>I agree to the terms</span>
</label>
<label>
<input type="checkbox">
<span>Subscribe to the newsletter</span>
</label>
</div>
</form>
Practice: List Styling with Pseudo-Classes
/* Custom list styling */
.feature-list {
max-width: 600px;
margin: 40px auto;
padding: 0;
list-style: none;
}
.feature-list li {
padding: 20px;
margin-bottom: 10px;
background: white;
border-radius: 8px;
border-left: 4px solid #3498db;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
position: relative;
}
/* Numbered list with ::before */
.feature-list li::before {
content: counter(feature-counter);
counter-increment: feature-counter;
position: absolute;
left: -15px;
top: 50%;
transform: translateY(-50%);
background-color: #3498db;
color: white;
width: 30px;
height: 30px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 0.9rem;
}
/* Reset counter */
.feature-list {
counter-reset: feature-counter;
}
/* First item */
.feature-list li:first-child {
border-top-left-radius: 12px;
border-top-right-radius: 12px;
}
/* Last item */
.feature-list li:last-child {
border-bottom-left-radius: 12px;
border-bottom-right-radius: 12px;
margin-bottom: 0;
}
/* Hover state */
.feature-list li:hover {
transform: translateX(10px);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.12);
border-left-color: #2c3e50;
}
/* Even items */
.feature-list li:nth-child(even) {
border-left-color: #27ae60;
}
.feature-list li:nth-child(even)::before {
background-color: #27ae60;
}
/* Not last child */
.feature-list li:not(:last-child) {
margin-bottom: 10px;
}
.feature-list li:not(:last-child)::after {
content: "↓";
position: absolute;
right: 20px;
top: 50%;
transform: translateY(-50%);
color: #ddd;
font-size: 1.2rem;
}
/* Selection style */
.feature-list li::selection {
background-color: #3498db;
color: white;
}
<ol class="feature-list">
<li>First feature with a complete description</li>
<li>Second feature with its own unique advantages</li>
<li>Third innovative feature</li>
<li>Fourth feature for productivity</li>
<li>Fifth easy-to-use feature</li>
</ol>
Practice: Text Decoration with Pseudo-Elements
/* Decorative heading */
.section-title {
text-align: center;
font-size: 2.5rem;
color: #2c3e50;
margin-bottom: 40px;
position: relative;
padding-bottom: 20px;
}
.section-title::after {
content: "";
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 80px;
height: 4px;
background: linear-gradient(90deg, #3498db, #9b59b6);
border-radius: 2px;
}
/* Quote styling */
.quote {
position: relative;
padding: 30px 40px;
background-color: #f8f9fa;
border-radius: 10px;
font-style: italic;
color: #555;
line-height: 1.8;
}
.quote::before {
content: "\201C";
font-size: 5rem;
color: #3498db;
opacity: 0.2;
position: absolute;
top: -10px;
left: 15px;
font-family: Georgia, serif;
line-height: 1;
}
.quote::after {
content: "\201D";
font-size: 5rem;
color: #3498db;
opacity: 0.2;
position: absolute;
bottom: -40px;
right: 15px;
font-family: Georgia, serif;
line-height: 1;
}
/* Tag styling */
.tag {
display: inline-block;
padding: 4px 12px;
background-color: #e8f4fd;
color: #3498db;
border-radius: 15px;
font-size: 0.85rem;
font-weight: 500;
position: relative;
}
.tag::before {
content: "#";
margin-right: 2px;
opacity: 0.7;
}
/* Badge with notification count */
.notification-badge {
position: relative;
display: inline-block;
}
.notification-badge::after {
content: attr(data-count);
position: absolute;
top: -8px;
right: -8px;
background-color: #e74c3c;
color: white;
font-size: 0.75rem;
font-weight: 700;
min-width: 20px;
height: 20px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
padding: 0 5px;
}
/* Drop cap for article */
.article p:first-of-type::first-letter {
font-size: 4.5rem;
font-weight: 700;
color: #3498db;
float: left;
line-height: 0.8;
margin-right: 12px;
margin-top: 8px;
font-family: Georgia, serif;
}
/* First line emphasis */
.intro-text::first-line {
font-weight: 700;
color: #2c3e50;
font-size: 1.1em;
}
/* Selection style */
.highlight-text::selection {
background-color: #fff3cd;
color: #856404;
}
<h2 class="section-title">Section Title</h2>
<blockquote class="quote">
CSS allows us to create boundless web design.
</blockquote>
<span class="tag">CSS</span>
<span class="tag">Tutorial</span>
<span class="tag">Web</span>
<span class="notification-badge" data-count="5">Message</span>
<article class="article">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.</p>
<p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</p>
</article>
Summary
Pseudo-classes (:) target element states such as :hover, :focus, :checked, :nth-child(), and :not(). Pseudo-elements (::) create virtual elements such as ::before, ::after, ::first-line, ::first-letter, and ::selection. Both enable interactive and decorative styling without adding extra markup to the HTML.