Question
How to Work with CSS Pseudo-Elements (::before and ::after) in JavaScript
Question
Is there a way to select or manipulate CSS pseudo-elements such as ::before and ::after using jQuery or plain JavaScript?
For example, suppose a stylesheet contains this rule:
.span::after {
content: 'foo';
}
How can the displayed content value be changed from 'foo' to 'bar' using vanilla JavaScript or jQuery?
Short Answer
By the end of this page, you will understand why ::before and ::after cannot be selected like normal DOM elements, how to read their computed styles, and the common ways developers effectively change their appearance or content using JavaScript and CSS.
Concept
CSS pseudo-elements like ::before and ::after are not real DOM nodes. They are generated by the browser when CSS is applied to an element.
That is the key reason you cannot do something like this:
$('.span::after')
or this:
document.querySelector('.span::after')
Those selectors work on actual elements in the document tree. A pseudo-element is more like a visual layer created from CSS, not an element you can grab and edit directly.
Why this matters
In real programming, this affects how you design dynamic UI behavior:
- You cannot directly attach events to
::beforeor::after - You cannot directly select them with jQuery or DOM APIs
- You usually change the parent element's class, inline style, or CSS variable instead
- You can read pseudo-element styles using
getComputedStyle()
If you need to change what a pseudo-element shows, the usual approach is:
- Change a class on the real element
- Update a CSS custom property (
--value) - Dynamically edit a stylesheet rule
- In some cases, use a real child element instead of a pseudo-element
For the content property specifically, JavaScript can influence it indirectly through CSS or by editing stylesheet rules, but not by selecting the pseudo-element itself as a DOM node.
Mental Model
Think of a pseudo-element like a shadow drawn by CSS next to a real element.
- The real element is the actual object you can hold
- The pseudo-element is a visual decoration painted by the browser
You can change the object so the shadow changes, but you cannot pick up the shadow itself.
So if ::after shows 'foo', you do not directly edit the ::after node, because there is no node. Instead, you change the CSS rule or change something on the parent element that the CSS rule depends on.
Syntax and Examples
Core idea
You cannot directly select ::before or ::after with jQuery or querySelector(), but you can:
- Read pseudo-element styles with
getComputedStyle() - Change classes on the parent element
- Use CSS variables to drive pseudo-element content
- Edit stylesheet rules in JavaScript
Reading pseudo-element content
const el = document.querySelector('.span');
const styles = getComputedStyle(el, '::after');
console.log(styles.content); // often returns '"foo"'
This reads the computed content of ::after.
Best beginner-friendly approach: toggle a class
<span class="span"></span>
.span::after {
content: 'foo';
}
.span.changed::after {
content: 'bar';
}
const el = document.querySelector('.span');
el.classList.add('changed');
Why this works
You are not editing the pseudo-element directly. You are changing the class of the real element, which causes a different CSS rule to apply.
Using a CSS custom property
<span class="span"></span>
.span::after {
content: var(--after-text);
}
.span {
--after-text: 'foo';
}
const el = document.querySelector('.span');
el.style.setProperty('--after-text', '"bar"');
This is a flexible modern solution.
jQuery example with classes
$('.span').addClass('changed');
Editing a stylesheet rule directly
for (const sheet of document.styleSheets) {
for (const rule of sheet.cssRules || []) {
if (rule.selectorText === '.span::after') {
rule.style.content = '"bar"';
}
}
}
This can work, but it is less common and can fail for cross-origin stylesheets.
Step by Step Execution
Consider this example:
<span class="label"></span>
.label::after {
content: 'foo';
}
.label.updated::after {
content: 'bar';
}
const el = document.querySelector('.label');
el.classList.add('updated');
What happens step by step
-
The browser finds the
<span>with classlabel -
CSS applies this rule:
.label::after { content: 'foo'; }So the browser visually creates an
::afterpseudo-element showingfoo -
JavaScript runs:
const el = document.querySelector('.label');This gets the real
<span>element, not the pseudo-element -
Then JavaScript adds a class:
el.classList.add('updated'); -
The element now matches both selectors:
.label::after.label.updated::after
-
Because
.label.updated::afteris more specific, its rule wins:.label.updated::after { content: 'bar'; } -
The browser redraws the pseudo-element, and the text changes from
footobar
Reading the value
You can inspect the final computed value like this:
const afterStyles = getComputedStyle(el, '::after');
console.log(afterStyles.content);
Real World Use Cases
Pseudo-elements are commonly used for decoration and lightweight UI labels. JavaScript often changes the parent element so the pseudo-element updates automatically.
Common use cases
- Notification badges
- Show a small label like
NeworSale
- Show a small label like
- Required form indicators
- Add a red
*after a label
- Add a red
- Status labels
- Display
Online,Offline, orDraft
- Display
- Icons before links or buttons
- Add arrows, bullets, or symbols using CSS
- Tooltips and visual markers
- Decorative arrows or helper text
Example: required field marker
label.required::after {
content: ' *';
color: red;
}
document.querySelector('#emailLabel').classList.add('required');
Example: product badge
.product.sale::before {
content: 'Sale';
background: crimson;
color: white;
padding: 2px 6px;
}
document.querySelector('.product').classList.add('sale');
Real Codebase Usage
In real projects, developers usually avoid directly editing pseudo-element rules unless necessary. Instead, they use maintainable patterns.
Common patterns
1. Class-based state changes
This is the most common approach.
.message::after {
content: 'Pending';
}
.message.sent::after {
content: 'Sent';
}
messageEl.classList.add('sent');
Why teams like it:
- Easy to read
- Works well with CSS architecture
- Keeps style logic in CSS
- Easy to test
2. CSS custom properties for dynamic values
Useful when the displayed value changes often.
.tag::after {
content: var(--tag-text);
}
tagEl.style.setProperty('--tag-text', '"Featured"');
This is common in component-based apps.
3. Guard clauses before changing styles
const el = document.querySelector('.tag');
if (!el) return;
el.style.setProperty('--tag-text', '"Ready"');
This avoids runtime errors when elements do not exist.
4. Use real elements when content is truly data
If the text is meaningful application data, developers often prefer this:
<span class="tag"><span class="tag-text">Ready</span></span>
Instead of storing important text only in ::before or ::after.
Why:
- Better accessibility
- Easier to update
- Easier to test
- Easier to localize
5. Limited stylesheet rule editing
Teams sometimes edit rules dynamically for themes or generated styles, but this is less common because it can be harder to maintain and may be restricted by browser security for external stylesheets.
Common Mistakes
1. Trying to select a pseudo-element directly
Broken code:
document.querySelector('.span::after');
Why it is wrong:
::afteris not a real DOM element- DOM selectors return actual nodes only
Use instead:
const el = document.querySelector('.span');
Then change a class or CSS variable on el.
2. Forgetting that content needs quoted text
Broken code:
el.style.setProperty('--after-text', 'bar');
This may not work correctly for content.
Correct:
el.style.setProperty('--after-text', '"bar"');
The CSS content value for a string must include quotes.
3. Using pseudo-elements for important application text
Broken idea:
- Putting critical data only in
::after
Why it is risky:
- Harder for screen readers
- Harder to copy or inspect
- Harder to update from JavaScript
Better approach:
- Use a real child element when the text is meaningful content
4. Assuming jQuery has special support for pseudo-elements
Broken assumption:
$('.span::after').text('bar');
Why it fails:
- jQuery works with DOM elements, not generated pseudo-elements
5. Editing stylesheet rules without handling cross-origin stylesheets
Broken code:
for (const sheet of document.styleSheets) {
console.log(sheet.cssRules);
}
This can throw errors for stylesheets loaded from another domain.
Safer idea:
- Use class changes or CSS variables when possible
- Use
try...catchif iterating over stylesheets
6. Expecting .style.content on the element to affect ::after
Broken code:
el.style.content = '"bar"';
Why it fails:
- That sets inline style on the real element, not its pseudo-element
- Pseudo-elements must be controlled through CSS rules or variables
Comparisons
| Approach | Can directly select pseudo-element? | Good for dynamic updates | Easy to maintain | Notes |
|---|---|---|---|---|
jQuery selector like $('.x::after') | No | No | No | Does not work because pseudo-elements are not DOM nodes |
querySelector('.x::after') | No | No | No | Same limitation |
getComputedStyle(el, '::after') | Read only | Limited | Yes | Good for inspecting computed styles |
| Toggle classes on the parent | No | Yes | Yes | Best general-purpose solution |
| CSS custom properties | No | Yes | Yes | Great for dynamic values like labels |
| Edit stylesheet rules in JS | Indirectly | Yes | Medium | More complex and may hit security restrictions |
| Use a real child element instead | Yes | Yes | Yes | Best when the text is actual content |
::before / ::after vs real elements
| Feature | Pseudo-element | Real element |
|---|---|---|
| Exists in the DOM | No | Yes |
| Can be selected with JS | No | Yes |
| Can receive events directly | No | Yes |
| Good for decoration | Yes | Yes |
| Good for meaningful text | Usually no | Yes |
| Easy to update dynamically | Indirectly | Yes |
Cheat Sheet
Quick rules
::beforeand::afterare not DOM elements- You cannot select them with jQuery or
querySelector() - You can read their computed styles with
getComputedStyle(el, '::after') - You usually change the parent element's class or set a CSS variable
- For important text, prefer a real HTML element
Read pseudo-element styles
const el = document.querySelector('.item');
const styles = getComputedStyle(el, '::after');
console.log(styles.content);
Change content by toggling a class
.item::after { content: 'foo'; }
.item.active::after { content: 'bar'; }
el.classList.add('active');
Change content with a CSS variable
.item {
--after-text: 'foo';
}
.item::after {
content: var(--after-text);
}
el.style.setProperty('--after-text', '"bar"');
Edit a stylesheet rule
rule.style.content = '"bar"';
Common gotcha
For string content, include quotes inside the value:
'"bar"'
not:
'bar'
FAQ
Can JavaScript select ::before or ::after directly?
No. Pseudo-elements are not real DOM nodes, so they cannot be selected directly with DOM APIs or jQuery.
Can jQuery manipulate pseudo-elements?
Not directly. jQuery can manipulate the parent element, and that can indirectly change the pseudo-element through CSS.
How do I change content in ::after using JavaScript?
The usual methods are:
- add or remove a class
- set a CSS custom property
- edit a stylesheet rule
Can I read the current content value of a pseudo-element?
Yes, with getComputedStyle(element, '::after').content.
Why does setting el.style.content not work?
Because that affects the real element's inline style, not the pseudo-element. Pseudo-elements are controlled through CSS rules.
Should I use pseudo-elements for important text?
Usually no. If the text is meaningful content, use a real HTML element so it is easier to update, access, and test.
Is :after different from ::after?
They refer to the same concept in practice for this case. ::after is the modern pseudo-element syntax, while :after is the older form.
What is the best approach in modern code?
Usually either:
- toggle classes for named states, or
- use CSS custom properties for dynamic values
Mini Project
Description
Build a small status label component that uses ::after to display a status message. The project demonstrates the correct way to change pseudo-element content from JavaScript without trying to select the pseudo-element itself.
Goal
Create a label whose ::after text changes between Pending, Processing, and Done when buttons are clicked.
Requirements
- Create one visible label element in HTML.
- Use a
::afterpseudo-element to show the current status text. - Add three buttons for
Pending,Processing, andDone. - Update the pseudo-element text using JavaScript.
- Do not try to directly select
::afterin JavaScript.
Keep learning
Related questions
Angular ngClass Conditional Class Binding Explained
Learn how to use Angular ngClass for conditional classes, fix common binding mistakes, and understand why this template error happens.
CSS :not() Selector for Elements Without a Class or Attribute
Learn how to use the CSS :not() selector to target elements that do not have a specific class or attribute, with examples and common pitfalls.
CSS Font Scaling Relative to Container Size: %, em, rem, vw, and Responsive Text
Learn how CSS font scaling really works and how to make text responsive using %, em, rem, vw, clamp(), and media queries.