13

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?

2
  • What do you plan on doing with the list of variables once you've found them? Commented Aug 18, 2017 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. Commented Aug 18, 2017 at 21:44

5 Answers 5

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;
}

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

1 Comment

Really appreciate the amount of effort that went into this. Thank you.
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. 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
}

1 Comment

Here is a demo page I made to use this for theming a webpage mvndaai.com/css_variables
5

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.

Comments

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">

2 Comments

This is super interesting. I would probably do the try/catch inside a .filter
I updated my answer to include a catch for this. Thanks!
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
}

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.