RSS FeedTwitterMastodonBlueskyShare IconHeart IconGithub IconArrow IconClock IconGUI Challenges IconHome IconNote IconBlog IconCSS IconJS IconHTML IconShows IconOpen Source Software IconSpeaking IconTools IconShuffle IconNext IconPrevious IconCalendar IconCalendar Edit IconNotebook IconObservable Notebooks IconSlash IconGoogle G Icon
Three custom properties are repeated in a grid and 3D tilted a bit. They are var(--party) var(--power) and var(--mix).
A cartoon skull with a hotpink hat on.15 min read

10 powerful ways to use CSS variables

css

CSS custom properties are AMAZING. I'm going to attempt to name and roundup all the categories and strategies of custom props that I've come across.

be sure to comment if you know more strategies!

1. tokens #

These custom properties are generally just global values, named objectively, and are used atomically.

They create team alignment by naming what are otherwise "magic numbers." They often follow naming conventions.

:root {
  --size-1: 1rem;
  --size-2: 1.25rem;
  --blue-1: hsl(200 50% 50%);
  --blue-2: hsl(200 50% 60%);
  --gray-0: hsl(none none 90%);
  --gray-1: hsl(none none 10%);
}

Usage of tokens looks like:

.card {
  background: var(--surface-1);
  color: var(--text-1);
  padding: var(--card-padding);
}

It's very likely these tokens will become values of other custom properties in your CSS because they are generally static and aren't overwritten later..

OpenProps === tokens

2. house props #

