title: An advanced way to use CSS variables
url: https://gomakethings.com/an-advanced-way-to-use-css-variables/
hash_url: a161012772
archive_date: 2024-04-08
og_image: https://gomakethings.com/img/og.png
description: Yesterday, we learned about CSS variables. Today, I wanted to show you an advanced approach to working with them that I often use with client projects.
favicon: https://gomakethings.com/img/favicon.ico
language: en_US
Let’s dig in!
I like to scope design system or theme defaults to the :root
element. This makes them accessible to every element and class in the design system.
:root {
/_ Colors _/
--color-primary: #0088cc;
--color-secondary: rebeccapurple;
--color-black: #272727;
--color-white: #ffffff;
/_ Sizes _/
--size-default: 1rem;
--size-small: 0.875rem;
--size-large: 1.25rem;
/_ Typefaces _/
--font-sans: "PT Sans", sans;
--font-serif: "PT Serif", serif;
--font-mono: Menlo, Monaco, "Courier New", monospace;
}
I typically have variables for --color-*
, --size-*
, and --font-*
, as well as ones to define the max width of containers and how much --spacing
to use between paragraphs and various elements.
Let’s look at styles for a button
element.
button {
background-color: var(--color-primary);
border: 0.125rem solid var(--color-primary);
border-radius: 0.25em;
color: var(--color-white);
display: inline-block;
font-size: var(--font-default);
font-weight: normal;
line-height: 1.2;
padding: 0.5rem 0.6875rem;
}
button:hover {
background-color: var(--color-secondary);
border-color: var(--color-secondary);
}
Let’s say we want to add a secondary button style: the .btn-secondary
class.
<button>Primary Button</button>
<button class="btn-secondary">Secondary Button</button>
Using only globals, we might write the CSS like this.
.btn-secondary {
background-color: var(--color-secondary);
border-color: var(--color-secondary);
}
.btn-secondary:hover {
background-color: var(--color-primary);
border-color: var(--color-primary);
}
It totally works, and we can easily update our global colors later and have them automatically update the button styles.
But there’s another way we could approach this that I think works a little bit better.
While global variables scoped to the :root
let me define system-wide defaults, I also like to scope variables for styles that change with utility classes to the element itself.
CSS variables scoped to an element can use other CSS variables as their value. But scoping them to the element provides an easy way to modify them.
Looking at our button
again, I’ll often do something like this…
button {
--bg-color: var(--color-primary);
--color: var(--color-white);
--size: var(--font-default);
--padding-x: 0.6875rem;
--padding-y: 0.5rem;
background-color: var(--bg-color);
border: 0.125rem solid var(--bg-color);
border-radius: 0.25em;
color: var(--color);
display: inline-block;
font-size: var(--size);
font-weight: normal;
line-height: 1.2;
padding: var(--padding-y) var(--padding-x);
}
Now, to change the button:hover
style, I only need to update the --bg-color
variable, which controls both the background-color
and border-color
properties.
button:hover {
--bg-color: var(--color-secondary);
}
This approach is a little bit more work up-front, but it has bigger payoffs the more you use it.
For example, our .btn-secondary
class gets shorter.
.btn-secondary {
--bg-color: var(--color-secondary);
}
.btn-secondary:hover {
--bg-color: var(--color-primary);
}
With every utility class you add to modify your base styles, using element-scoped CSS variables makes things a bit easier.
For example, we can add .btn-large
and .btn-small
classes by doing this…
.btn-large {
--size: var(--font-large);
--padding-x: 0.875rem;
--padding-y: 0.75rem;
}
.btn-small {
--size: var(--font-small);
--padding-x: 0.5rem;
--padding-y: 0.25rem;
}
Nope! It’s a lot more work, and sometimes results in code that’s a bit less readable at first glance.
It’s a good approach to use…