Skillversitas

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.