CSS variables, also known as custom properties or cascading variables, are a way of defining shareable styles for HTML elements. If you have used SCSS / SASS before, the idea is very similar as you can see in the basic example below:
/* We can define a CSS Variable like this */
--main-color: black;
/* And to consume this we make use of the CSS var() function */
color: var(--main-color);
Modern Web Applications are quite large in their scope, with a multitude of pages being accessible for a given user. When developing these pages, each page and it's subsequent elements or components should follow a rigid design system, such as paragraph tags being styled consistently on every page they are found on.
The way we currently do this is via a custom React component we created inside our Component Library, a <P/>
tag. That's great for reusability, but how do we define the styles for it?
For a quick reference, visit https://github.com/flexera/ui-react-components/blob/sit/packages/ui-react-components/theme/theme.ts#L55
The above link shows us our theme object, which can use like this inside our custom React components (please note that this example uses our current CSS in JS approach via Styled Components)
export const P = styled.p`
color: ${({ theme }) => theme.colors.text};
`;
This in itself works great for our React applications, but we run into problems when we need to replicate our design system into non-React applications such as an Angular codebase.
Can we have the best of both?
Well, I think so!
In the below code snippet I have created a small sample of CSS Variables and attached them to :root
, meaning that any element on the page can reference the variables.
:root {
/* General */
--fontSizeBody: .875rem;
--letterSpacing: .2px;
/* Fonts */
--fontFamilyPrimary: "Source Sans Pro", Helvetica, Arial, sans-serif;
--fontWeightPrimaryRegular: 400;
--fontWeightPrimaryBold: 700;
/* Colors */
--colorBg: #FFFFFF;
--colorBodyText: var(--colorTextDark);
--colorPrimary: #00549f;
--colorSecondary: #DDDDDD;
--colorDanger: #C32525;
--colorTextLight: #FFFFFF;
--colorTextDark: #333333;
/* Buttons */
--buttonPrimaryBgColor: var(--colorPrimary);
--buttonPrimaryTextColor: var(--colorTextLight);
--buttonSecondaryBgColor: var(--colorSecondary);
--buttonSecondaryTextColor: var(--colorTextDark);
--buttonDangerBgColor: var(--colorDanger);
--buttonDangerTextColor: var(--colorTextLight);
--buttonRadius: 4px;
/* Spacing */
--marginMd: 1rem;
--marginLg: 2rem;
}
We then need to apply this at a root HTML level, which is typically an index.html page or maybe a head component that we import to the root of our JS application. This means that regardless of the framework choice, React, Angular or other, our application is able to consume CSS Variables.
To change this at the Component Library level, we could do a couple of things.
The first option is to simply just update the theme variable value, that way we do not create any more overhead for ourselves by going through components on a individual basis and updating everything to CSS Variables.
// Inside theme.ts
export const BaseTheme = {
colors: {
text: 'var(--colorBodyText)';
}
}
// Inside Style.ts file for the component
export const P = styled.p`
color: ${({ theme }) => theme.colors.text};
`;
Or we could get rid of our BaseTheme object and simply update everything individually, removing the need for the theme object and making it slightly more similar to how we might define our styles in non-React applications.
// Inside Style.ts file for the component
export const P = styled.p`
color: var(--colorBodyText);
`;
For a simple application, or even an Angular one, we would do the following:
:root {
--colorBodyText: #333333;
}
p {
color: var(--colorBodyText);
}
Theming
The biggest benefit of using CSS Variables is it's theming capabilities. White labelling and theming have been talked about briefly within Flexera to date, but here is one approach we could take.
// index.html
// Note the use of data-theme here
<html dir="ltr" lang="en" data-theme="light">
</html>
// styles.css
[data-theme="light"] {
--colorBg: #FFFFFF;
--colorBodyText: #222222;
}
[data-theme="dark"] {
--colorBg: #222222;
--colorBodyText: #FFFFFF;
}
// scripts.js
const changeTheme = () => {
const currentTheme = document.documentElement.getAttribute("data-theme");
document.documentElement.setAttribute('data-theme', currentTheme === "light" ? "dark" : "light")
}
In the previous snippet we apply a data-theme
attribute to our HTML tag. This provides us with an easy way of manipulating our theme on the fly, allowing us to alter this via JavaScript whenever we want. This would be more than enough for providing a light or dark theme, and then we could even save the preference to indexedDB, or alike.