skip to Main Content

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>

2

Answers


  1. The issue is due to specificity. The new --background: blue rule is being applied, but it’s overridden by the existing rule:

    enter image description here

    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>
    Login or Signup to reply.
  2. 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>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search