Daedalus
Daedalus

Reputation: 4262

How to change CSS :root color variables in JavaScript

Alright, I'm creating a system for my webpage that allows users to change the theme. How I want to accomplish this is by having all the colors as variables, and the colors are set in the :root part of the CSS.

What I want to do is change those colors via JavaScript. I looked up how to do it, but nothing that I attempted actually worked properly. Here's my current code:

CSS:

:root {
  --main-color: #317EEB;
  --hover-color: #2764BA;
  --body-color: #E0E0E0;
  --box-color: white;
}

JS:

(Code to set the theme, it's ran on the click of a button) - I didn't bother adding the :root change to the other 2 themes since it doesn't work on the Dark theme

function setTheme(theme) {
  if (theme == 'Dark') {
    localStorage.setItem('panelTheme', theme);
    $('#current-theme').text(theme);
    $(':root').css('--main-color', '#000000');
  }
  if (theme == 'Blue') {
    localStorage.setItem('panelTheme',  'Blue');
    $('#current-theme').text('Blue');
    alert("Blue");
  }
  if (theme == 'Green') {
    localStorage.setItem('panelTheme', 'Green');
    $('#current-theme').text('Green');
    alert("Green");
  }
}

(Code that is ran when the html is loaded)

function loadTheme() {
  //Add this to body onload, gets the current theme. If panelTheme is empty, defaults to blue.
  if (localStorage.getItem('panelTheme') == '') {
    setTheme('Blue');
  } else {
    setTheme(localStorage.getItem('panelTheme'));
    $('#current-theme').text(localStorage.getItem('panelTheme'));
  }
}

It shows the alert, but does not actually change anything. Can someone point me in the right direction?

Upvotes: 137

Views: 160000

Answers (13)

Pisandelli
Pisandelli

Reputation: 403

We can use the registerProperty() using the @property CSS at-rule.

window.CSS.registerProperty({
  name: "--my-color",
  syntax: "<color>",
  inherits: false,
  initialValue: "#c0ffee",
});

Upvotes: 0

horiatu
horiatu

Reputation: 392

private switchDarkMode = (value: boolean) => {
    let darkStyles = document.getElementById("darkStyles");
    if (!darkStyles) {
        darkStyles = domConstruct.create(
            "style",
            {
                id: "darkStyles",
            },
            document.head
        );
    }
    darkStyles.innerHTML = `
:root {
    --white: ${!value ? "white" : "black"};
};
`;
    }

Upvotes: 0

DoRyu Dracken
DoRyu Dracken

Reputation: 144

If you want to target the stylesheet you can do this:

    const styleSheet = document.styleSheets[0]

    const ruleIndex = [...styleSheet.cssRules].findIndex(rule => rule.cssText.includes('--sidebar:'))
    const ruleText = styleSheet.cssRules[ruleIndex].cssText
    let newRule: string

    if (isOpen()) newRule = ruleText.replace('--sidebar: var(--sidebar-open)', '--sidebar: var(--sidebar-closed)')
    else newRule = ruleText.replace('--sidebar: var(--sidebar-closed)', '--sidebar: var(--sidebar-open)')

    styleSheet.deleteRule(ruleIndex)
    styleSheet.insertRule(newRule, ruleIndex)

Just be sure that the string to replace is exactly the same as in your stylesheet, including spaces;

CSS:

:root {
    --sidebar: var(--sidebar-open);
    --sidebar-open: 15rem;
    --sidebar-closed: 2rem;
}

Text to teplace:

Works:

--sidebar: var(--sidebar-open)

Doesn't works (One space is missing):

--sidebar:var(--sidebar-open)

The answer from @Michael works, but it adds a new rule every time you switch the style.

Upvotes: 0

serge512
serge512

Reputation: 31

As per the answer of DEV Tiago França, which worked on my case. here is why it works like a charm:

/* Root original style */
:root {
    --hl-color-green: green;
}
/* Using * overrides the variable for all html tags where it is actually used */
* {
    --hl-color-green: #0cc120;
}

Upvotes: 0

DEV Tiago Fran&#231;a
DEV Tiago Fran&#231;a

Reputation: 1696

/*My style*/
:root {
    --hl-color-green: green;
}

/*My update*/
* {
 --hl-color-green: #0cc120;
}

Upvotes: -2

Pakpoom Tiwakornkit
Pakpoom Tiwakornkit

Reputation: 2939

I think this is cleaner and easier to remember. to set/get css variables to/from :root

const root = document.querySelector(':root');

// set css variable
root.style.setProperty('--my-color', 'blue');

// to get css variable from :root
const color = getComputedStyle(root).getPropertyValue('--my-color'); // blue

Example: setting multiple variables all at once

const setVariables = vars => Object.entries(vars).forEach(v => root.style.setProperty(v[0], v[1]));
const myVariables = {
  '--color-primary-50': '#eff6ff',
  '--color-primary-100': '#dbeafe',
  '--color-primary-200': '#bfdbfe',
  '--color-primary-300': '#93c5fd',
  '--color-primary-400': '#60a5fa',
  '--color-primary-500': '#3b82f6',
  '--color-primary-600': '#2563eb',
  '--color-primary-700': '#1d4ed8',
  '--color-primary-800': '#1e40af',
  '--color-primary-900': '#1e3a8a',
};
setVariables(myVariables);

Upvotes: 32

John Skoubourdis
John Skoubourdis

Reputation: 3259

TL;DR

A solution to the problem could be the below code:

const headTag = document.getElementsByTagName('head')[0];
const styleTag = document.createElement("style");

styleTag.innerHTML = `
:root {
    --main-color: #317EEB;
    --hover-color: #2764BA;
    --body-color: #E0E0E0;
    --box-color: white;
}
`;
headTag.appendChild(styleTag);

Explanation:

Although @Daedalus answer with document.documentElement does the job pretty well, a slightly better approach is to add the styling into a <style> HTML tag (solution proposed).

If you add document.documentElement.style then all the CSS variables are added into the html tag and they are not hidden:

CSS style inline to html

On the other hand, with the proposed code:

const headTag = document.getElementsByTagName('head')[0];
const styleTag = document.createElement("style");

styleTag.innerHTML = `
:root {
    --main-color: #317EEB;
    --hover-color: #2764BA;
    --body-color: #E0E0E0;
    --box-color: white;
}
`;
headTag.appendChild(styleTag);

the HTML tag will be cleaner and if you inspect the HTML tag you can also see the :root styling as well.

HTML tag without styles

Upvotes: 6

NVRM
NVRM

Reputation: 13082

Read only, retrieve all CSS --root rules in an array, without using .getComputedStyle().

This may allow to retrieve values before full DOM content load, to create modules that use global root theme variables, but not via CSS. (canvas context...)

/* Retrieve all --root CSS variables
*  rules into an array
*  Without using getComputedStyle (read string only)
*  On this example only the first style-sheet
*  of the document is parsed
*/
console.log(

  [...document.styleSheets[0].rules]
   .map(a => a.cssText.split(" ")[0] === ":root" ?
    a.cssText.split("{")[1].split("}")[0].split("--") : null)
   .filter(a => a !== null)[0]
   .map(a => "--"+a)
   .slice(1)

)
:root {
    --gold: hsl(48,100%,50%);
    --gold-lighter: hsl(48,22%,30%);
    --gold-darker: hsl(45,100%,47%);
    --silver: hsl(210,6%,72%);
    --silver-lighter: hsl(0,0%,26%);
    --silver-darker: hsl(210,3%,61%);
    --bronze: hsl(28,38%,67%);
    --bronze-lighter: hsl(28,13%,27%);
    --bronze-darker: hsl(28,31%,52%);
}

Upvotes: 0

Michael
Michael

Reputation: 864

For those who want to modify the actual style sheet the following works:

var sheet = document.styleSheets[0];
sheet.insertRule(":root{--blue:#4444FF}");

More info at here: https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet/insertRule

Upvotes: 26

gojimmypi
gojimmypi

Reputation: 526

I came here looking how to toggle the :root color-scheme with JavaScript, which sets the browser to dark mode (including the scroll bars) like this:

:root {
    color-scheme: dark;
}

using the @Daedalus answer above, this is how I implemented my dark mode detection from user preference:

    const userPrefersDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
    const preferredTheme = userPrefersDarkMode ? 'dark' : 'light';
    document.documentElement.style.setProperty("color-scheme", preferredTheme);

or with saved toggle:

    const savedTheme = localStorage.getItem('theme');
    if (savedTheme == 'dark') {
        thisTheme = 'light'
    }
    else {
        thisTheme = 'dark'; // the default when never saved is dark
    }
    document.documentElement.style.setProperty("color-scheme", thisTheme);
    localStorage.setItem('theme', thisTheme);

see also the optional meta tag in the header:

<meta name="color-scheme" content="dark light">

Upvotes: 8

iProDev
iProDev

Reputation: 603

To use the values of custom properties in JavaScript, it is just like standard properties.

// get variable from inline style
element.style.getPropertyValue("--my-variable");

// get variable from wherever
getComputedStyle(element).getPropertyValue("--my-variable");

// set variable on inline style
element.style.setProperty("--my-variable", 4);

Upvotes: 8

v2p
v2p

Reputation: 659

old jquery magic still working too

$('#yourStyleTagId').html(':root {' +
    '--your-var: #COLOR;' +
'}');

Upvotes: 2

Daedalus
Daedalus

Reputation: 4262

Thank you @pvg for providing the link. I had to stare at it for a little to understand what was going on, but I finally figured it out.

The magical line I was looking for was this:

document.documentElement.style.setProperty('--your-variable', '#YOURCOLOR');

That did exactly what I wanted it to do, thank you very much!

Upvotes: 261

Related Questions