8

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
  • 81,773
  • 12
  • 93
  • 144
MorganR
  • 599
  • 3
  • 10
  • 21
  • 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

4 Answers4

15

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.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
  • 81,773
  • 12
  • 93
  • 144
7

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

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 => ({key: parts[0].trim(), value: parts[1].trim() }))
;

console.log(variables);
:root {
    --foo: #fff;
    --bar: #aaa
}
mvndaai
  • 3,017
  • 2
  • 27
  • 31
  • 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
0

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
  • 81,773
  • 12
  • 93
  • 144
Exo Flame
  • 19
  • 3
-1

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,260
  • 30
  • 34