{"id":311697,"date":"2020-06-08T07:13:13","date_gmt":"2020-06-08T14:13:13","guid":{"rendered":"https:\/\/css-tricks.com\/?p=311697"},"modified":"2020-06-08T07:13:15","modified_gmt":"2020-06-08T14:13:15","slug":"how-to-get-all-custom-properties-on-a-page-in-javascript","status":"publish","type":"post","link":"https:\/\/css-tricks.com\/how-to-get-all-custom-properties-on-a-page-in-javascript\/","title":{"rendered":"How to Get All Custom Properties on a Page in JavaScript"},"content":{"rendered":"\n

We can use JavaScript to get the value of a CSS custom property. Robin wrote up a detailed explanation about this in Get a CSS Custom Property Value with JavaScript<\/a>. To review, let\u2019s say we\u2019ve declared a single custom property on the HTML element:<\/p>\n\n\n\n

html {\n\u00a0 --color-accent: #00eb9b;\n}<\/code><\/pre>\n\n\n\n

In JavaScript, we can access the value with getComputedStyle<\/code> and getPropertyValue<\/code>:<\/p>\n\n\n\n

const colorAccent = getComputedStyle(document.documentElement)\n\u00a0 .getPropertyValue('--color-accent'); \/\/ #00eb9b<\/code><\/pre>\n\n\n\n

Perfect. Now we have access to our accent color in JavaScript. You know what\u2019s cool? If we change that color in CSS, it updates in JavaScript as well! Handy.<\/p>\n\n\n\n\n\n\n\n

What happens, though, when it\u2019s not just one property we need access to in JavaScript, but a whole bunch of them?<\/p>\n\n\n\n

html {\n\u00a0 --color-accent: #00eb9b;\n\u00a0 --color-accent-secondary: #9db4ff;\n\u00a0 --color-accent-tertiary: #f2c0ea;\n\u00a0 --color-text: #292929;\n\u00a0 --color-divider: #d7d7d7;\n}<\/code><\/pre>\n\n\n\n

We end up with JavaScript that looks like this:<\/p>\n\n\n\n

const colorAccent = getComputedStyle(document.documentElement).getPropertyValue('--color-accent'); \/\/ #00eb9b\nconst colorAccentSecondary = getComputedStyle(document.documentElement).getPropertyValue('--color-accent-secondary'); \/\/ #9db4ff\nconst colorAccentTertiary = getComputedStyle(document.documentElement).getPropertyValue('--color-accent-tertiary'); \/\/ #f2c0ea\nconst colorText = getComputedStyle(document.documentElement).getPropertyValue('--color-text'); \/\/ #292929\nconst colorDivider = getComputedStyle(document.documentElement).getPropertyValue('--color-text'); \/\/ #d7d7d7<\/code><\/pre>\n\n\n\n

We\u2019re repeating ourselves a lot. We could shorten each one of these lines by abstracting the common tasks to a function.<\/p>\n\n\n\n

const getCSSProp = (element, propName) => getComputedStyle(element).getPropertyValue(propName);\nconst colorAccent = getCSSProp(document.documentElement, '--color-accent'); \/\/ #00eb9b\n\/\/ repeat for each custom property...<\/code><\/pre>\n\n\n\n

That helps reduce code repetition, but we still have a less-than-ideal situation. Every time we add a custom property in CSS, we have to write another line of JavaScript to access it. This can and does work fine if we only have a few custom properties. I\u2019ve used this setup on production projects before. But, it\u2019s also possible to automate this.<\/p>\n\n\n\n

Let\u2019s walk through the process of automating it by making a working thing.<\/p>\n\n\n

What are we making?<\/h3>\n\n\n

We\u2019ll make a color palette, which is a common feature in pattern libraries. We\u2019ll generate a grid of color swatches from our CSS custom properties. <\/p>\n\n\n\n

Here\u2019s the complete demo<\/a> that we\u2019ll build step-by-step.<\/p>\n\n\n\n

\"A
Here’s what we’re aiming for.<\/figcaption><\/figure>\n\n\n\n

Let\u2019s set the stage. We\u2019ll use an unordered list to display our palette. Each swatch is a <li><\/code> element that we\u2019ll render with JavaScript. <\/p>\n\n\n\n

<ul class=\"colors\"><\/ul><\/code><\/pre>\n\n\n\n

The CSS for the grid layout isn\u2019t pertinent to the technique in this post, so we won\u2019t look at in detail. It\u2019s available in the CodePen demo<\/a>.<\/p>\n\n\n\n

Now that we have our HTML and CSS in place, we\u2019ll focus on the JavaScript. Here\u2019s an outline of what we\u2019ll do with our code:<\/p>\n\n\n\n

  1. Get all stylesheets on a page, both external and internal<\/li>
  2. Discard any stylesheets hosted on third-party domains<\/li>
  3. Get all rules for the remaining stylesheets<\/li>
  4. Discard any rules that aren\u2019t basic style rules<\/li>
  5. Get the name and value of all CSS properties<\/li>
  6. Discard non-custom CSS properties<\/li>
  7. Build HTML to display the color swatches<\/li><\/ol>\n\n\n\n

    Let\u2019s get to it.<\/p>\n\n\n

    Step 1: Get all stylesheets on a page<\/h3>\n\n\n

    The first thing we need to do is get all external and internal stylesheets on the current page. Stylesheets are available as members of the global document.<\/p>\n\n\n\n

    document.styleSheets<\/code><\/pre>\n\n\n\n

    That returns an array-like object. We want to use array methods, so we\u2019ll convert it to an array. Let\u2019s also put this in a function that we\u2019ll use throughout this post.<\/p>\n\n\n\n

    const getCSSCustomPropIndex = () => [...document.styleSheets];<\/code><\/pre>\n\n\n\n
    \n
    CodePen Demo<\/a><\/div>\n<\/div>\n\n\n\n

    When we invoke getCSSCustomPropIndex<\/code>, we see an array of CSSStyleSheet<\/code><\/a> objects, one for each external and internal stylesheet on the current page.<\/p>\n\n\n\n

    \"The<\/figure>\n\n\n

    Step 2: Discard third-party stylesheets<\/h3>\n\n\n

    If our script is running on https:\/\/example.com any stylesheet we want to inspect must also be on https:\/\/example.com. This is a security feature. From the MDN docs for CSSStyleSheet<\/code><\/a>:<\/p>\n\n\n\n

    In some browsers, if a stylesheet is loaded from a different domain, accessing cssRules<\/code> results in SecurityError<\/code>.<\/p><\/blockquote>\n\n\n\n

    That means that if the current page links to a stylesheet hosted on https:\/\/some-cdn.com, we can\u2019t get custom properties \u2014 or any styles \u2014 from it. The approach we\u2019re taking here only works for stylesheets hosted on the current domain.<\/p>\n\n\n\n

    CSSStyleSheet<\/code> objects have an href<\/code> property. Its value is the full URL to the stylesheet, like https:\/\/example.com\/styles.css. Internal stylesheets have an href<\/code> property, but the value will be null<\/code>.<\/p>\n\n\n\n

    Let\u2019s write a function that discards third-party stylesheets. We\u2019ll do that by comparing the stylesheet\u2019s href <\/code>value to the current location.origin<\/code>.<\/p>\n\n\n\n

    const isSameDomain = (styleSheet) => {\n\u00a0 if (!styleSheet.href) {\n\u00a0 \u00a0 return true;\n\u00a0 }\n\u2028\n\u00a0 return styleSheet.href.indexOf(window.location.origin) === 0;\n};<\/code><\/pre>\n\n\n\n

    Now we use isSameDomain<\/code> as a filter ondocument.styleSheets<\/code>.<\/p>\n\n\n\n

    const getCSSCustomPropIndex = () => [...document.styleSheets]\n\u00a0 .filter(isSameDomain);<\/code><\/pre>\n\n\n\n
    \n
    CodePen Demo<\/a><\/div>\n<\/div>\n\n\n\n

    With the third-party stylesheets discarded, we can inspect the contents of those remaining.<\/p>\n\n\n

    Step 3: Get all rules for the remaining stylesheets<\/h3>\n\n\n

    Our goal for getCSSCustomPropIndex<\/code> is to produce an array of arrays. To get there, we\u2019ll use a combination of array methods to loop through, find values we want, and combine them. Let\u2019s take a first step in that direction by producing an array containing every style rule.<\/p>\n\n\n\n

    const getCSSCustomPropIndex = () => [...document.styleSheets]\n\u00a0 .filter(isSameDomain)\n\u00a0 .reduce((finalArr, sheet) => finalArr.concat(...sheet.cssRules), []);<\/code><\/pre>\n\n\n\n
    \n
    CodePen Demo<\/a><\/div>\n<\/div>\n\n\n\n

    We use reduce<\/code> and concat<\/code> because we want to produce a flat array where every first-level element is what we\u2019re interested in. In this snippet, we iterate over individual CSSStyleSheet<\/code><\/a> objects. For each one of them, we need its cssRules<\/code>. From the MDN docs<\/a>:<\/p>\n\n\n\n

    The read-only CSSStyleSheet<\/code> property cssRules returns a live CSSRuleList<\/code> which provides a real-time, up-to-date list of every CSS rule which comprises the stylesheet. Each item in the list is a CSSRule<\/code> defining a single rule.<\/p><\/blockquote>\n\n\n\n

    Each CSS rule is the selector, braces, and property declarations. We use the spread operator ...sheet.cssRules<\/code> to take every rule out of the cssRules<\/code> object and place it in finalArr<\/code>. When we log the output of getCSSCustomPropIndex<\/code>, we get a single-level array of CSSRule<\/code><\/a> objects.<\/p>\n\n\n\n

    \"Example<\/figure>\n\n\n\n

    This gives us all the CSS rules for all the stylesheets. We want to discard some of those, so let\u2019s move on.<\/p>\n\n\n

    Step 4: Discard any rules that aren\u2019t basic style rules<\/h3>\n\n\n

    CSS rules come in different types. CSS specs define each of the types with a constant name and integer. The most common type of rule is the CSSStyleRule<\/code>. Another type of rule is the CSSMediaRule<\/code>. We use those to define media queries, like @media (min-width: 400px) {}<\/code>. Other types include CSSSupportsRule<\/code>, CSSFontFaceRule<\/code>, and CSSKeyframesRule<\/code>. See the Type constants section<\/a> of the MDN docs for CSSRule<\/code> for the full list.<\/p>\n\n\n\n

    We\u2019re only interested in rules where we define custom properties and, for the purposes in this post, we\u2019ll focus on CSSStyleRule<\/code>. That does leave out the CSSMediaRule<\/code> rule type where it\u2019s valid to define custom properties. We could use an approach that\u2019s similar to what we\u2019re using to extract custom properties in this demo, but we\u2019ll exclude this specific rule type to limit the scope of the demo.<\/p>\n\n\n\n

    To narrow our focus to style rules, we\u2019ll write another array filter:<\/p>\n\n\n\n

    const isStyleRule = (rule) => rule.type === 1;<\/code><\/pre>\n\n\n\n

    Every CSSRule<\/code> has a type<\/code> property that returns the integer for that type constant. We use isStyleRule<\/code> to filter sheet.cssRules<\/code>.<\/p>\n\n\n\n

    const getCSSCustomPropIndex = () => [...document.styleSheets]\n\u00a0 .filter(isSameDomain)\n\u00a0 .reduce((finalArr, sheet) => finalArr.concat(\n\u00a0 \u00a0 [...sheet.cssRules].filter(isStyleRule)\n\u00a0 ), []);<\/code><\/pre>\n\n\n\n
    \n
    CodePen Demo<\/a><\/div>\n<\/div>\n\n\n\n

    One thing to note is that we are wrapping ...sheet.cssRules<\/code> in brackets so we can use the array method filter.<\/p>\n\n\n\n

    Our stylesheet only had CSSStyleRules<\/code> so the demo results are the same as before. If our stylesheet had media queries or font-face<\/code> declarations, isStyleRule<\/code> would discard them.<\/p>\n\n\n

    Step 5: Get the name and value of all properties<\/h3>\n\n\n

    Now that we have the rules we want, we can get the properties that make them up. CSSStyleRule<\/code> objects have a style property that is a CSSStyleDeclaration<\/code><\/a> object. It\u2019s made up of standard CSS properties, like color<\/code>, font-family<\/code>, and border-radius<\/code>, plus custom properties. Let\u2019s add that to our getCSSCustomPropIndex<\/code> function so that it looks at every rule, building an array of arrays along the way:<\/p>\n\n\n\n

    const getCSSCustomPropIndex = () => [...document.styleSheets]\n\u00a0 .filter(isSameDomain)\n\u00a0 .reduce((finalArr, sheet) => finalArr.concat(\n\u00a0 \u00a0 [...sheet.cssRules]\n\u00a0 \u00a0 \u00a0 .filter(isStyleRule)\n\u00a0 \u00a0 \u00a0 .reduce((propValArr, rule) => {\n\u00a0 \u00a0 \u00a0 \u00a0 const props = []; \/* TODO: more work needed here *\/\n\u00a0 \u00a0 \u00a0 \u00a0 return [...propValArr, ...props];\n\u00a0 \u00a0 \u00a0 }, [])\n\u00a0 ), []);<\/code><\/pre>\n\n\n\n

    If we invoke this now, we get an empty array. We have more work to do, but this lays the foundation. Because we want to end up with an array, we start with an empty array by using the accumulator, which is the second parameter of reduce<\/code>. In the body of the reduce<\/code> callback function, we have a placeholder variable, props<\/code>, where we\u2019ll gather the properties. The return<\/code> statement combines the array from the previous iteration \u2014 the accumulator \u2014 with the current props<\/code> array.<\/p>\n\n\n\n

    Right now, both are empty arrays. We need to use rule.style<\/code> to populate props with an array for every property\/value in the current rule:<\/p>\n\n\n\n

    const getCSSCustomPropIndex = () => [...document.styleSheets]\n\u00a0 .filter(isSameDomain)\n\u00a0 .reduce((finalArr, sheet) => finalArr.concat(\n\u00a0 \u00a0 [...sheet.cssRules]\n\u00a0 \u00a0 \u00a0 .filter(isStyleRule)\n\u00a0 \u00a0 \u00a0 .reduce((propValArr, rule) => {\n\u00a0 \u00a0 \u00a0 \u00a0 const props = [...rule.style].map((propName) => [\n          propName.trim(),\n          rule.style.getPropertyValue(propName).trim()\n        ]);\n\u00a0 \u00a0 \u00a0 \u00a0 return [...propValArr, ...props];\n\u00a0 \u00a0 \u00a0 }, [])\n\u00a0 ), []);<\/code><\/pre>\n\n\n\n
    \n
    CodePen Demo<\/a><\/div>\n<\/div>\n\n\n\n

    rule.style<\/code> is array-like, so we use the spread operator again to put each member of it into an array that we loop over with map. In the map<\/code> callback, we return an array with two members. The first member is propName<\/code> (which includes color<\/code>, font-family<\/code>, --color-accent<\/code>, etc.). The second member is the value of each property. To get that, we use the getPropertyValue<\/code><\/a> method of CSSStyleDeclaration<\/code>. It takes a single parameter, the string name of the CSS property. <\/p>\n\n\n\n

    We use trim<\/code> on both the name and value to make sure we don\u2019t include any leading or trailing whitespace that sometimes gets left behind.<\/p>\n\n\n\n

    Now when we invoke getCSSCustomPropIndex<\/code>, we get an array of arrays. Every child array contains a CSS property name and a value.<\/p>\n\n\n\n

    \"Output<\/figure>\n\n\n\n

    This is what we\u2019re looking for! Well, almost. We\u2019re getting every property in addition to custom properties. We need one more filter to remove those standard properties because all we want are the custom properties.<\/p>\n\n\n

    Step 6: Discard non-custom properties<\/h3>\n\n\n

    To determine if a property is custom, we can look at the name. We know custom properties must start with two dashes (--<\/code>). That\u2019s unique in the CSS world, so we can use that to write a filter function:<\/p>\n\n\n\n

    ([propName]) => propName.indexOf(\"--\") === 0)<\/code><\/pre>\n\n\n\n

    Then we use it as a filter on the props<\/code> array:<\/p>\n\n\n\n

    const getCSSCustomPropIndex = () =>\n\u00a0 [...document.styleSheets].filter(isSameDomain).reduce(\n\u00a0 \u00a0 (finalArr, sheet) =>\n\u00a0 \u00a0 \u00a0 finalArr.concat(\n\u00a0 \u00a0 \u00a0 \u00a0 [...sheet.cssRules].filter(isStyleRule).reduce((propValArr, rule) => {\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 const props = [...rule.style]\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 .map((propName) => [\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 propName.trim(),\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 rule.style.getPropertyValue(propName).trim()\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 ])\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 .filter(([propName]) => propName.indexOf(\"--\") === 0);\n\u2028\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return [...propValArr, ...props];\n\u00a0 \u00a0 \u00a0 \u00a0 }, [])\n\u00a0 \u00a0 \u00a0 ),\n\u00a0 \u00a0 []\n\u00a0 );<\/code><\/pre>\n\n\n\n
    \n
    CodePen Demo<\/a><\/div>\n<\/div>\n\n\n\n

    In the function signature, we have ([propName])<\/code>. There, we\u2019re using array destructuring to access the first member of every child array in props. From there, we do an indexOf<\/code> check on the name of the property. If --<\/code> is not at the beginning of the prop name, then we don\u2019t include it in the props<\/code> array.<\/p>\n\n\n\n

    When we log the result, we have the exact output we\u2019re looking for: An array of arrays for every custom property and its value with no other properties.<\/p>\n\n\n\n

    \"The<\/figure>\n\n\n\n

    Looking more toward the future, creating the property\/value map doesn\u2019t have to require so much code. There\u2019s an alternative in the CSS Typed Object Model Level 1<\/a> draft that uses CSSStyleRule.styleMap<\/code><\/a>. The styleMap<\/code> property is an array-like object of every property\/value of a CSS rule. We don\u2019t have it yet, but If we did, we could shorten our above code by removing the map<\/code>:<\/p>\n\n\n\n

    \/\/ ...\nconst props = [...rule.styleMap.entries()].filter(\/*same filter*\/);\n\/\/ ...<\/code><\/pre>\n\n\n\n
    \n
    CodePen Demo<\/a><\/div>\n<\/div>\n\n\n\n

    At the time of this writing, Chrome and Edge have implementations of styleMap<\/code> but no other major browsers do. Because styleMap<\/code> is in a draft, there\u2019s no guarantee that we\u2019ll actually get it, and there\u2019s no sense using it for this demo. Still, it\u2019s fun to know it\u2019s a future possibility!<\/p>\n\n\n\n

    We have the data structure we want. Now let\u2019s use the data to display color swatches.<\/p>\n\n\n

    Step 7: Build HTML to display the color swatches<\/h3>\n\n\n

    Getting the data into the exact shape we needed was the hard work. We need one more bit of JavaScript to render our beautiful color swatches. Instead of logging the output of getCSSCustomPropIndex<\/code>, let\u2019s store it in variable.<\/p>\n\n\n\n

    const cssCustomPropIndex = getCSSCustomPropIndex();<\/code><\/pre>\n\n\n\n

    Here\u2019s the HTML we used to create our color swatch at the start of this post:<\/p>\n\n\n\n

    <ul class=\"colors\"><\/ul><\/code><\/pre>\n\n\n\n

    We\u2019ll use innerHTML<\/code> to populate that list with a list item for each color:<\/p>\n\n\n\n

    document.querySelector(\".colors\").innerHTML = cssCustomPropIndex.reduce(\n\u00a0 (str, [prop, val]) => `${str}<li class=\"color\">\n\u00a0 \u00a0 <b class=\"color__swatch\" style=\"--color: ${val}\"><\/b>\n\u00a0 \u00a0 <div class=\"color__details\">\n\u00a0 \u00a0 \u00a0 <input value=\"${prop}\" readonly \/>\n\u00a0 \u00a0 \u00a0 <input value=\"${val}\" readonly \/>\n\u00a0 \u00a0 <\/div>\n\u00a0 \u00a0<\/li>`,\n\u00a0 \"\");<\/code><\/pre>\n\n\n\n
    \n
    CodePen Demo<\/a><\/div>\n<\/div>\n\n\n\n

    We use reduce to iterate over the custom prop index and build a single HTML-looking string for innerHTML<\/code>. But reduce<\/code> isn\u2019t the only way to do this. We could use a map<\/code> and join<\/code> or forEach<\/code>. Any method of building the string will work here. This is just my preferred way to do it.<\/p>\n\n\n\n

    I want to highlight a couple specific bits of code. In the reduce<\/code> callback signature, we\u2019re using array destructuring again with [prop, val]<\/code>, this time to access both members of each child array. We then use the prop<\/code> and val<\/code> variables in the body of the function.<\/p>\n\n\n\n

    To show the example of each color, we use a b<\/code> element with an inline style:<\/p>\n\n\n\n

    <b class=\"color__swatch\" style=\"--color: ${val}\"><\/b><\/code><\/pre>\n\n\n\n

    That means we end up with HTML that looks like:<\/p>\n\n\n\n

    <b class=\"color__swatch\" style=\"--color: #00eb9b\"><\/b><\/code><\/pre>\n\n\n\n

    But how does that set a background color? In the full CSS<\/a> we use the custom property --color<\/code> as the value of  background-color<\/code> for each .color__swatch<\/code>. Because external CSS rules inherit from inline styles, --color<\/code>  is the value we set on the b<\/code> element.<\/p>\n\n\n\n

    .color__swatch {\n\u00a0 background-color: var(--color);\n\u00a0 \/* other properties *\/\n}<\/code><\/pre>\n\n\n\n

    We now have an HTML display of color swatches representing our CSS custom properties!<\/p>\n\n\n\n