Question

How to Work with CSS Pseudo-Elements (::before and ::after) in JavaScript

cssjavascriptjqueryjquery-selectorspseudo-element

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 ::before or ::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:

  1. Change a class on the real element
  2. Update a CSS custom property (--value)
  3. Dynamically edit a stylesheet rule
  4. 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.

Take Quiz

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

  1. The browser finds the <span> with class label

  2. CSS applies this rule:

    .label::after {
      content: 'foo';
    }
    

    So the browser visually creates an ::after pseudo-element showing foo

  3. JavaScript runs:

    const el = document.querySelector('.label');
    

    This gets the real <span> element, not the pseudo-element

  4. Then JavaScript adds a class:

    el.classList.add('updated');
    
  5. The element now matches both selectors:

    • .label::after
    • .label.updated::after
  6. Because .label.updated::after is more specific, its rule wins:

    .label.updated::after {
      content: 'bar';
    }
    
  7. The browser redraws the pseudo-element, and the text changes from foo to bar

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 New or Sale
  • Required form indicators
    • Add a red * after a label
  • Status labels
    • Display Online, Offline, or Draft
  • 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');
Take Quiz

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:

  • ::after is 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...catch if 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

ApproachCan directly select pseudo-element?Good for dynamic updatesEasy to maintainNotes
jQuery selector like $('.x::after')NoNoNoDoes not work because pseudo-elements are not DOM nodes
querySelector('.x::after')NoNoNoSame limitation
getComputedStyle(el, '::after')Read onlyLimitedYesGood for inspecting computed styles
Toggle classes on the parentNoYesYesBest general-purpose solution
CSS custom propertiesNoYesYesGreat for dynamic values like labels
Edit stylesheet rules in JSIndirectlyYesMediumMore complex and may hit security restrictions
Use a real child element insteadYesYesYesBest when the text is actual content

::before / ::after vs real elements

FeaturePseudo-elementReal element
Exists in the DOMNoYes
Can be selected with JSNoYes
Can receive events directlyNoYes
Good for decorationYesYes
Good for meaningful textUsually noYes
Easy to update dynamicallyIndirectlyYes
Take Quiz

Cheat Sheet

Quick rules

  • ::before and ::after are 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
Take Quiz

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

  1. Create one visible label element in HTML.
  2. Use a ::after pseudo-element to show the current status text.
  3. Add three buttons for Pending, Processing, and Done.
  4. Update the pseudo-element text using JavaScript.
  5. Do not try to directly select ::after in JavaScript.
Take Quiz

Keep learning

Related questions

View more