How to pass JavaScript values to CSS Pseudo-elements
Pseudo-elements like ::before and ::after are incredibly useful for adding decorative content, badges, labels, or icons. But by default, they cannot directly access JavaScript variables.
The good news is that there are modern, clean ways to bridge this gap.
Why you might need this
In real-world UI development, you often need dynamic styling such as:
- Showing dynamic counters (e.g. notification badges: “3”)
- Displaying labels like “Week 5”, “Step 2”, or “Level 10”
- Showing user-specific data (e.g. initials or status tags)
- Rendering countdown text like “Ends in 2 days”
- Creating dynamic tooltips or hints
- Updating UI states without adding extra DOM elements
Instead of injecting extra HTML, pseudo-elements keep your markup clean and semantic.
The Key Idea: CSS Custom Properties (Variables)
The most modern and recommended approach is using CSS custom properties (variables).
JavaScript can update these variables dynamically, and CSS can read them inside pseudo-elements.
I’ll focus on implementing this with vanilla JS.
Example 1: Dynamic Counter Badge Demo
Use the slider below to change the counter value.
Use the slider below to change the counter value:
Example 1: Dynamic Counter Badge code
<div class="example-one-holder">
<button type="button" class="notification" id="notification">
Notifications
</button>
<p>Use the slider below to change the counter value:</p>
<input type="range" id="counterSlider" min="0" max="99" value="3">
</div>
.example-one-holder {
max-width: 300px;
margin: 0 auto;
padding: 20px;
border: 1px solid #c9c9c9;
border-radius: 10px;
display: flex;
flex-direction: column;
gap: 12px;
}
.example-one-holder .notification {
position: relative;
padding: 10px 20px;
background-color: #eee;
display: flex;
font-size: 20px;
--counter: "0";
font-weight: 400;
justify-content: center;
border: 1px solid #bbb;
border-radius: 6px;
color: #000;
cursor: default;
}
.example-one-holder .notification::after {
content: var(--counter);
position: absolute;
top: -10px;
right: -10px;
background-color: #ff0000;
color: #ffffff;
font-weight: 500;
border-radius: 50%;
font-size: 14px;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
}
document.addEventListener("DOMContentLoaded", () => {
const counterSlider = document.getElementById("counterSlider");
const notification = document.getElementById("notification");
if (!counterSlider || !notification) {
return;
}
const updateCounter = (value) => {
notification.style.setProperty("--counter", `'${value}'`);
};
updateCounter(counterSlider.value);
counterSlider.addEventListener("input", (e) => {
updateCounter(e.target.value);
});
});
Example 2: Dynamic Sale Label Demo
Sample product card with a dynamic sale label that counts down to the end of the sale. The label updates every second to show the remaining time.
Some random product name
€29.99
Example 2: Dynamic Sale Label code
<div class="example-two-holder">
<div class="product-card">
<h2>Some random product name</h2>
<p>€29.99</p>
</div>
</div>
.example-two-holder {
max-width: 300px;
margin: 20px auto;
padding: 20px;
border: 1px solid #c9c9c9;
border-radius: 10px;
display: flex;
flex-direction: column;
}
.example-two-holder .product-card {
position: relative;
padding: 20px 20px 100px 20px;
background-color: #f5f5f5;
text-align: center;
font-family: Arial, sans-serif;
--sale-label: "";
}
.example-two-holder .product-card::before {
content: var(--sale-label);
position: absolute;
white-space: pre;
bottom: 10px;
left: -10px;
color: #ffffff;
font-weight: bold;
padding: 15px 10px;
font-size: 12px;
box-sizing: border-box;
width: 143px;
height: 55px;
background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTQzIiBoZWlnaHQ9IjU1IiB2aWV3Qm94PSIwIDAgMTQzIDU1IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNMTQzIDEzSDZWNTVIMTQzTDEyNi4xOTggMzRMMTQzIDEzWiIgZmlsbD0idXJsKCNwYWludDBfbGluZWFyXzQ0MzNfNjM3OCkiLz4KPHBhdGggZD0iTTAgNkMwIDIuNjg2MjkgMi42ODYyOSAwIDYgMFY1NUMyLjY4NjI5IDU1IDAgNTIuMzEzNyAwIDQ5VjZaIiBmaWxsPSIjOTc0N0ZGIi8+CjxwYXRoIGQ9Ik0wIDZDMCAyLjY4NjI5IDIuNjg2MjkgMCA2IDBWMTNDMi42ODYyOSAxMyAwIDEwLjMxMzcgMCA3VjZaIiBmaWxsPSIjNTMwQkIyIi8+CjxkZWZzPgo8bGluZWFyR3JhZGllbnQgaWQ9InBhaW50MF9saW5lYXJfNDQzM182Mzc4IiB4MT0iODEuMzkzMSIgeTE9IjM0IiB4Mj0iLTM3Ljk0MzQiIHkyPSIzNCIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgo8c3RvcCBzdG9wLWNvbG9yPSIjOTc0N0ZGIi8+CjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzk3NDdGRiIvPgo8L2xpbmVhckdyYWRpZW50Pgo8L2RlZnM+Cjwvc3ZnPgo=);
}
document.addEventListener("DOMContentLoaded", () => {
const productCard = document.querySelector(".product-card");
if (!productCard) {
return;
}
const endDate = new Date();
endDate.setDate(endDate.getDate() + 2);
const updateSaleLabel = () => {
const now = new Date();
const timeDiff = endDate - now;
if (timeDiff <= 0) {
productCard.style.setProperty("--sale-label", "'Sale ended'");
clearInterval(interval);
return;
}
const days = Math.floor(timeDiff / (1000 * 60 * 60 * 24));
const hours = Math.floor((timeDiff / (1000 * 60 * 60)) % 24);
const minutes = Math.floor((timeDiff / (1000 * 60)) % 60);
const seconds = Math.floor((timeDiff / 1000) % 60);
const label = `Sale ends in \\A ${days}d ${hours}h ${minutes}m ${seconds}s`;
productCard.style.setProperty("--sale-label", `'${label}'`);
};
updateSaleLabel();
const interval = setInterval(updateSaleLabel, 1000);
});
Best Practices
- Always initialize the CSS variable, for example:
--dynamic-text: ""; - Use
white-space: pre;orwhite-space: pre-line;for line breaks - Escape quotes properly when setting values from JavaScript
- Prefer plain, non-nested CSS in inline
<style>blocks for broader browser compatibility
Conclusion
Passing JavaScript values to CSS pseudo-elements is now easier than ever thanks to CSS custom properties. This technique keeps your HTML clean and your styling powerful. Whether you’re building dynamic badges, labels, or tooltips, this approach allows you to leverage the full power of CSS while still keeping your JavaScript logic intact.
Got a specific project in mind? Feel free to contact me. I’m always happy to chat about how I can help you with your next web project.