37

I am using css root variables in my project for dynamically changing the colors of all elements anytime.

My css looks like

:root{
  --primaryColor:aliceblue;
  --secondaryColor:blue;
  --errorColor:#cc2511;
}

used in css

.contentCss {
  background-color: var(--primaryColor);
} 

I can access variable in javascript as follows to change value dynamically

document.documentElement.style.setProperty('--primaryColor', 'green');

It works fine. I want to get all variables inside an array and according to that change the values of each variable dynamically.

9 Answers 9

39

this script will return an array of root variables in all stylesheets, served from the domain. Out of domain stylesheets are not accessible due to CORS policies.

Array.from(document.styleSheets)
  .filter(
    sheet =>
      sheet.href === null || sheet.href.startsWith(window.location.origin)
  )
  .reduce(
    (acc, sheet) =>
      (acc = [
        ...acc,
        ...Array.from(sheet.cssRules).reduce(
          (def, rule) =>
            (def =
              rule.selectorText === ":root"
                ? [
                    ...def,
                    ...Array.from(rule.style).filter(name =>
                      name.startsWith("--")
                    )
                  ]
                : def),
          []
        )
      ]),
    []
  );

Note: a root: rule in a lower order stylesheet will override a parent root rule.

Sign up to request clarification or add additional context in comments.

Comments

8

I needed a similar solution today. Here's a quick one on codepen.

// could pass in an array of specific stylesheets for optimization
function getAllCSSVariableNames(styleSheets = document.styleSheets){
   var cssVars = [];
   // loop each stylesheet
   for(var i = 0; i < styleSheets.length; i++){
      // loop stylesheet's cssRules
      try{ // try/catch used because 'hasOwnProperty' doesn't work
         for( var j = 0; j < styleSheets[i].cssRules.length; j++){
            try{
               // loop stylesheet's cssRules' style (property names)
               for(var k = 0; k < styleSheets[i].cssRules[j].style.length; k++){
                  let name = styleSheets[i].cssRules[j].style[k];
                  // test name for css variable signiture and uniqueness
                  if(name.startsWith('--') && cssVars.indexOf(name) == -1){
                     cssVars.push(name);
                  }
               }
            } catch (error) {}
         }
      } catch (error) {}
   }
   return cssVars;
}

function getElementCSSVariables (allCSSVars, element = document.body, pseudo){
   var elStyles = window.getComputedStyle(element, pseudo);
   var cssVars = {};
   for(var i = 0; i < allCSSVars.length; i++){
      let key = allCSSVars[i];
      let value = elStyles.getPropertyValue(key)
      if(value){cssVars[key] = value;}
   }
   return cssVars;
}

var cssVars = getAllCSSVariableNames();
console.log(':root variables', getElementCSSVariables(cssVars, document.documentElement));

Comments

4

You can declare an associative array with the keys as the node property with their values, then use a function to set your theme:

var primaryColor = document.documentElement.style.getPropertyValue('--primaryColor');
var secondaryColor = document.documentElement.style.getPropertyValue('--secondaryColor');
var errorColor = document.documentElement.style.getPropertyValue('--errorColor');

var themeColors = {}
themeColors["--primaryColor"] = primaryColor;
themeColors["--secondaryColor"] = secondaryColor;
themeColors["--errorColor"] = errorColor;

function setTheme(theme) {
     for (key in theme) {
        let color = theme[key];
        document.documentElement.style.setProperty(key, color);
   }
 }

A working example I used with Atom and Bootstrap:

        var backgroundColor = document.documentElement.style.getPropertyValue('--blue');
        backgroundColor = "#dc3545";

        function setTheme(theme) {
          for (key in theme) {
            let color = theme[key];
            document.documentElement.style.setProperty(key, color);
          }
        }

        var theme = {}
        theme["--blue"] = backgroundColor;

        setTheme(theme);

Override default colors

>>Edit<<

Nadeem clarified the question a bit better with a comment below, unfortunately however I've learned that :root can be accessed with get Window.getComputedStyle() however this doesn't return CSS Variable declarations.

