75

Is it possible to change a CSS pseudo-element style via JavaScript?

For example, I want to dynamically set the color of the scrollbar like so:

document.querySelector("#editor::-webkit-scrollbar-thumb:vertical").style.background = localStorage.getItem("Color");

and I also want to be able to tell the scrollbar to hide like so:

document.querySelector("#editor::-webkit-scrollbar").style.visibility = "hidden";

Both of these scripts, however, return:

Uncaught TypeError: Cannot read property 'style' of null

Is there some other way of going about this?
Cross-browser interoperability is not important, I just need it to work in webkit browsers.

Nakilon
  • 32,203
  • 13
  • 95
  • 132
木川 炎星
  • 3,433
  • 9
  • 35
  • 49

8 Answers8

41

If you're comfortable with some graceful degradation in older browsers you can use CSS Vars. Definitely the easiest of the methods I've seen here and elsewhere.

So in your CSS you can write:

#editor {
  --scrollbar-background: #ccc;
}

#editor::-webkit-scrollbar-thumb:vertical {
  /* Fallback */
  background-color: #ccc;
  /* Dynamic value */
  background-color: var(--scrollbar-background);
}

Then in your JS you can manipulate that value on the #editor element:

document.getElementById("#editor").style.setProperty('--scrollbar-background', localStorage.getItem("Color"));

Lots of other examples of manipulating CSS vars with JS here: https://eager.io/blog/communicating-between-javascript-and-css-with-css-variables/

Wes Ruvalcaba
  • 546
  • 1
  • 4
  • 3
  • Right, it won't work in older browsers (https://caniuse.com/#search=css%20variables), but it will go to the fallback. – Wes Ruvalcaba Sep 03 '18 at 14:36
  • 1
    This is the best answer if you need to target a specific element. For IE compatibility, include [css-vars-ponyfill](https://www.npmjs.com/package/css-vars-ponyfill) and run ```cssVars({ variables: { 'scrollbar-background': '#color' } })``` when updating. – Fabian von Ellerts Feb 18 '19 at 14:09
  • saved my time. thanks. – Kathrine Hanson Mar 03 '20 at 15:48
  • I was already using css variables to change colors of scrollbar, but it was just updating all the internal scrollbars but not the main one (which is on whole page). So I did this by inserting new css rule, using insertRule() method. Hence, when the user selects a new theme it does two things delete the rule(if any) and insert the rule with new colors. – Muhammad Ashhar Hasan Apr 14 '20 at 11:21
  • 2
    That's really clever. Excellent solution. – Rounin Apr 29 '20 at 14:18
  • This should be the accepted answer. Works like a charm :) – Rafael Jul 06 '20 at 21:23
29

To edit an existing one which you don't have a direct reference to requires iterating all style sheets on the page and then iterating all rules in each and then string matching the selector.

Here's a reference to a method I posted for adding new CSS for pseudo-elements, the easy version where you're setting from js

Javascript set CSS :after styles

var addRule = (function (style) {
    var sheet = document.head.appendChild(style).sheet;
    return function (selector, css) {
        var propText = typeof css === "string" ? css : Object.keys(css).map(function (p) {
            return p + ":" + (p === "content" ? "'" + css[p] + "'" : css[p]);
        }).join(";");
        sheet.insertRule(selector + "{" + propText + "}", sheet.cssRules.length);
    };
})(document.createElement("style"));

addRule("p:before", {
    display: "block",
    width: "100px",
    height: "100px",
    background: "red",
    "border-radius": "50%",
    content: "''"
});

sheet.insertRule returns the index of the new rule which you can use to get a reference to it for it which can be used later to edit it.

Community
  • 1
  • 1
  • 1
    This is great. To work with the rule afterward (and remove it) I had to edit it to actually return the result of sheet.insertRule(). I also had it return the newly added style sheet so that .removeRule could be called. – Scott Smith Dec 31 '12 at 18:21
  • 1
    I've added a check for `content` and even a fallback if the input is already a string. – yckart May 15 '13 at 16:13
  • I'm worried that this solution over-complicates things, violating the KISS principle and the convention of keeping styles in style sheets. – Chris Fritz Oct 16 '13 at 13:59
  • 1
    This is particularly useful for writing interfaces that allow users to visualise CSS changes to existing widget frameworks (eg jquery-ui, jquery-mobile...) that make heavy use of pseudo elements for icons. Upvoted! – Escher Mar 25 '16 at 09:44
  • As noted (by someone else) in your linked post, I can't get the `content` property to work this way. Which happens to be my entire reason for needing it, so that's tough. – Jonathan Tuzman Feb 20 '20 at 21:34
