10

I've set some CSS custom properties in my stylesheet:

:root {
    --bc: #fff;
    --bc-primary: #eee;
    --bc-secondary: #ddd;
}

I can retrieve them individually if I already know the name of the CSS variable like so:

console.log(getComputedStyle(document.body).getPropertyValue('--bc'));

// #fff

But if I wanted to pull a list of CSS variables and their values out, how would that be done?

Asons
  • 84,923
  • 12
  • 110
  • 165
MorganR
  • 619
  • 3
  • 11
  • 22
  • What do you plan on doing with the list of variables once you've found them? –  Aug 18 '17 at 20:19
  • I'm working on a 'theme switcher' that changes between dark and light themes of my app. Simply changing the colours associated with the CSS variables has the effect I'm looking for without having to add/remove classes. I'm sure there are other ways of accomplishing the same goal with better browser support. – MorganR Aug 18 '17 at 21:44

5 Answers5

15

Update:

  • To catch CORS errors, I added !styleSheet.href && to the first if-statement.

One possible solution would be to parse the document.styleSheets, and then split the rules into properties/values

var allCSS = [].slice.call(document.styleSheets)
  .reduce(function(prev, styleSheet) {
    if (!styleSheet.href && styleSheet.cssRules) {
      return prev + [].slice.call(styleSheet.cssRules)
        .reduce(function(prev, cssRule) {        
          if (cssRule.selectorText == ':root') {
            var css = cssRule.cssText.split('{');
            css = css[1].replace('}','').split(';');
            for (var i = 0; i < css.length; i++) {
              var prop = css[i].split(':');
              if (prop.length == 2 && prop[0].indexOf('--') == 1) {
                console.log('Property name: ', prop[0]);
                console.log('Property value:', prop[1]);
              }              
            }
          }
        }, '');
    }
  }, '');
:root {
    --bc: #fff;
    --bc-primary: #eee;
    --bc-secondary: #ddd;
}
Asons
  • 84,923
  • 12
  • 110
  • 165
6

Based on LGSon's answer here is something similar but using map, filter, and flat to make it easier to read line by line. Catches CORS errors based on Exo Flame's answer.

const variables = Array.from(document.styleSheets)
    .filter(styleSheet => {
        try { return styleSheet.cssRules; }
        catch(e) { console.warn(e); }
    })
    .map(styleSheet => Array.from(styleSheet.cssRules))
    .flat()
    .filter(cssRule => cssRule.selectorText === ':root')
    .map(cssRule => cssRule.cssText.split('{')[1].split('}')[0].trim().split(';'))
    .flat()
    .filter(text => text !== "")
    .map(text => text.split(':'))
    .map(parts => ({key: parts[0].trim(), value: parts[1].trim() }))
;

console.log(variables);
:root {
    --foo: #fff;
    --bar: #aaa
}
mvndaai
  • 3,453
  • 3
  • 30
  • 34
  • Here is a demo page I made to use this for theming a webpage https://mvndaai.com/css_variables/ – mvndaai Jan 27 '22 at 22:33
2

MDN has a page demonstrating use of the 'experimental' Element.computedStyleMap method:

for (const [prop, val] of document.documentElement.computedStyleMap()){ 
  console.log( prop, val); 
}

You can see the current support of this at caniuse As of last edit, all major browsers but Firefox support this API.

This CSS Tricks tutorial explicitly answers the question and catches also custom properties in basic style rules, with clear code.

flq
  • 22,247
  • 8
  • 55
  • 77
Lee Goddard
  • 10,680
  • 4
  • 46
  • 63
1

In the new Chrome, reading external style sheets using Javascript might break due to CORS.

Does anyone know a way around this, and if nothing, let this be a warning if you use CDN.

https://stackoverflow.com/a/49994161

This was helpful: https://betterprogramming.pub/how-to-fix-the-failed-to-read-the-cssrules-property-from-cssstylesheet-error-431d84e4a139

Here is a version that filters out remote sheets so you still get your local styles I also used Array.from() to improve readability

    var allCSSVars = Array.from(document.styleSheets)
        .filter((styleSheet) => {
            let isLocal = !styleSheet.href || styleSheet.href.startsWith(window.location.origin)
            if (!isLocal) console.warn("Skipping remote style sheet due to cors: ", styleSheet.href);
            return isLocal;
        })
        .map((styleSheet) => Array.from(styleSheet.cssRules))
        .flat()
        .filter((cssRule) => cssRule.selectorText === ':root')
        .map((cssRule) => cssRule.cssText.split('{')[1].split('}')[0].trim().split(';'))
        .flat()
        .filter((text) => text !== '')
        .map((text) => text.split(':'))
        .map((parts) => {
            return {key: parts[0].trim(), value: parts[1].trim()}
        })
        
        
        
console.log("vars: ", allCSSVars)

//another way not sure whitch is best but the top way is looking promising

allCSSVars = [].slice.call(document.styleSheets)
        .reduce(function (prev, styleSheet) {
            try {
                if (styleSheet.cssRules) {

                    return prev + [].slice.call(styleSheet.cssRules)
                        .reduce(function (prev, cssRule) {

                            if (cssRule.selectorText == ':root') {
                                var css = cssRule.cssText.split('{');
                                css = css[1].replace('}', '').split(';');
                                for (var i = 0; i < css.length; i++) {
                                    var prop = css[i].split(':');
                                    if (prop.length == 2 && prop[0].indexOf('--') == 1) {
                                        console.log('Property name: ', prop[0]);
                                        console.log('Property value:', prop[1]);
                                    }
                                }
                            }
                        }, '');
                }
            } catch (e) {
                console.warn("Skiping: ", e)
                return [];
            }

        }, '');
:root {
    --bc: #fff;
    --bc-primary: #eee;
    --bc-secondary: #ddd;
}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css">

This is shows what happens without the try statement, I wasn't able to convert this dense code quickly so used the more traditional version :).

const variables = [].slice.call(document.styleSheets)
  .map((styleSheet) => [].slice.call(styleSheet.cssRules))
  .flat()
  .filter((cssRule) => cssRule.selectorText === ':root')
  .map((cssRule) => cssRule.cssText.split('{')[1].split('}')[0].trim().split(';'))
  .flat()
  .filter((text) => text !== '')
  .map((text) => text.split(':'))
  .map((parts) => parts[0].trim() + ':  ' + parts[1].trim())
;

console.log(variables.join('\n'));
:root {
    --foo: #fff;
    --bar: #aaa
}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css">
Asons
  • 84,923
  • 12
  • 110
  • 165
Exo Flame
  • 97
  • 4
0

Thanks @Ason and @mvndaai. I like this formatting personally:

const variables = [].slice.call(document.styleSheets)
  .map((styleSheet) => [].slice.call(styleSheet.cssRules))
  .flat()
  .filter((cssRule) => cssRule.selectorText === ':root')
  .map((cssRule) => cssRule.cssText.split('{')[1].split('}')[0].trim().split(';'))
  .flat()
  .filter((text) => text !== '')
  .map((text) => text.split(':'))
  .map((parts) => parts[0].trim() + ':  ' + parts[1].trim())
;

console.log(variables.join('\n'));
:root {
    --foo: #fff;
    --bar: #aaa
}
Sámal Rasmussen
  • 2,887
  • 35
  • 36