A hack around this is just to read the css file, parse it for variables and stuff them into an associative array, but even this assumes you know where to get that css file...

        //an associative array that will hold our values
        var cssVars = {};

        var request = new XMLHttpRequest();
        request.open('GET', './css/style.css', true);

        request.onload = function() {
          if (request.status >= 200 && request.status < 400) {
              //Get all CSS Variables in the document
              var matches = request.responseText.match(/(--)\w.+;/gi);

              //Get all CSS Variables in the document
              for(let match in matches) {
                  var property = matches[match];
                  //split the Variable name from its value
                  let splitprop = property.split(":")

                  //turn the value into a string
                  let value = splitprop[1].toString()

                  cssVars[splitprop[0]] = value.slice(0, -1); //remove ;
              }

              // console.log(cssVars);
              // > Object {--primaryColor: "aliceblue", --secondaryColor: "blue", --errorColor: "#cc2511"}

              // console.log(Object.keys(cssVars));
              // > ["--primaryColor", "--secondaryColor", "--errorColor" ]

              setTheme(cssVars)

            } else {
              // We reached our target server, but it returned an error
            }
          };

          request.onerror = function() {
            console.log("There was a connection error");
          };

          request.send();



          function setTheme(theme) {
            var keys = Object.keys(theme)

            for (key in keys) {
              let prop = keys[key]
              let color = theme[keys[key]];

              console.log(prop, color);
              // --primaryColor aliceblue etc...
            }
          }

1 Comment

suppose i have 200 variables in css so i have to write this line var primaryColor = document.documentElement.style.getPropertyValue('--primaryColor'); 200 times. I want it dynamically.
2

If you know all your variable will be placed inside the :root and it's the first declaration in your first CSS file, you can try something like this and you will get all the variable inside an Object:

var declaration = document.styleSheets[0].cssRules[0];
var allVar = declaration.style.cssText.split(";");

var result = {}
for (var i = 0; i < allVar.length; i++) {
  var a = allVar[i].split(':');
  if (a[0] !== "")
    result[a[0].trim()] = a[1].trim();
}

console.log(result);
var keys = Object.keys(result);
console.log(keys);

//we change the first variable
document.documentElement.style.setProperty(keys[0], 'green');

//we change the variable  --secondary-color
document.documentElement.style.setProperty(keys[keys.indexOf("--secondary-color")], 'red');
:root {
  --primary-color: aliceblue;
  --secondary-color: blue;
  --error-color: #cc2511
}

p {
  font-size: 25px;
  color: var(--primary-color);
  border:1px solid var(--secondary-color)
}
<p>Some text</p>

5 Comments

interesting solution, quite clean, I considered using .cssRules[0] but I found that only the first :root element was read. if you have multiple ones from different sources, say local and CDN, only the first will be read.
@RLoniello yes as you may notice i used 0 index and if you follow them you will see that i took the first CSS file and the first rule inside this CSS file ... so you can do the same with the other, the only thing you have to know is the order and indexes ... this is the small drawback :) but if you know your CSS files it won't be an issue ;)
@RLoniello use console.log with document.styleSheets to see what inside it and you will understand ;)
yeah I get it :) Just watch out for the loading order of your CDN stylesheets. :P
@RLoniello yes if you get the orders it easy ... but if not you can make the code more complicated where you loop through all the styles to get the variable .. so almost the same thing i have done but with all the style .. you simply need to add an if else to check that the style is a varialbe (it should start with --)
2

Here is another in typescript, where I continued work that RLoniello mentioned. It also outputs it as a JS object, that is converts --font-family: "Verdana" to fontFamily: "Verdana".

const CssKeyToJsKey = (key: string) =>
    key.replace('--', '').replace(/-./g, (x) => x.toUpperCase()[1]);

const getAllCSSVariableNames = (styleSheets: StyleSheetList = document.styleSheets) => {
    const cssVars = [];

    Array.from(styleSheets).forEach((styleSheet) => {
        Array.from(styleSheet.cssRules).forEach((rule) => {
            if (!rule || !rule['style']) {
                return;
            }

            Array.from(rule['style']).forEach((style: string) => {
                if (style.startsWith('--') && cssVars.indexOf(style) == -1) {
                    cssVars.push(style);
                }
            });
        });
    });

    return cssVars;
};