20

EDIT: There is technically a way of directly changing CSS pseudo-element styles via JavaScript, as this answer describes, but the method provided here is preferable.

The closest to changing the style of a pseudo-element in JavaScript is adding and removing classes, then using the pseudo-element with those classes. An example to hide the scrollbar:

CSS

.hidden-scrollbar::-webkit-scrollbar {
   visibility: hidden;
}

JavaScript

document.getElementById("editor").classList.add('hidden-scrollbar');

To later remove the same class, you could use:

document.getElementById("editor").classList.remove('hidden-scrollbar');
Josh Habdas
  • 6,370
  • 2
  • 53
  • 55
Chris Fritz
  • 1,746
  • 1
  • 18
  • 23
11

I changed the background of the ::selection pseudo-element by using CSS custom properties doing the following:

/*CSS Part*/
:root {
    --selection-background: #000000;
}
#editor::selection {
    background: var(--selection-background);
}

//JavaScript Part
document.documentElement.style.setProperty("--selection-background", "#A4CDFF");
TazExprez
  • 121
  • 4
  • 6
  • The only normal answer I found for adding styles to pseudo-elements since yesterday! I'm setting --nav-opacity-level: 0; in scss and changing it's value to 1 in js. When the button is clicked, the js function sets the value to 1. But, the opacity is not getting set 0 as I'm initially setting it, but it's getting 1. Would really appreciate help :) – Nodirabegimxonoyim Oct 04 '20 at 07:45
6

You can't apply styles to psuedo-elements in JavaScript.

You can, however, append a <style> tag to the head of your document (or have a placeholding <style id='mystyles'> and change its content), which adjusts the styles. (This would work better than loading in another stylesheet, because embedded <style> tags have higher precedence than <link>'d ones, making sure you don't get cascading problems.

Alternatively, you could use different class names and have them defined with different psuedo-element styles in the original stylesheet.

Niet the Dark Absol
  • 301,028
  • 70
  • 427
  • 540
4

I posted a question similar to, but not completely like, this question.

I found a way to retrieve and change styles for pseudo elements and asked what people thought of the method.

My question is at Retrieving or changing css rules for pseudo elements

Basically, you can get a style via a statement such as:

document.styleSheets[0].cssRules[0].style.backgroundColor

And change one with :

document.styleSheets[0].cssRules[0].style.backgroundColor = newColor;

You, of course, have to change the stylesheet and cssRules index. Read my question and the comments it drew.

I've found this works for pseudo elements as well as "regular" element/styles.

Community
  • 1
  • 1
SimonT
  • 376
  • 2
  • 15
2

An old question, but one I came across when try to dynamically change the colour of the content of an element's :before selector.

The simplest solution I can think of is to use CSS variables, a solution not applicable when the question was asked:

"#editor::-webkit-scrollbar-thumb:vertical {
    background: --editorScrollbarClr
}

Change the value in JavaScript:

document.body.style.setProperty(
    '--editorScrollbarClr', 
     localStorage.getItem("Color")
);

The same can be done for other properties.

Lee Goddard
  • 8,401
  • 3
  • 41
  • 52
0

Looks like querySelector won't work with pseudo-classes/pseudo-elements, at least not those. The only thing I can think of is to dynamically add a stylesheet (or change an existing one) to do what you need.

Lots of good examples here: How do I load css rules dynamically in Webkit (Safari/Chrome)?

Community
  • 1
  • 1
Hemlock
  • 5,844
  • 1
  • 23
  • 35
  • 2
    `querySelector` will not work with **any** pseudo-elements, because they're pseudo-elements, not actual elements that exist in the DOM. –  Oct 12 '14 at 06:25