House props are named for project reusability. They also create team alignment, jargon, and can be adaptive (see #3).

:root {
  --brand: var(--blue-1);
  --card-padding: var(--size-1);
  --surface: #eee;

  @media (prefers-color-scheme: dark) {
    --surface: #111;  
  }
}

Usage of house props looks like:

.card {
  border-color: var(--brand);
  padding: var(--card-padding);
  background: var(--surface);
}

Open Props offers --surface-{1-4} and --text-{1,2} house props (which adapt to light & dark). They come with use of normalize.css. A light and dark card can be created with a mix of house props and tokens, no media query required as it's baked into the prop:

.card {
  background: var(--surface-2); /* surface-1 is the page bg */
  color: var(--text-1);
  border-radius: var(--radius-2);
  padding: var(--size-3);
  box-shadow: var(--shadow-2);
}

3. adaptive props #

Aka dynamic house props; these will change, you expect them to change. They need setup though, orchestrated and empowered with CSS conditionals.

:root {
  --text: var(--gray-0);
}

@media (prefers-color-scheme: dark) {
  :root {
    --text: var(--gray-10);
  }
}

Usage of adaptive props looks like:

body {
  color: var(--text);
}

I've got a tinyyyy bit more verbose pattern for adaptive props; create static house props with names that make their usage obvious (no magic values).

:root {
  /* 2 static props */
  --text-dark-mode: var(--gray-0);
  --text-light-mode: var(--gray-10);

  /* 1 adaptive prop: defaults to light static prop */
  --text: var(--text-light-mode);

  @media (prefers-color-scheme: dark) {
    /* adaptive prop set to dark static prop */
    --text: var(--text-dark-mode);
  }
}

Here's another example; adaptive sizing

:root {
  --adaptive-padding: .5rem;

  @media (width >= 320px)  { --adaptive-padding: 1rem }
  @media (width >= 720px)  { --adaptive-padding: 1.25rem }
  @media (width >= 1024px) { --adaptive-padding: 1.5rem }
}

Lastly, since media queries aren't required to make this adaptive props, here's a neat trick by Ahmad Shadeed that bakes the conditions into the math inside the prop. He's got 1 custom property that will adapt to either square corners when full screen, or rounded corners when not.

.card {
   border-radius: max(0px, min(8px, calc((100vw - 4px - 100%) * 9999)));
}

Open Props offers these now too, heavily inspired by Ahmad. They look like this in Open Props, which will have a var(--radius-2) sized rounded corners when it's not full width:

/* Open Props sets them up like this */
:root {
  --radius-conditional-2: clamp(0px, calc(100vw - 100%) * 1e5, var(--radius-2));
}

/* then authors get to use them like this! */
.card {
  border-radius: var(--radius-conditional-2);
}

Love it when a custom property disguises all the implentation details away from other authors. Reminds me of NPM and installing modules or importing functions.

4. pseudo-private props #

Lea Verou calls these pseudo-private custom properties. I appreciated the throw back to pseudo private properties in Javascript.

this._foo = 'please dont change this'

Then us mimmicking it in CSS:

.card {
  --_radius: 10px;
}

Which makes them like static tokens or house props, but scoped instead of global.

Vanilla Extract offers scoped custom properties, they use this "pseudo-private" technique in the CSS output.

.button__1qipc2y2 {
  --_1qipc2y0: #4263eb;
  --_1qipc2y1: #edf2ff;
  background-color: var(--_1qipc2y0);
  color: var(--_1qipc2y1);
}

.button__1qipc2y2:active {
  --_1qipc2y0: #3b5bdb;
  --_1qipc2y1: white;
}

A tweet about this.

5. partial props #

Prop puzzle pieces. Parts of a full usable prop. Here, brand color channels as partial props.

:root {
  --h: 200;
  --s: 50%;
  --l: 50%;
}

Usage of partial props to make adaptive props looks like:

:root {
  --brand: hsl(var(--h) var(--s) var(--l));
  --text: hsl(var(--h) 30% 5%);
  --surface: hsl(var(--h) 25% 99%);
}

@media (prefers-color-scheme: dark) {
  :root {
    --brand: hsl(var(--h) calc(var(--s) / 2) calc(var(--l) / 2));
    --text: hsl(var(--h) 10% 90%);
    --surface: hsl(var(--h) 20% 10%);
  }
}

6. mixin props #

I first saw this usage of CSS vars on Smashing Magazine by Miriam Suzanne. Great distillation of it in this Codepen.

I think of basic mixin props as a collection of partial props placed on shorthands (background-image, border, border-image, mask-image, etc) . This makes the partial props like params into a greater "mixin" prop, function thing.

* {
  --input-1: 1px;
  --input-2: var(--blue-5);
  --border-mixin: var(--input-1) solid var(--input-2);
}

Usage of basic mixin props looks like:

.card {
  border: var(--border-mixin);
}

.card.variant {
  --input-2: var(--purple-5);
}

Mia adds a nice hook feature by setting "required" prop parameters to initial. Then, any element that specifies this required prop, triggers the mixin to be valid, applying its effect.

* {
  /* define the mixin with a required parameter */
  --stripes: linear-gradient(
    var(--stripes-angle),
    powderblue 20%, 
    pink 20% 40%, 
    white 40% 60%, 
    pink 60% 80%, 
    powderblue 80%
  );

  /* reset the required parameter on each element */
  --stripes-angle: initial;  
  
  /* apply the results everywhere */
  /* (will only display when a valid angle is given) */
  background-image: var(--stripes);
}

Usage of mixin props looks like:

.stripes {
  /* providing a valid angle causes the "mixin" to work */
  --stripes-angle: to bottom right;
}

Snippet source

7. swap props #

These props flip n' flop so other props can swap. Hehe, a simple example is a bool prop:

.house-button {
  --_hover: 0;

  &:hover {
    --_hover: 1;
  }
}

How can I use this? Ask Jhey, he's got plenty-o-demo's with bool swappin props. Like this one which includes the swap prop --active:

a:is(:hover, :focus) {
  --active: 1;
}

:is(svg, .char) {
  transform:
    rotate(calc((var(--active, 0) * var(--r, 0)) * 1deg))
    translate(
      calc((var(--active, 0) * var(--x, 0)) * 1%),
      calc((var(--active, 0) * var(--y, 0)) * 1%)
    );
}

This one only transforms if --active is 1, because otherwise the 0 causes the math to be 0 degrees. Swappin props between 0 and 1, ugh, power.

He goes further in this light/dark theme demo switch, pivoting a color scheme and animations. Rad stuff.

And you can't miss Ana Tudor's post on Dry Switching with CSS Variables. This was the first place I ever saw props used as bools to pivot behavior and UI.

There's also Jane Ori who's made many games and intense systems out of Dry Switching / swappy props: CSS Sweeper, CSS Conways Game of Life, and more. They've even got this wild library called CSS Media Vars which enable responsive design with named breakpoints and props, it's very cool.

Also, there's a sweet PostCSS plugin from @CSStools called conditional values that gives some syntactic sugar to this:

.fancy-container {
    --is-fancy: true;
}

.block {
    color: csstools-if(--is-fancy pink else red);
}

/* becomes */
:root {
    --is-fancy:  ;
}

.fancy-container {
    --is-fancy: initial;
}

.block {
    --is-fancy--0: var(--is-fancy) red;
    color: var(--is-fancy--0,pink);
}

8. style query props #

Container query props! Could be used as enums for theming, state machines, you name it.

Here's the gist:

button {
  @container style(--vibe: primary) {
    --_bg: var(--indigo-5);
    --_border: var(--indigo-4);
  }
  
  @container style(--vibe: rad) {
    --_bg: var(--gradient-11);
    border: none;
  }
  
  @container style(--size: large) {
    font-size: var(--font-size-4);
  }
}

Try this snippet in Canary on Codepen!

Manuel Matuzović has been writing about it with a post on how style queries work on computed custom property values and how using @property can help with style queries.

9. meta lang props #

Make your own CSS API, in CSS! Mixins are the primary key but other strategies from this post are helpful.

Me using a quick one we'll make:

.button {
  --bg: blue;
  --text-color: white;
}

Setting up this means setting the actual css property color with your new name for it text.

* {
  background-color: var(--bg);
  color: var(--text);
}

There, you just created an interface into an interface. These custom properties pass through to the actual CSS property.

So, that was a pretty tame example. Here's one where I'm adding a new property that matches both padding and gaps.

* {
  padding: var(--gapadding);
  gap: var(--gapadding);
}

.card {
  --gapadding: 1rem;
}

Here's a wild example! It's not real, but I'm pretty sure this could all be setup.

.button {
  --_type: 3d-primary;
  --_accent: var(--neon-purple);
  --_accent-hover: var(--neon-pink);
  --_3d-depth-level: 2;
  --_particles: 20;
}

10. typed props #

@property is another really cool custom property category of usage. These provide type safety and can assist browsers in knowing your animation intents.

@property --focal-size {
  syntax: '<length-percentage>';
  initial-value: 100%;
  inherits: false;
}

That definition of --focal-size is enough to teach some browsers how to animate a gradient used into a mask. Hold alt/opt inside the codepen below, it'll transition if you are in a browser with @property.

Conclusion #

Between mixins, @property, scoping tricks, and style queries… There still a lot of unexplored territory here and tons of power 😉

Did I miss a category‽

39 comments #

608likes
73reposts
  • Chrome Developers
  • Filmy Sperm
  • Franko
  • romainmenke
  • dave cOllison
  • CSS Tools
  • Dohány Tamás
  • Caleb (he/him) #BLM
  • Crown 🌟🐳
  • Thomas Broyer
  • Scott Kellum :typetura:
  • jfroehlich
  • Max Böck
  • Miguel Marques
  • Dmitry N. Medvedev 🤍💙🤍
  • Una 🇺🇦
  • bezier
  • Kyle Stoneman
  • B L I И K
  • Fabrizio Calderan
  • 🔥.codes
  • Michael Klöpzig
  • Yannick Schall
  • Basit Bashir
  • Marcel dos Santos
  • Brecht
  • Etienne🐿 van Delden-de la Haije
  • Cyphr Sylph
  • Damian Wajer
  • Benny Powers 🇨🇦️🇮🇱️
  • Antti Mattila
  • Adam
  • oxomichael
  • Polemizante
  • Sieg
  • Chanfi Attoumani
  • alex
  • lahincapie
  • Kais
  • Jason Lengstorf ⚡️
  • LetMeLiveFree
  • Віталій Бобров 🇺🇦💗🛡️⚔️
  • 全部入りHTML太郎
  • John Miller
  • Alice Li
  • ziumek🖤
  • Y Nathan M
  • Marlon Alvarez
  • Casey Robinson👨🏻‍💻
  • 比喩
  • Thilo Maier
  • Brian Hinton 🐧
  • Jorge Galarza
  • jules
  • Egor Kloos
  • Alex Rothuis ????
  • Tristan Gibbs ????
  • Michael
  • Ryan Mulligan
  • The Wired Serf - Jeff Jones
  • ????
  • Brent Morton
  • Wolfgang Wiese
  • Jack Iakovenko
  • Sam I am Designs 🫶
  • Dennis Buizert
  • umut şirin
  • Malindu Warapitiya

Join the conversation on

We have a PostCSS plugin with syntactic sugar for number 7 : npmjs.com/package/@cssto… I use it personally to "communicate" between two different stylesheets. When A has styles for dark mode B get's different prop values.
romainmenkeromainmenke
sick, will add to the post!
Adam ArgyleAdam Argyle
nerdy.dev doesn't work on my phone since last website update. Just FYI.
Ky6ukKy6uk
does i work on Stable Chrome for you? the site has some View Timeline animations and stuff that had a few shaky builds, maybe it's related to that? thanks for the report!
Adam ArgyleAdam Argyle
Yep, it does work there.
Ky6ukKy6uk

@argyleink
11. Door props to prop open your door

Baruch KatzBaruch Katz
I don’t know what a swappy prop is but I’ve never wanted something more in my life
Rob DiMarzoRob DiMarzo
coined by @jlengstorf in a video we did with Open Props hehe, def has a nice ring to it. nerdy.dev/was-on-the-lea…
Adam ArgyleAdam Argyle
10 powerful ways to use CSS variables · 2023年1月26日 nerdy.dev/custom-prop-ca…
ユイトユイト
I shall preach the good word of the swappy prop from the highest peak
Rob DiMarzoRob DiMarzo
Powerful ways to use CSS variables: nerdy.dev/custom-prop-ca…
JulienJulien
We use this tool to align our tokens with the team! so each one can export them in the format they need: tokens.layoutit.com
Mariana BeldiMariana Beldi
Ha! I don't know about the name "swappy" for that technique 😅 But I appreciate the shout out in the article 🙏 Was definitely one of the first things I discovered when trying them out and has become a staple for sure when I use them. It's gotta be in a lot of my demos 😅
jhey 🔨🐻✨jhey 🔨🐻✨
Feel like it might be worth revisiting my own custom property article again 🤓 I like the format here. I likely don't use all the listed ways. But there are a couple of scenarios I think I could add 🤓✨
jhey 🔨🐻✨jhey 🔨🐻✨
I will accept no swappy slander here, sir
Jason Lengstorf ⚡️Jason Lengstorf ⚡️
Haha 😅 Surely there is a use case for "Smash" props?!
jhey 🔨🐻✨jhey 🔨🐻✨
you have my attention 👀
Jason Lengstorf ⚡️Jason Lengstorf ⚡️
Sounds like the family game "joy wack-a-mole" where I say something that brings me joy and my family tells me how awful it is
Adam ArgyleAdam Argyle
Hi Adam! Is it possible to add a high contrast mode for the text? I really like the design but sadly the light gray text color gives me a hard time while reading your posts :(
BerkayBerkay
Switch to dark mode? Is it the color or the font thickness?
Adam ArgyleAdam Argyle
Oh wait, you seeing this? Theres a regression to light theme it seems, this is not how the light design should be
Adam ArgyleAdam Argyle
Usually, it is rather fine on mobile devices but on desktop devices, the grays seem lighter (not that they are, just my perception). This is the font color I see on both mobile and desktop. I use dark mode on my computer but I didn't see a dark mode toggle +
BerkayBerkay
or dark mode on initial visit, but I am not certain on this, so I can't provide solid feedback regarding dark mode.
BerkayBerkay
Yes! This is how I see your posts on desktop, and previously on mobile too. But in the screenshot I added below it seems like font color has got darker or font weight has got bolder.
BerkayBerkay
A wonderful article by @argyleink Learn how to use CSS Variable. 🔥💯🙌 nerdy.dev/custom-prop-ca…
Rohan.dev 🚀👨🏻‍💻Rohan.dev 🚀👨🏻‍💻
There's even more layers not captured in the image! I outline 10 ways to use tokens in this post 👍🏻 nerdy.dev/custom-prop-ca…
Adam ArgyleAdam Argyle

@argyleink Wow first off thank you so much for reading and boosting.

Your article about custom prop categories is so fricken cool. Such a treasure trove of fantastic thinking in there. Thanks so much for sharing that!

As far as hating naming, I feel like I wrote the article because I love naming stuff ???? Open props looks like it provides some really nice utilities, similar in concept to tailwind. I liked this article from a while ago on why tailwind is controversial: https://joshcollinsworth.com/blog/tailwind-is-smart-steering

Classic rock, Mario Kart, and why we can't agree on Tailwind
Jonathan DallasJonathan Dallas
nice article! imo the way @kevinpowell.co uses pseudo properties is really cool if i remember correctly he does somethin like: --_accent: var(--accent, black); and then references --_accent in css and --accent can be defined elsewhere (other css or html etc) with a fallback provided
????????
Looks like Adam mentioned that approach in ghere, and linked to the @lea.verou.me article where I picked it up from ???? lea.verou.me/blog/2021/10...
Kevin PowellKevin Powell
Thoughts on the perf problems when using CSS variables? We’ve seen problems when using too many vars. specifically too much RAM usage that can sometimes even cause browser crashes.
NmnNmn
Possible indeed, seen cases where 5k+ on root, or all props on * becoming noticeable paint issues Also different issues for different browsers, each try to optimize for gnarly cases Pretty rare to reach the numbers that cause issues, imo Can't "billion laughs attack" either.
Adam ArgyleAdam Argyle