const getElementCSSVariables = (
    allCSSVars: Array<string>,
    element: HTMLElement = document.body,
    pseudo: string | undefined = ''
) => {
    const elStyles = window.getComputedStyle(element, pseudo);
    const cssVars = {};

    allCSSVars.forEach((key) => {
        const value = elStyles.getPropertyValue(key);

        if (value) {
            cssVars[CssKeyToJsKey(key)] = value;
        }
    });

    return cssVars;
};

export const getAllCSSVariables = (): Record<string, string> => {
    const cssVars = getAllCSSVariableNames();

    return getElementCSSVariables(cssVars, document.documentElement);
};

Comments

2

Here's a functional approach to get all :root CSS variables that won't throw type errors if you're using a stricter TypeScript configuration:

const rootCssVariables: string[] = Array.from(document.styleSheets)
  .flatMap((styleSheet: CSSStyleSheet) => Array.from(styleSheet.cssRules))
  .filter(
    (cssRule: CSSRule): cssRule is CSSStyleRule =>
      cssRule instanceof CSSStyleRule && cssRule.selectorText === ':root',
  )
  .flatMap((cssRule: CSSStyleRule) => Array.from(cssRule.style))
  .filter((style: string) => style.startsWith('--'))

console.log(rootCssVariables)
// e.g. ['--background-color', '--text-color', etc...]

1 Comment

Probably the cleanest and most modern answer here.
1

Based on @kitschpatrol's answer, here's some code to get :root variables and their values as an object.

Adds catching access exceptions, e.g. Google Font stylesheets (Uncaught DOMException: Failed to read the 'cssRules' property from 'CSSStyleSheet': Cannot access rules).

export const getTheme = () => {
  const rootStyles = window.getComputedStyle(document.documentElement);
  return Object.fromEntries(
    Array.from(document.styleSheets)
      .flatMap((styleSheet) => {
        try {
          return Array.from(styleSheet.cssRules);
        } catch (error) {
          return [];
        }
      })
      .filter((cssRule) => cssRule instanceof CSSStyleRule)
      .flatMap((cssRule) => Array.from(cssRule.style))
      .filter((style) => style.startsWith("--"))
      .map((variable) => [variable, rootStyles.getPropertyValue(variable)]),
  );
};

Example results:

{
  "--accent": "hsl(342, 50%, 50%)",
  "--dark-gray": "#606060",
  "--gray": "#b0b0b0",
  "--light-gray": "#e0e0e0",
  "--success": "#10b981",
  "--warning": "#f59e0b",
  "--error": "#f43f5e",
  "--sans": "\"Poppins\", sans-serif",
  "--mono": "\"IBM Plex Mono\", monospace",
}

Note: If you already know the variable name and just want to get it, it's sufficient to do getComputedStyle(someElement).getPropertyValue(--some-css-var). Unfortunately getComputedStyle doesn't provide any way to iterate over variable properties, only static properties e.g. display.

If you wanted to get all variables available on a particular element, I believe you could remove cssRule.selectorText === ":root" and do getComputedStyle on the element instead of document.documentElement (root).

If you want this to be reactive, just re-run the func whenever you know these variables will change. For example, if you have a dark mode toggle and want to write a React hook:

export const useTheme = () => {
  const [theme, setTheme] = useState<Record<`--${string}`, string>>({});

  // when some kind of reactive dark mode variable changes, e.g. a Jotai atom
  useEffect(() => {
    setTheme(getTheme());
  }, [isDarkMode]);

  // or e.g. listen for a data-dark html attribute to change with a MutationObserver util hook
  useMutationObserver(() => setTheme(getTheme()), document.documentElement, { attributes: true, attributeFilter: ["data-dark"] });

  return theme;
};

Comments

0

Map to a named Object without using .getComputedStyle()

/* Retrieve all --root CSS variables
*  rules into a named Object
*  Without using getComputedStyle (read string only)
*/
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.split(": "))
   .filter(a => a[0] !== " ")
   .reduce((a, v) => ({ ...a, [v[0].slice(1)] : v[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%);
}

Comments

-1

If you want to get var color name only then try

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.