Selecting and manipulating CSS pseudo-elements such as ::before and ::after using jQuery

Selecting and manipulating CSS pseudo-elements such as ::before and ::after using jQuery

Is there any way to select/manipulate CSS pseudo-elements such as ::before and ::after (and the old version with one semi-colon) using jQuery?
For example, my stylesheet has the following rule:
.span::after{ content:’foo’ }

How can I change ‘foo’ to ‘bar’ using jQuery?

Solutions/Answers:

Solution 1:

You could also pass the content to the pseudo element with a data attribute and then use jQuery to manipulate that:

In HTML:

<span>foo</span>

In jQuery:

$('span').hover(function(){
    $(this).attr('data-content','bar');
});

In CSS:

span:after {
    content: attr(data-content) ' any other text you may want';
}

If you want to prevent the ‘other text’ from showing up, you could combine this with seucolega’s solution like this:

In HTML:

<span>foo</span>

In jQuery:

$('span').hover(function(){
    $(this).addClass('change').attr('data-content','bar');
});

In CSS:

span.change:after {
    content: attr(data-content) ' any other text you may want';
}

Solution 2:

You’d think this would be a simple question to answer, with everything else that jQuery can do. Unfortunately, the problem comes down to a technical issue: css :after and :before rules aren’t part of the DOM, and therefore can’t be altered using jQuery’s DOM methods.

There are ways to manipulate these elements using JavaScript and/or CSS workarounds; which one you use depends on your exact requirements.


I’m going to start with what’s widely considered the “best” approach:

1) Add/remove a predetermined class

In this approach, you’ve already created a class in your CSS with a different :after or :before style. Place this “new” class later in your stylesheet to make sure it overrides:

p:before {
    content: "foo";
}
p.special:before {
    content: "bar";
}

Then you can easily add or remove this class using jQuery (or vanilla JavaScript):

$('p').on('click', function() {
    $(this).toggleClass('special');
});
    $('p').on('click', function() {
      $(this).toggleClass('special');
    });
p:before {
  content: "foo";
  color: red;
  cursor: pointer;
}
p.special:before {
  content: "bar";
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<p>This is a paragraph.</p>
<p>This is another paragraph.</p>
  • Pros: Easy to implement with jQuery; quickly alters multiple styles at once; enforces separation of concerns (isolating your CSS and JS from your HTML)
  • Cons: CSS must be pre-written, so the content of :before or :after isn’t completely dynamic

2) Add new styles directly to the document’s stylesheet

It’s possible to use JavaScript to add styles directly to the document stylesheet, including :after and :before styles. jQuery doesn’t provide a convenient shortcut, but fortunately the JS isn’t that complicated:

