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