I’m using new CSSStyleSheet()
to create a custom stylesheet for a specific document where the styles depend on the content.
Within that stylesheet, I add CSS variables with the root selector. Elements in the project will then be selected by an ID and get specific rules that use those variables. The issue is, that I do not know the IDs before, and as such the styles and the selectors have to be created dynamically.
So far it works flawlessly.
The issue that I facing now is that during some specific events, the value of the CSS variables should change. I tried changing it by using insertRule()
with and without index. I also tried replace()
and replaceSync()
. Nothing achieved the desired result.
The question now is, how can I change the root
for this stylesheet?
const CSS = new CSSStyleSheet();
const rootRule = `:root{
--background: red;
}`;
CSS.insertRule(rootRule, 0);
const bodyRule = `body {
background: var(--background);
}`
CSS.insertRule(bodyRule, 1);
document.adoptedStyleSheets = [CSS];
// change color
BUTTON.addEventListener('click', function() {
console.log('button clicked');
const newRule = `:root {
--background: blue;
}`
CSS.insertRule(newRule, 0);
})
<button id="BUTTON">Change background-color</button>
0
You are inserting the rule to the CSSStyleSheet correctly (you can tell by adding console.log(CSS.cssRules[0].cssText);
to the button click to check whether it’s inserted). The problem you are having is that your code is being inserted at index 0
, meaning before your existing rootRule
. This is the default position, but you also specify it with CSS.insertRule(newRule, 0);
. And because rules for specific selectors in CSS get overwritten by later rules of the same selectors, the rootRule
is still the one getting applied.
Change it to CSS.insertRule(newRule, 2);
and you will see it work:
const CSS = new CSSStyleSheet();
const rootRule = `:root{
--background: red;
}`;
CSS.insertRule(rootRule, 0);
const bodyRule = `body {
background: var(--background);
}`
CSS.insertRule(bodyRule, 1);
document.adoptedStyleSheets = [CSS];
// change color
BUTTON.addEventListener('click', function() {
console.log('button clicked');
const newRule = `:root {
--background: blue;
}`
CSS.insertRule(newRule, 2);
})
<button id="BUTTON">Change background-color</button>
The issue is due to specificity. The new --background: blue
rule is being applied, but it’s overruled by the existing rule:
The quick and dirty fix to this would be to use !important
, although this is not ideal:
const newRule = `:root {
--background: blue !important;
}
A better aproach would be to remove the original rule, before adding the new one. This can be done using deleteRule()
. Here’s a working example:
const CSS = new CSSStyleSheet();
const rootRule = `:root{
--background: red;
}`;
CSS.insertRule(rootRule, 0);
const bodyRule = `body {
background: var(--background);
}`
CSS.insertRule(bodyRule, 1);
document.adoptedStyleSheets = [CSS];
BUTTON.addEventListener('click', function() {
CSS.deleteRule(0); // delete the original :root rule
const newRule = `:root {
--background: blue;
}`
CSS.insertRule(newRule, 0);
})
<button id="BUTTON">Change background-color</button>
1