var str = "bar";
document.styleSheets[0].addRule('p.special:before','content: "'+str+'";');
var str = "bar";
document.styleSheets[0].addRule('p.special:before', 'content: "' + str + '";');
p:before {
  content: "foo";
  color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<p class="special">This is a paragraph</p>
<p>This is another paragraph</p>

.addRule() and the related .insertRule() methods are fairly well-supported today.

As a variation, you can also use jQuery to add an entirely new stylesheet to the document, but the necessary code isn’t any cleaner:

var str = "bar";
$('<style>p.special:before{content:"'+str+'"}</style>').appendTo('head');
var str = "bar";
$('<style>p.special:before{content:"' + str + '"}</style>').appendTo('head');
p:before {
  content: "foo";
  color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<p class="special">This is a paragraph</p>
<p>This is another paragraph</p>

If we’re talking about “manipulating” the values, not just adding to them, we can also read the existing :after or :before styles using a different approach:

var str = window.getComputedStyle(document.querySelector('p'), ':before') 
           .getPropertyValue('content');
var str = window.getComputedStyle($('p')[0], ':before').getPropertyValue('content');
console.log(str);

document.styleSheets[0].addRule('p.special:before', 'content: "' + str+str + '";');
p:before {
    content:"foo";
    color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<p class="special">This is a paragraph</p>
<p>This is another paragraph</p>

We can replace document.querySelector('p') with $('p')[0] when using jQuery, for slightly shorter code.

  • Pros: any string can be dynamically inserted into the style
  • Cons: original styles aren’t altered, just overridden; repeated (ab)use can make the DOM grow arbitrarily large

3) Alter a different DOM attribute

You can also to use attr() in your CSS to read a particular DOM attribute. (If a browser supports :before, it supports attr() as well.) By combining this with content: in some carefully-prepared CSS, we can change the content (but not other properties, like margin or color) of :before and :after dynamically:

p:before {
    content: attr(data-before);
    color: red;
    cursor: pointer;
}

JS:

$('p').on('click', function () {
    $(this).attr('data-before','bar');
});
$('p').on('click', function () {
    $(this).attr('data-before','bar');
});
p:before {
    content: attr(data-before);
    color: red;
    cursor: pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<p>This is a paragraph.</p>
<p>This is another paragraph.</p>

This can be combined with the second technique if the CSS can’t be prepared ahead of time:

var str = "bar";

document.styleSheets[0].addRule('p:before', 'content: attr(data-before);');

$('p').on('click', function () {
    $(this).attr('data-before', str);
});
var str = "bar";
document.styleSheets[0].addRule('p:before', 'content: attr(data-before) !important;');

$('p').on('click', function() {
  $(this).attr('data-before', str);
});
p:before {
  content: "foo";
  color: red;
  cursor: pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<p>This is a paragraph.</p>
<p>This is another paragraph.</p>
  • Pros: Doesn’t create endless extra styles
  • Cons: attr in CSS can only apply to content strings, not URLs or RGB colors

Solution 3:

Although they are rendered by browsers through CSS as if they were like other real DOM elements, pseudo-elements themselves are not part of the DOM, because pseudo-elements, as the name implies, are not real elements, and therefore you can’t select and manipulate them directly with jQuery (or any JavaScript APIs for that matter, not even the Selectors API). This applies to any pseudo-elements whose styles you’re trying to modify with a script, and not just ::before and ::after.

You can only access pseudo-element styles directly at runtime via the CSSOM (think window.getComputedStyle()), which is not exposed by jQuery beyond .css(), a method that doesn’t support pseudo-elements either.

You can always find other ways around it, though, for example:

  • Applying the styles to the pseudo-elements of one or more arbitrary classes, then toggling between classes (see seucolega’s answer for a quick example) — this is the idiomatic way as it makes use of simple selectors (which pseudo-elements are not) to distinguish between elements and element states, the way they’re intended to be used

  • Manipulating the styles being applied to said pseudo-elements, by altering the document stylesheet, which is much more of a hack

Solution 4:

You can’t select pseudo elements in jQuery because they are not part of DOM.
But you can add an specific class to the father element and control its pseudo elements in CSS.

EXAMPLE

In jQuery:

<script type="text/javascript">
    $('span').addClass('change');
</script>

In CSS:

span.change:after { content: 'bar' }

Solution 5:

In the line of what Christian suggests, you could also do:

$('head').append("<style>.span::after{ content:'bar' }</style>");

Solution 6:

We can also rely on custom properties (aka CSS variables) in order to manipulate pseudo-element. We can read in the specification that:

Custom properties are ordinary properties, so they can be declared on
any element, are resolved with the normal inheritance and cascade
rules, can be made conditional with @media and other conditional rules, can be used in HTML’s style attribute, can be read or set using the CSSOM, etc.

Considering this, the idea is to define the custom property within the element and the pseudo-element will simply inherit it; thus we can easily modify it.

Please note that CSS variables might not be available in all browsers you consider relevant (e.g. IE 11): https://caniuse.com/#feat=css-variables

1) Using inline style:

.box:before {
  content:var(--content,"I am a before element");
  color:var(--color, red);
  font-size:25px;
}
<div class="box"></div>
<div class="box" style="--color:blue;--content:'I am a blue element'"></div>
<div class="box" style="--color:black"></div>
<div class="box" style="--color:#f0f;--content:'another element'"></div>

2) Using CSS and classes

.box:before {
  content:var(--content,"I am a before element");
  color:var(--color, red);
  font-size:25px;
}

.blue {
  --color:blue;
  --content:'I am a blue element';
}
.black {
  --color:black;
}
<div class="box"></div>
<div class="box black" ></div>
<div class="box blue"></div>

3) Using javascript

document.querySelectorAll('.box')[0].style.setProperty("--color", "blue");
document.querySelectorAll('.box')[1].style.setProperty("--content", "'I am another element'");
.box:before {
  content:var(--content,"I am a before element");
  color:var(--color, red);
  font-size:25px;
}
<div class="box"></div>
<div class="box"></div>

4) Using jQuery

$('.box').eq(0).css("--color", "blue");
/* the css() function with custom properties works only with a jQuery vesion >= 3.x
   with older version we can use style attribute to set the value. Simply pay
   attention if you already have inline style defined! 
*/
$('.box').eq(1).attr("style","--color:#f0f");
.box:before {
  content:"I am a before element";
  color:var(--color, red);
  font-size:25px;
}
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>

It can also be used with complex values:

.box {
  --c:"content";
  --b:linear-gradient(red,blue);
  --s:20px;
  --p:0 15px;
}

.box:before {
  content: var(--c);
  background:var(--b);
  color:#fff;
  font-size: calc(2 * var(--s) + 5px);
  padding:var(--p);
}
<div class="box"></div>

You may notice that I am considering the syntax var(--c,value) where value is the default value and also called the fallback value.

From the same specification we can read:

The value of a custom property can be substituted into the value of another property with the var() function. The syntax of var() is:

var() = var( <custom-property-name> [, <declaration-value> ]? )

The first argument to the function is the name of the custom property to be substituted. The second argument to the function, if provided, is a fallback value, which is used as the substitution value when the referenced custom property is invalid.

And later:

To substitute a var() in a property’s value:

  1. If the custom property named by the first argument to the var() function is animation-tainted, and the var() function is being used in the animation property or one of its longhands, treat the custom property as having its initial value for the rest of this algorithm.
  2. If the value of the custom property named by the first argument to the var() function is anything but the initial value, replace the var() function by the value of the corresponding custom property.
  3. Otherwise, if the var() function has a fallback value as its second argument, replace the var() function by the fallback value. If there are any var() references in the fallback, substitute them as well.
  4. Otherwise, the property containing the var() function is invalid at computed-value time.

If we don’t set the custom property OR we set it to initial OR it contains an invalid value then the fallback value will be used. The use of initial can be helpful in case we want to reset a custom property to its default value.

Related

How to store inherit value inside a CSS custom property (aka CSS variables)?

CSS custom properties (variables) for box model