CSS Custom Properties and !important

💁‍♂️ In this post, I’m taking my own spin on a post originally by Stefan Judis. Doing so, it helped me to better grasp it myself and I was also able to simplify the examples. I hope you will find it helpful too.

What happens when you set !important on a Custom Property? Stefan Judis dug into the subject and posted about it. It all starts with this interesting paragraph from the specification:

Custom properties can contain a trailing !important, but this is automatically removed from the property’s value by the CSS parser […]

As an example, take this snippet:

div {
  --color: red !important;
  background: var(--color);

div {
  background: yellow;

Under normal circumstances, red !important would win from yellow. But since !important gets removed (because it is used in a Custom Property) the snippet basically boils down to this:

div {
  background: red;

div {
  background: yellow;

As both selectors live in the same Origin and have the same Specificity, it’s “Order of Appearance” that determines the winner: the last declaration wins, i.e. background: yellow;


This doesn’t mean that !important is entirely discarded in combination with Custom Properties though. The modifier does get used, but only when trying to determine the actual value for that Custom Property if you have multiple competing declarations.

div {
  --color: red !important;
  background: var(--color);

.class {
  --color: blue;

Although the .class selector is more specific than the div selector, the resulting value for --color will be red. This is because the !important made it win.


When the resulting value gets used in the background property, the !important part will, as per spec, be removed again.

This means that when combining both snippets …

div {
  --color: red !important;
  background: var(--color);

.class {
  --color: blue;

div {
  background: yellow;

… the result will again be yellow because we end up in the same situation as in the first example:

  • For the Custom Properties, red !important will win from blue
  • When used, (the flattened) background: red; will lose from background: yellow; because both selectors live in the same Origin, have the same Specificity, and background: yellow; is declared later.

Here’s a CodePen demo for you to play with:

See the Pen
Custom Properties vs !important
by Bramus (@bramus)
on CodePen.

A first look at container-query-polyfill, a polyfill for CSS Container Queries

Surma has been working on container-query-polyfill, a lightweight polyfill for CSS Container Queries. Let’s take a look at how it works and how it differs from cqfill


🤔 Container Queries?

Container Queries allow authors to style elements according to the size or appearance of a container. For size based container queries this is similar to a @media query, except that it will evaluate against the size of a parent container instead of the viewport.

For style based container queries you conditionally apply styles based on the calculated value of another CSS property.

🤔 Polyfill?

A polyfill is a piece of code (or plugin) that provides the technology that you, the developer, expect the browser to provide natively — What is a Polyfill?



Unlike cqfillwhich was covered here before— this Polyfill for Container Queries:

  • Does not require you to declare a separate Custom Property --css-contain that duplicates the value of the contain property
  • Does not require you to duplicate your @container rule into a @media rule
  • Parses the newer Container Queries Syntax that uses the container-type + container-name properties (shorthanded to container)

Because of this, the polyfill is a drop-in piece of code that will automagically do its thing 🙂

It transpiles CSS code on the client-side and implements Container Query functionality using ResizeObserver and MutationObserver.

Additionally this polyfill does not rely on requestAnimationFrame, which makes it more performant.


Installation + Usage

Installation per NPM:

npm i container-query-polyfill

To use, import the polyfill when no support for container queries is detected:

const supportsContainerQueries = "container" in document.documentElement.style;
if (!supportsContainerQueries) {

Alternatively, load it via a (self-invoking) remote script:

<script src="https://unpkg.com/container-query-polyfill/cqfill.iife.min.js"></script>

Supported are all modern browsers: Chrome 88+, Firefox 78+, and Safari 14+.

👨‍🏫 Unfamiliar with the syntax of CSS Container Queries itself? No worries, you can learn CSS Container Queries here.



I’ve updated my original Container Queries Demo to include this polyfill. Works like a charm:

See the Pen
CSS Container Queries Demo
by Bramus (@bramus)
on CodePen.



Do note that this polyfill comes with a few limitations:

  • Only a subset of queries is supported for now. Specifically, only min-width, max-width, min-height and max-height are supported.
  • Only top-level Container Queries are supported — no nesting of CQs in other Media Queries.
  • Container query thresholds can only be specified using pixels.

These limitations are the result of a design choice:

My aim is to make the polyfill work correctly for the majority of use-cases, but cut corners where possible to keep the polyfill simple(-ish), small and efficient.

Sounds totally reasonable.



🔥 Like what you see? Want to stay in the loop? Here's how:

The Future of CSS: Scroll-Linked Animations with @scroll-timeline (Part 4)

Just before I left on holiday, the 2nd article I wrote for CSS-Tricks got published. In it, I take a look at the JavaScript side of the Scroll-Linked Animations specification.

With WAAPI + ScrollTimeline, a typical “progressbar as you scroll” can be coded like this:

const myScrollTimeline = new ScrollTimeline({
  source: document.scrollingElement,
  orientation: 'block',
  scrollOffsets: [
    new CSSUnitValue(0, 'percent'),
    new CSSUnitValue(100, 'percent'),

    transform: ["scaleX(0)", "scaleX(1)"]
    duration: 1, 
    fill: "forwards", 
    timeline: myScrollTimeline

Find the details in the article.

Scroll-Linked Animations With the Web Animations API (WAAPI) and ScrollTimeline →

20 years of Bram.us

Origin story of how bram.us came to be (and more) — a tale about frietkoten, dial-up internet, static HTML, ICQ cracks, a personal blog gradually evolving into a tech blog, …


Table of Contents

  1. Frietkoten
  2. The very first Cyber Frietkot
  3. Discovering view-source (1997)
  4. Getting started with bramus.be (2001 — 2005)
  5. The Middle Ages: Moving to bram.us and WordPress (2005 — 2011)
  6. The Renaissance of bram.us (2011 — 2019)
  7. Modern Era bram.us (2019 — present)
  8. In Summary


# Frietkoten

In Belgium we have a phenomenon called a “frietkot”. It’s a place where you can buy french fries (which are a Belgian invention!) and (mostly unhealthy) deep-fried snacks to go along with that. Some frietkoten are nothing more than trailers along the road offering only take-away, while other places are proper buildings offering table service.

If you’re from or ever have been to Belgium, this view should be pretty familiar:


# The very first Cyber Frietkot

The year was 1994 (1995?). I was at the age of 11-12, playing with my LEGOs day-in-day-out. Every Wednesday, our family would go to the nearby “John’s Frietshop”. Frietkot-ower John wasn’t only baking fries though; he was also active on the internet — an at the time still upcoming, rare, and expensive thing.

One day, John decided to combine his two passions, and – inspired by the rise of cybercafés — he created the first “Cyber Frietkot”. In his shop, he set up a computer with a permanent internet connection (again: a very rare thing back then), offering free (!) access to the internet to anyone who visited. He even had his own website, preserved here thanks to The Wayback Machine.

John and Katie, from John’s Frietshop

It was then and there that I went online for the very first time. Under the impression that I was going to actually Surf on the World Wide Web I clicked and clicked around until John asked me to stop doing so — he could clearly tell I did not know what I was doing at all.


# Discovering view-source (1997)

Early 1996 we got our first computer at home, a Pentium 100 with 16MB of RAM and a 1.2GB HDD — a blazing fast machine in times when the i486 still was quite common. Although it was a family computer, it quickly turned into my personal playground. Over time, the LEGOs got replaced with clicking around in MS Paint and playing games like Microsoft Pinball and Hover! that came with the OS.

Microsoft Hover!

As it was only until late 1998 that we got dial-up internet at home, I had to go elsewhere to get on the internet. First I frequented the city library, and later on I spent lots of time in the back room of a local computer shop named “Soft Connection”. At that local computer shop, I started to get the hang of this thing called the internet. I got my first Hotmail e-mail address, looked up guides and cheats for the games I was playing back then (Tomb Raider, GTA, Total Annihilation, etc.), etc. It was also there that I got intrigued with how the web was built and eventually discovered view-source.

I collected code-snippets from viewing the source of sites that I visited. Combining those with Frontpage Express (that came with Internet Explorer 4 and 5), I soon created my very first web pages with info about myself. Those pages never got published by the way; they only existed on one of the floppy disks I carried around.


# Early Days (1999 — 2001)

I think it wasn’t until 1999 that I actually published my first personal site. Domains I used over time were:

  • bramus.tk, which acted as an <iframe /> façade for the URL my ISP provided (users.pandora.be/bramus)
  • bramus.f2s.com, which was one of the few hosters that offered FTP+POP3 for free
  • bramus.has.it, which, again, acted as a façade

The earliest version of my site that I could find back (in a working state) is this one from 2001, on the bramus.has.it façade URL. Static HTML with nested tables and Verdana 10 all the way down (and lots of WIP gifs 🙈) if I recall correctly:

Much cringe at 17 year old 2001-me 😬

The year before, I had flunked a year in high school on purpose to switch majors from economics to IT and was taking my first steps into programming with Visual Basic 6.0. I also had a deep interest in ICQ and how it worked. My website acted both as a personal blog + a way to distribute the (simple) applications I had created.

In 2000, I had also built some non-personal websites such as one for the annual party of the local Boy Scouts I was part of, or for some of the games I played and/or modded (GTA!). An example is Jumbot Routing (JBR), which collected Waypoint files for Jumbot, a Half-Life bot.

JBR (2000), as preserved by The Wayback Machine

Pretty weird to look back at it now, knowing that I manually collected all those waypoint files and manually crafted all HTML. Nothing from those sites was stored in a database nor automated, as PHP+MySQL were skills I didn’t have in 2000.


# Getting started with bramus.be (2001 — 2005)

On Nov 19 2001 a classmate of mine told me about a company here in Belgium to cheaply register your own .be domain, no credit card required. It was then that I registered bramus.be and moved my website over to that domain. I like to consider this date — 2001.11.19 — as the formal start date of what eventually became bram.us.

bramus.be got registered on November 19, 2001

Dropping the static websites I built before, I switched to a CMS named PHP-Nuke to send news into the world. Through my website, I mostly distributed ICQ-related cracks, something I had learned over time. These weren’t your typical shareware cracks but cracks that enabled several hidden features such as showing the IP of another user, a way to launch multiple instances, tweaks to allow you to use multiline passwords, etc.

bramus.be (2002), as preserved by The Wayback Machine

On the icq.bramus.be domain I also offered all ICQ related stuff on a single (static) page.

icq.bramus.be (2002), as preserved by The Wayback Machine

In 2002, I started to attend college and finally learned HTML properly and took on CSS. PHP and MySQL were also covered in the curriculum. This led me into creating my own CMS (a thing almost every developer does at a certain point in time, I guess) dubbed “B-Noob”. I dropped in the CMS on bramus.be, removing all old content along with that 🙈.

bramus.be (2003), as preserved by The Wayback Machine

Yes, those are my shoes/legs you see there at the top, with the bottom part of the photo sliced up into small images to build the navigation — that’s how we did those things back then 😅

By the end of 2003 the ICQ-related stuff had moved over to allicq.com (and later mirabiliz.com), a joint collaboration between me and a Russian friend nicknamed koma.

allicq.com (2003), as preserved by The Wayback Machine

With the ICQ stuff moved out of the way, bramus.be remained a place for me to post some personal projects and personal news. Taking inspiration from a band named The Streets, the tagline was “Days in the life of a geezer named Bramus!”. Post frequency remained pretty low as I was busy attending college.

While in college, my interest in building websites only grew. During those three years I realized that the web was my passion and wanted to pursue a career as a web developer. As a result, I did my internship at a local web agency in 2005. The contents of my blog also shifted to posts with web-related news and/or web HOWTOs. A typical kind of post for those days, for example, was one about PNG transparency in IE6 with pngbehavior.htc.

☝️ Did you notice that all these sites can still be accessed in today’s modern browsers, almost 20 years after they were created? That’s what you get for choosing HTML+CSS 😉 Had I chosen to use Flash to create my websites, these versions would no longer be consumable. #futurefriendly

(For the record: The 2000-version of my own site sported a Flash-intro, a mistake I only made once)


# The Middle Ages: Moving to bram.us and WordPress (2005 — 2011)

Early 2005 I got handed over the domain bram.us from a friend in the ICQ scene. I moved everything over to that domain, but put up the blog on the dailies.bram.us subdomain (still powered by my own CMS). In September of that year I also took on my first job as a professional web developer at the web agency where I had done my internship.

A (broken-imaged) dailies.bram.us (2005), as preserved by The Wayback Machine

After a very short break early 2006 I ditched my homegrown CMS and installed WordPress 2.0 in March 2006, which I’ve been using ever since. By switching to WordPress, I gained built-in pingback and RSS feed support and could also use the many themes and plugins it offers.

I went for a (modded) Hemingway Bright theme. Only my main posts — which followed a naming scheme similar to how Scrubs episodes got named — got featured in the main content section. External links were collected using an “elsewhere” plugin and shown in the site’s footer.

bram.us (2006), as preserved by The Wayback Machine

☝️ This concept of having Original Content and an Elsewhere archive still stands today. However, the links to external content are no longer managed using a separate plugin but they now simply have their own dedicated category.

Content shared was mostly TinyMCE related, some PHP-related snippets, … but also some projects such as jsProgressBarHandler (a JS library to show progress bars) and flashLightboxInjector (a PrototypeJS class that bridges the gap between Flash and Lightbox2).

flashLightboxInjector, a bridge between Flash and Lightbox2

These projects and posts were always sparked by things I encountered at work.

In 2008, after three years of being a web developer, my former College called me and offered me a job as a lecturer Web Technologies (and later, by 2011, on Mobile Development too). I took the job and moved to the lovely city of Ghent. With that, I moved over my personal posts to Nieuw in Gent, where I also wrote in Dutch.

In my role as a lecturer I cranked out tons of (web-based) course materials, but it also meant that my share of Original Content posts on bram.us diminished. To counter that I challenged myself with a few projects such as Gowalla MILF (a missing items location finder for Gowalla) and Yummy! (a self-hosted delicious). Not only did these projects keep my practical knowledge up-to-date, they also made me write about them.

Yummy! — A self-hosted delicious


# The Renaissance of bram.us (2011 — 2019)

By 2011 bram.us had kinda come to a stall, and the posts on Nieuw in Gent had shifted to include the sharing of interesting videos and stuff — something way outside of the originally intended scope of personal content. In September 2011 I shut down Nieuw in Gent and decided to blow new life into bram.us. That included switching to a theme named Salju.

bram.us (2012), as preserved by The Wayback Machine

One of the things I liked from the start about the Salju theme is that it shows various icons along to each post based on the tags. A video icon, image icon, link icon, etc. — again a feature still present on bram.us to this day. The tagline also changed to “A rather geeky/technical weblog by Bram(us) Van Damme”, which got tweaked to the current “A rather geeky/technical weblog, est. 2001, by Bramus” over the years.

2011 is also when I created a Twitter account for bram.us, after @mathias asked about it.

I picked up the pace of Original Content posts again over the next few years. The posts were always a mix between backend (PHP/MySQL) and frontend (HTML/CSS/JS), as those where the subjects I taught as a lecturer but which I also encountered through my secondary (after-hours) occupation as a self-employed web developer. I also took my first steps with public speaking at local meetups (Fronteers, JS Belgium, etc.) in 2012.

In 2014 I published a post on Web Development and Education, and not surprisingly one year later I quit as a lecturer. So in 2015 I became a full-time frontend developer at Small Town Heroes (RIP), digging into technologies such as Stylus, ES2015, React, and eventually (the JS side of) React Native. This, again, got reflected in the content that I shared here on bram.us.

During all that time, my secondary occupation — in which I did frontend development, backend development, and mobile development (with Ionic and later React Native) — remained active and kept on expanding. In 2017 I decided to go all-in on it and went full-time freelance.

In 2018 I also spoke at a real conference for the first time, with a talk on future features of JavaScript.

For a short time I also cross-posted some content to Medium to gain more reach. I stopped doing this though, as it only made Medium bigger instead of growing bram.us.


# Modern Era bram.us (2019 — present)

The Salju theme served me well, but in times of Responsive Web Design it started to show its age. I always postponed the (impossible) task of making Salju responsive and in January 2019 — almost 9 (!) years after the term RWD got coined 🙈 — I finally made bram.us responsive. In the end, I went for an inverted approach: instead of making Salju responsive, I made the responsive default WordPress 2019 theme look like Salju.

bram.us (2019), as preserved by The Wayback Machine

This 2019-redesign is still the design you see here today, although it has received a few extra small tweaks over time.

In the early days of bram.us, I added some donate boxes to selected posts asking visitors to “thank me with a coffee”, linking to $3 donation form via PayPal. I don’t think I got over 50 donations in all those years, so in 2020 I decided to drop those and include ads (powered by Carbon) here on bram.us. These ads don’t make me rich, but they do cover the basic costs of hosting this site.

Content-wise I was still switching between backend and frontend, as that’s what I also did in my day-to-day job too as a freelancer. By simply looking at the content posted on my blog, one could tell what I was working on. For example: late 2019/early 2020 the recurring themes were not only frontend but also included Terraform and Google Cloud Run.

By the end of 2020, I decided to focus purely on frontend development again. This regained focus allowed me to write some rather big deep-dive posts, which got shared a lot and even got featured on other prominent web dev blogs.


# In Summary

Looking back at 20 years of bram.us (and more) is looking back at most of my life. Growing up from an 11-year-old boy who traded in his LEGOs for HTML after discovering view-source a few years later. I don’t think John from John’s Frietshop would ever have guessed that one of his young visitors would grow up to become a frontend developer that now fits the senior staff/principle profile.

Although there are many blogs out there that have taken the internet by storm and have gained way more readers in shorter periods of time, I’m very thankful that I was (and still am) able to build up an audience here at bram.us over these past 20 years. I know my pacing of posts isn’t always steady, but I also think that I’m the only person that’s bothered by that.

If you’re doubting about starting your own blog: don’t hesitate and go for it. Even if you post only 1 article a year, it’s worth it. Through blogging, I not only scratch my own itch (“write the blog post you wanted to find yourself”), it’s also what put me on the radar of several companies and colleague web developers. This opened doors and created opportunities for me, both professionally and personally. By re-focussing mainly on frontend late 2020, I’ve also been able to dig deeper into certain CSS topics, expanding my knowledge about those subjects along the way. As a side-effect, I almost doubled my followers on Twitter this year, including some web dev-people who I look up to myself.

While digging up all this old content, I’m happy that an initiative like The Wayback Machine was able to preserve most of it. I’m also very glad always to have managed my content, not relying on external services that get sold and/or shut down behind our backs. Above that, I take pride in the fact that these old sites can still be accessed in today’s modern browsers.

I don’t know what exactly the future of bram.us will hold but do know that this theme of sharing knowledge/content will always be present. I hope that I will long be able to make time for it, and I also hope that you will also stick around to keep reading the posts.


🔥 Like what you see? Want to stay in the loop? Here's how:

Gone Diving

Photo of luggage

As this post gets published I’m on my way to the airport, to go on a 7-day diving holiday in Egypt.

Emperor Angelfish, Pomacanthus Imperator, Red Sea, Egypt
Emperor Angelfish, Pomacanthus Imperator, Red Sea, Egypt

Under normal conditions about 20 of my buddies from my diving club Bubbledivers would join, but this year is different. Because we were not certain if and when we could depart, only 4 of us responded to the late call. As Egypt is rather close by, very affordable, and has a lot to offer I didn’t hesitate to join.

Diving Equipment, sitting on the boat

Later today we’ll land in Hurghada, and immediately hop on our boat which will be our home for the next few days — yes, we’re doing a “liveaboard”. From there we’ll go northbound towards the Suez Gulf. On our way we’ll dive the wrecks at Abu Nuhas, be amazed by the beauty of Ras Mohammed, the wonderful Thistlegorm wreck, etc.

Plan of the Thistlegorm Wreck
Plan of the Thistlegorm Wreck

It’ll be my fourth diving-holiday in Egypt and I’m not bored with it at all … watching the beautiful underwater life and visiting the wrecks is something that brings me joy and inner peace.

Red Sea Clownfish, Two-banded Anemonefish, Amphiprion bicintus, Red Sea, Egypt
Red Sea Clownfish, Two-banded Anemonefish, Amphiprion bicintus, Red Sea, Egypt

To give you an idea of what it’s all about, here’s a video from my last visit in 2018, where we did about the same route as we’ll do now:


CSS Full-Height Slideshow with Centered Slides thanks to grid-auto-rows

Recently I saw this tweet float by:

Now that sounds like a job for CSS Grid to me!

See the Pen CSS Full-Height Slideshow with Centered Slides by Bramus (@bramus) on CodePen.

The code is documented, yet the key part here is to:

  1. Use grid-auto-rows: 100vh (or soon 100dvh) on the container to place the children inside rows of 100vh each.
  2. Use place-items: center; on the container to center the items themselves inside their assigned row.

Because we’re using grid-auto-rows, the code will work with any number of direct children in the container.


🔥 Like what you see? Want to stay in the loop? Here's how:

Remove GoPro Webcam App + Plugin

A while ago I installed the GoPro Webcam App, only to find out my GoPro is too old to work with it. Although I had removed the GoPro Webcam Application itself from /Applications, the GoPro Webcam would still be shown in a list of choices.

To remove the GoPro Webcam from that list, you need to remove the file /Library/CoreMediaIO/Plug-ins/DAL/GoProWebcam.plugin

In Finder, use SHIFT+CMD+G to navigate to /Library/CoreMediaIO/Plug-ins/DAL/ and then remove the file GoProWebcam.plugin

☝️ If you have other Virtual Cameras installed, you’ll also find them in that folder.

Alternatively, run this in Terminal:

sudo rm /Library/CoreMediaIO/Plug-ins/DAL/GoProWebcam.plugin

(Yes, sudo is required here)

Once the file is removed removed you’ll need to restart the affected applications, as they had already loaded that plugin into memory.


Did this help you out? Like what you see?
Thank me with a coffee.

I don't do this for profit but a small one-time donation would surely put a smile on my face. Thanks!

☕️ Buy me a Coffee (€3)

To stay in the loop you can follow @bramus or follow @bramusblog on Twitter.

Media Queries Level 4: Media Query Range Contexts (Media Query Ranges)

A media feature like width can take its value from a range. When used, we prefix these with min- or max- to express “minimum condition” or “maximum condition” constraints.

@media (min-width: 300px) and (max-width: 750px) {

In CSS Media Queries Level 4 these type of Media Features can now be written as a “range context”, which uses ordinary mathematical comparison operators.

@media (300px <= width <= 750px) {

The syntax might seem a little bit odd at first, but for me the trick is to read it as “the width sits in between the two values”

Also works with single values. This example will most likely be more readable to anyone who has (basic) programming knowledge:

/* Old Way */
@media (max-width: 750px) {
/* New Way */
@media (width <= 750px) {


At the time for writing, only Gecko/Firefox supports Range Contexts (ever since Firefox 63). There was some (minor) movement in the Blink/Chromium issue in September, but progress seems to have stalled. No word on WebKit/Safari.

The pen embedded below will indicate if your browser supports Media Query Range Contexts or not:


If you’re using PostCSS, you can use the postcss-media-minmax processor to already write Range Contexts:

npm install postcss-media-minmax
var fs = require('fs')
var postcss = require('postcss')
var minmax = require('postcss-media-minmax')

var css = fs.readFileSync('input.css', 'utf8')

var output = postcss()
console.log('\n====>Output CSS:\n', output)  


🔥 Like what you see? Want to stay in the loop? Here's how:

On Using Custom Properties

On Twitter, John Allsopp recently shared that Flexbox is the greatest thing that happed to CSS. I quickly chimed in with my personal favourite. To me the single greatest addition to CSS over the past 20 years is Custom Properties. They’ve opened up a ton of possibilities, and make us CSS Authors way more productive. The way they are specced — as properties and not simply variables — is pure genius and gives them their true power.


💁‍♂️ I heard spec author Tab Atkins talk about them for the first time at CSS Day 2013, but it wasn’t until Lea Verou‘s talk CSS Variables: var(--subtitle); in 2016 that it really clicked for me.


Over at CSS-Tricks, Chris Coyier writes about the Different Degrees of Custom Property Usage: how do you provide defaults, and how far do you go when splitting things up into several parts?

Regarding the fallback part Chris sticks to what we already know: a 2nd argument to var():

el {
  border: .1em solid var(--color, hsl(200deg 15% 73%));

The interesting part is on splitting things up: do you provide one single hsl color, or do you split out all the individual parts?

el {
  --color: hsl(200deg 15% 73%);
  border: .1em solid var(--color);
el {
  --color-h: 200deg;
  --color-s: 15%;
  --color-l: 73%;
  --color-hsl: var(--color-h) var(--color-s) var(--color-l);
  --color: hsl(var(--color-hsl));
  border: .1em solid var(--color);

Just like Chris I’m more leaning towards using --color here because although these Custom Properties are very nice, they do come at a cost: complexity. As I’ve tweeted back in February:

Thankfully there’s DevTools’s “Computed Styles” pane to somewhat help me out there 🙂

Additionally, settings these individual props on :root and then overriding --color-h for some specific element won’t work, because --color will already have been computed before it is passed down the inheritance tree. This is what Chris calls The Big Gotcha With Custom Properties.


Over at her blog, Lea Verou also wrote about Custom Properties and how to provide defaults. Interestingly, Lea opts to no directly use the passed in Custom Property, but resorts to something she calls “pseudo-private custom properties” that get an extra _ prefix.

My preferred solution is what I call pseudo-private custom properties. You use a different property internally than the one you expose, which is set to the one you expose plus the fallback.

Like so:

el {
  --_color: var(--color, hsl(200deg 15% 73%));
  border: .1em solid var(--_color);

As an extra she also taps into Houdini’s powerful @property to register a default value. Unfortunately Houdini is only supported in Chromium at the time of writing.

🎩 Houdini, ain't that a magician?

Houdini is a set of low-level APIs that exposes parts of the CSS engine, giving developers the power to extend CSS by hooking into the styling and layout process of a browser’s rendering engine. Houdini is a group of APIs that give developers direct access to the CSS Object Model (CSSOM), enabling developers to write code the browser can parse as CSS, thereby creating new CSS features without waiting for them to be implemented natively in browsers.

It really is magic, hence it's name Houdini. I'd recommend this slidedeck and this video to get you started


👀 Don’t try this at home

Because the contents of Custom Properties are not parsed until they are used (using var()), you can store anything in them. That means you could abuse them to create faux Single-line Comments in CSS:


ESNext: Import Assertions (JSON Modules, CSS Modules, …)

Did you know you can import JSON and CSS files just like you import JavaScript Modules? Thanks to Import Assertions, it’s as easy as this:

It’s a regular import statement with an extra assert { "type": … } part added to it. That way you can tell the browser to treat and parse the referenced file to be of a certain type. Once imported you can use the contents further down in your JavaScript code, just like you do with regular ES Module imports.

  "appName": "My App"
import configData from './config-data.json' assert { type: 'json' };

console.log(configData.appName); // "MyApp"


Import Assertions is currently at Stage-3, and is expected to become part of the upcoming ES2022.

💁‍♂️ Stage-3?

The Technical Committee which is concerned with the standardization of ECMAScript (i.e. TC39) has a 5 stage process in place, ranging from stage-0 to stage-4, by which it develops a new language feature.

Stage-3 is the Candidate Stage where the feature is considered complete and only critical changes will happen based on implementation experience. If all goes well the proposal will advance to Stage 4 without any changes, after which it will to become part of the ECMAScript Specification.

Import Assertions are supported in Chromium 91+. For browsers that don’t support it, you can use the es-module-shims polyfill which provides this functionality (and more nice tings)


In the most recent episode of CodePen Radio, just a little after the 10-minute mark, you can see host Chris Coyier demonstrate this (along with importing packages directly from Skypack):

The CSS example Chris shows there uses Lit. In a non-framework environment, add the imported styles using Constructed Stylesheets:

import styles from "./styles.css" assert { type: "css" };
document.adoptedStyleSheets = [...document.adoptedStyleSheets, styles];

💡 Do note that adoptedStyleSheets is only supported in Chromium browsers at the time of writing (CanIUse).


If you’re wondering why we can’t simply import those files directly, without the assertions, Axel Rauschmayer has the answer:

Browsers never look at filename extensions, they look at content types. And servers are responsible for providing content types for files.


With an external server, things are even more risky: If it sends the file with the content type text/javascript, it could execute code inside our app.


And oh, Import Assertions can be used with dynamic import(). Pass in the assertion as the second parameter:

const configData = await import('./config-data.json', {
  assert: { type: 'json' },
console.log(configData.appName); // "MyApp"


A future without a Build System is lurking around the corner, especially when you combine these Import Assertions with a follow-up Evaluator Attributes proposal and Import Maps 🤩



🔥 Like what you see? Want to stay in the loop? Here's how: