How to Select and Manipulate CSS ::before and ::after Pseudo-Elements with JavaScript or jQuery
CSS pseudo-elements like ::before and ::after are powerful tools for adding decorative or supplementary content to elements without cluttering the HTML. They are commonly used for icons, tooltips, separators, or visual flourishes. However, unlike regular DOM elements, ::before and ::after are not part of the DOM tree, which means you cannot directly select or manipulate them with JavaScript or jQuery.
This limitation often confuses developers: How do you dynamically update a pseudo-element’s content, color, or position? While direct manipulation isn’t possible, there are clever workarounds using CSS variables, class toggling, data attributes, and more.
In this blog, we’ll demystify pseudo-element manipulation. We’ll cover why direct access is restricted, explore practical methods to control ::before and ::after with JavaScript/jQuery, and walk through real-world examples. By the end, you’ll be equipped to dynamically style these pseudo-elements in your projects.
Table of Contents#
- Understanding ::before and ::after Pseudo-Elements
- Limitations of Direct Access
- Methods to Manipulate Pseudo-Elements
- Practical Examples
- Best Practices
- Conclusion
- Reference
1. Understanding ::before and ::after Pseudo-Elements#
Before diving into manipulation, let’s recap what ::before and ::after are:
- Pseudo-elements are CSS constructs that allow you to style specific parts of an element or insert content before/after an element’s content.
::beforeinserts content before the element’s content, and::afterinserts it after.- They are not part of the DOM (Document Object Model), meaning they don’t exist in the HTML structure. Instead, the browser generates them dynamically based on CSS rules.
- They require the
contentproperty to be defined (even if set tocontent: "";for empty content).
Basic Example:#
.button::before {
content: "→"; /* Required: Defines the content to insert */
margin-right: 8px;
color: #3498db;
}This adds a "→" icon before the text of any element with class button.
2. Limitations of Direct Access#
The key challenge with ::before and ::after is that they are not part of the DOM. This means:
- You cannot select them with JavaScript/jQuery methods like
document.querySelector()or$(). - You cannot directly modify their styles (e.g.,
element::before.style.color = "red"will fail).
Why?#
Pseudo-elements are generated by the browser as part of the rendering process, not the DOM. They are treated as "virtual" children of their parent element but are not exposed to the DOM API.
Can You Read Pseudo-Element Styles?#
Yes! You can read their computed styles using window.getComputedStyle(), but you cannot write to them. For example:
const button = document.querySelector('.button');
const beforeStyle = window.getComputedStyle(button, '::before');
console.log(beforeStyle.getPropertyValue('color')); // Output: rgb(52, 152, 219) (matches #3498db)This is useful for debugging, but not for dynamic updates.
3. Methods to Manipulate Pseudo-Elements#
Since direct manipulation is impossible, we use indirect methods by targeting the parent element of the pseudo-element. Below are the most common approaches:
3.1 Using CSS Variables (Custom Properties)#
CSS variables (officially called "custom properties") are reusable values defined in CSS. By binding pseudo-element styles to variables, you can update the variables with JavaScript, which in turn updates the pseudo-element.
How It Works:#
- Define CSS variables on the parent element (or
:rootfor global variables). - Use these variables in the pseudo-element’s CSS rules.
- Update the variables with JavaScript on the parent element.
Example:#
Step 1: Define Variables in CSS
/* Define a variable for the ::before color */
.button {
--before-color: #3498db; /* Default color */
}
/* Use the variable in ::before */
.button::before {
content: "→";
color: var(--before-color); /* Bind to the variable */
margin-right: 8px;
}Step 2: Update Variables with JavaScript
const button = document.querySelector('.button');
// Change the ::before color to red
button.style.setProperty('--before-color', '#e74c3c');Why This Works:
When you update --before-color on the parent .button, the var(--before-color) in ::before automatically inherits the new value.
3.2 Adding/Removing CSS Classes#
For predefined styles (e.g., "active" or "disabled" states), define CSS classes with different pseudo-element rules and toggle these classes on the parent element.
Example:#
Step 1: Define Classes in CSS
/* Default state */
.button::before {
content: "→";
color: #3498db;
}
/* "success" state: Green icon */
.button.success::before {
content: "✓";
color: #2ecc71;
}
/* "error" state: Red icon */
.button.error::before {
content: "✗";
color: #e74c3c;
}Step 2: Toggle Classes with JavaScript
const button = document.querySelector('.button');
// Switch to "success" state
button.classList.add('success');
button.classList.remove('error');
// Later, switch to "error" state
button.classList.remove('success');
button.classList.add('error');Best For:
Predefined visual states (e.g., hover, active, disabled) where styles are static and reusable.
3.3 Inline Styles with Data Attributes#
Use HTML data attributes (e.g., data-before-text) to pass dynamic content to pseudo-elements. CSS can read these attributes using the attr() function.
Example: Dynamic Content#
Step 1: Define Data Attribute in HTML
<div class="counter" data-after-text="items"></div>Step 2: Use attr() in CSS
.counter::after {
content: " " attr(data-after-text); /* Inserts " items" */
color: #666;
}Step 3: Update Data Attribute with JavaScript
const counter = document.querySelector('.counter');
// Change the ::after text to "products"
counter.dataset.afterText = "products";
// Now ::after content is " products"Example: Dynamic Values (e.g., Numbers)#
You can even combine data attributes with variables for numeric values like width or height:
.progress::before {
content: "";
display: block;
width: attr(data-progress width); /* Reads data-progress as a width value */
height: 8px;
background: #3498db;
}const progress = document.querySelector('.progress');
progress.dataset.progress = "75%"; // ::before width becomes 75%3.4 jQuery-Specific Methods#
jQuery simplifies DOM manipulation, but it doesn’t add new ways to target pseudo-elements directly. Instead, it wraps the vanilla JS methods above with cleaner syntax.
Example 1: Updating CSS Variables with jQuery#
$('.button').css('--before-color', '#e74c3c'); // Same as vanilla JS setPropertyExample 2: Toggling Classes with jQuery#
$('.button').addClass('success').removeClass('error');Example 3: Reading Data Attributes with jQuery#
$('.counter').data('afterText', 'products'); // Updates data-after-text4. Practical Examples#
Let’s apply these methods to real-world scenarios.
Example 1: Dynamic Tooltip (Updating ::before Text)#
Create a tooltip where the ::before text changes based on user input.
HTML:
<input type="text" class="tooltip-input" placeholder="Enter tooltip text">
<div class="tooltip" data-before-text="Hover me!">I have a tooltip</div>CSS:
.tooltip {
position: relative;
display: inline-block;
padding: 8px 16px;
background: #f1f1f1;
cursor: help;
}
.tooltip::before {
content: attr(data-before-text); /* Use data attribute for text */
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
padding: 4px 8px;
background: #333;
color: white;
border-radius: 4px;
white-space: nowrap;
opacity: 0;
transition: opacity 0.2s;
}
.tooltip:hover::before {
opacity: 1;
}JavaScript:
// Update tooltip text when input changes
const input = document.querySelector('.tooltip-input');
const tooltip = document.querySelector('.tooltip');
input.addEventListener('input', (e) => {
tooltip.dataset.beforeText = e.target.value || "Hover me!"; // Fallback text
});Example 2: Theme Switcher (Changing Pseudo-Element Colors)#
Use CSS variables to let users switch between light/dark themes, including pseudo-element colors.
CSS:
:root {
--primary-color: #3498db; /* Light theme */
--before-color: var(--primary-color);
}
.dark-theme {
--primary-color: #9b59b6; /* Dark theme */
}
.button::before {
content: "→";
color: var(--before-color);
margin-right: 8px;
}JavaScript:
const themeToggle = document.querySelector('#theme-toggle');
themeToggle.addEventListener('click', () => {
document.documentElement.classList.toggle('dark-theme');
// Toggles --primary-color, which updates ::before color
});Example 3: Progress Bar with Animated ::before#
Animate a progress bar where ::before represents the progress percentage.
CSS:
.progress-bar {
width: 300px;
height: 20px;
background: #eee;
border-radius: 10px;
overflow: hidden;
}
.progress-bar::before {
content: "";
display: block;
height: 100%;
width: var(--progress, 0%); /* Uses --progress variable (default 0%) */
background: #2ecc71;
transition: width 0.3s ease;
}JavaScript:
const progressBar = document.querySelector('.progress-bar');
let progress = 0;
// Simulate progress update every second
const interval = setInterval(() => {
progress += 10;
progressBar.style.setProperty('--progress', `${progress}%`);
if (progress >= 100) {
clearInterval(interval);
}
}, 1000);5. Best Practices#
-
Avoid Critical Content in Pseudo-Elements
Pseudo-elements are not accessible to screen readers and may not be indexed by search engines. Use them for decorative content only. -
Prefer CSS Variables for Dynamic Values
CSS variables are the most flexible way to handle dynamic styles (e.g., colors, widths) for pseudo-elements. -
Use Classes for Predefined States
For static states (e.g., "success," "error"), define classes with pseudo-element rules and toggle classes on the parent. -
Test Cross-Browser Compatibility
CSS variables are supported in all modern browsers, but older browsers (e.g., IE11) do not support them. Use data attributes or class toggling as fallbacks. -
Minimize Reflows
Frequent class toggling or variable updates can trigger browser reflows (layout recalculations). Batch changes when possible.
6. Conclusion#
While ::before and ::after pseudo-elements cannot be directly manipulated with JavaScript or jQuery, indirect methods like CSS variables, class toggling, and data attributes provide powerful workarounds. Choose the method that best fits your use case:
- CSS variables for dynamic, fine-grained style changes.
- Class toggling for predefined visual states.
- Data attributes for dynamic content insertion.
With these techniques, you can unlock the full potential of pseudo-elements in dynamic web applications.