Convert a JavaScript Style Object to CSS Custom Properties with this Little Helper Tool

In a Next.js-based project I’m working I noticed this theme.js file which contains a JavaScript object that defines what colors to use and such. As we are only using these values in CSS (via Styled Components) we decided on migrating away from it, and replace it with CSS Custom Properties. For this, I created a little helper tool.

The theme.js contents looked something like this (simplified):

const theme = {
    borderRadius: '15px',
    cardSpacing: {
        horizontal: '20px',
        vertical: '85px',
    colors: {
        primaryBackground: '#FAFAF2',
        primaryButton: '#FFFFFF',
        primaryCard: '#FFFFFF',

        accent: '#2ea7a4',
        text: '#2f2f2f',
        textSecondary: '#707070',
        placeholder: '##959595',

        title: '#000',
        subTitle: 'rgba(255, 255, 244, 0.5)',

export default theme;

Converted to Custom Properties, I want this:

--border-radius: 15px;
--card-spacing-horizontal: 20px;
--card-spacing-vertical: 85px;
--colors-primary-background: #FAFAF2;
--colors-primary-button: #FFFFFF;
--colors-primary-card: #FFFFFF;
--colors-accent: #2ea7a4;
--colors-text: #2f2f2f;
--colors-text-secondary: #707070;
--colors-placeholder: ##959595;
--colors-title: #000;
--colors-sub-title: rgba(255, 255, 244, 0.5);


To translate entries such as theme.colors.primaryCard to --colors-primary-card I decided to create a little helper tool. It automatically adds the required -- prefix, adjusts all camelCase occurrences to kebab-case, and plays nice with nested objects and (simple) arrays.

Perhaps this tool might also be of good use to you 🙂


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 in Times of Container Queries

Max Böck:

With container queries now on the horizon – will we need media queries at all? Is there a future where we build responsive interfaces completely without them?

As Max details we will still need both, but will see a shift from some Media Queries to Container Queries. Good read; I found myself nodding along for the entirety of the post.

Media Queries in Times of Container Queries →

Perfect Tooltips With CSS Clipping and Masking

In this post Louis Hoebregts creates nice tooltips. No, not a typical triangle injected at the bottom but a nice curved one, also letting the image underneath peek through.

For it he uses mask-image consisting of two parts: one rectangle overlaying the top part of the image + one SVG arrow overlaying the bottom part.

.tooltip {
    linear-gradient(#fff, #fff), /* Rectangle */
    url('data:image/svg+xml;utf8,'); /* Bottom  arrow */
    0 0, /* Rectangle */
    50% 100%; /* Bottom arrow */
    100% calc(100% - 18px), /* Rectangle */
    38px 18px; /* Bottom arrow */
  mask-repeat: no-repeat;

See the Pen
Idea #3 – Responsive complex clipping in CSS
by Louis Hoebregts (@Mamboleoo)
on CodePen.

Cranking it up a notch he even creates some nice looking message bubbles.

Perfect Tooltips With CSS Clipping and Masking →

Demystifying styled-components

Josh W. Comeau lays out the details how styled-components works internally.

For so many React devs, styled-components seems kinda magical. It isn’t at all clear how it uses traditional CSS features under-the-hood, and that lack of clarity can cause real problems when things go awry. In this post, we’ll learn exactly how styled-components works by building our own mini-version.

const styled = (Tag) => (styles) => {
  return function NewComponent(props) {
    const uniqueClassName = comeUpWithUniqueName(styles);
    createAndInjectCSSClass(uniqueClassName, styles);

    return <Tag className={uniqueClassName} {...props} />

styled.h1 = styled('h1');
styled.button = styled('button');
// ...And so on, for all DOM nodes!

Demystifying styled-components →

Multi Colored Text with CSS

Nice text-effect demo by Shireen Taj:


Using DevTools you can see that there’s a gradient background set onto the element.

By also applying -webkit-background-clip: text; the gradient is only shown on the text itself.


The demo’s also fun to fork and remix:

Add some Houdini to animate it

See the Pen Multi Colored Text with CSS (Animated with Houdini) by Bramus (@bramus) on CodePen.

Change the type of gradient

Rad fork by Adam Argyle:

See the Pen Multi Colored Text with CSS by Adam Argyle (@argyleink) on CodePen.

Make the center follow the mouse

This one uses a tad of JS to update the Custom Properties:

See the Pen Multi Colored Text with CSS (conic gradient + follow mouse) by Bramus (@bramus) on CodePen.

Using a radial-gradient which gets a bit bouncy

See the Pen Multi Colored Text with CSS (radial gradient + follow mouse) by Bramus (@bramus) on CodePen.

Creating a CSS flashlight

It steers away from the multi-color aspect, but the rest of the code is the same:

See the Pen CSS Flashlight Text by Bramus (@bramus) on CodePen.


CSS @​supports rules to target only Firefox / Safari / Chromium

Yesterday I took some time to rework my Houdini-powered CSS Gradient Border Animation Demo to include a fallback for non-Houdini browsers.

The plan of attack was pretty straightforward:

  • Manual frame-by-frame animations for non-Houdini browsers
  • Automagic Houdini-powered animations for browser with @property support

Only problem with that approach is that there’s currently no way to use @supports to directly detect whether a browser supports Houdini’s @property or not, so I got creative with @supports


🎩 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


Table of Contents

  1. What I want
  2. Getting creative with @supports
  3. The @supports rules
  4. Combined Demo
  5. In Closing


# What I want

Ideally I want to check for support for @property using something like this:

/* Syntax Proposal */
@supports (@property) {

/* Syntax Proposal, Expanded to check for certain descriptor support */
@supports (@property { syntax: "<angle>" }) {

/* Alternative Syntax Proposal */
@supports descriptor(@property, syntax: "<angle>") {

Unfortunately this is currently not possible and considered a shortcoming of CSS @supports. The issue is being discussed in CSSWG Issue 2463.


# Getting creative with @supports

💁‍♂️ Note that I’ll be using browser names here (e.g. Firefox), even though the statements apply to their underlying rendering engines (e.g. Gecko). And when talking about “Chromium” I mean all browsers that are based on it (e.g. Google Chrome, Microsoft Edge, Brave, etc.)

As Chromium (Blink) currently is the only browser that supports @property, we need to create a @supports rule that targets Chromium only. While at it let’s also create @supports rules to target only Safari (WebKit), Firefox (Gecko), or a combination thereof.

Looking at what each browser uniquely supports, these three rules can be used as a starting point:

  1. Only Firefox supports -moz-appearance: none
  2. Only Safari supports the :nth-child(An+B [of S]?) selector.
  3. Both Chromium and Firefox support contain: paint

👉 By combining/excluding these rules you can target a set of browsers, or only one specific one.

# UPDATE 2021.06.29 — It came to my attention that it’s possible to detect @property support by checking for background: paint(worklet); support:

The further contents of this post still stand though, as the separate @supports rules described below do their thing. However, if you want to check for Houdini @property support, use the one mentioned above.


# The @supports rules

🚨 Warning: the following @supports rules are a hacky workaround that reeks a lot like browser sniffing. They’re very fragile as rendering engines may add support for those properties/selectors over time. Things can — and will — break in the future. Be ye warned.

# Targeting Firefox Only

Firefox is the only browser that supports -moz-appearance: none

/* Firefox Only */
@supports (-moz-appearance: none) {

# Targeting Not-Firefox (e.g. Chromium + Safari)

By negating the selector only Firefox supports you can target Chromium + Safari:

/* Chromium + Safari */
@supports (not (-moz-appearance: none)) {

# Targeting Safari Only

Safari is the only browser that supports the complex :nth-child(An+B [of S]?) selector — which itself allows you to create a :nth-of-class-like selector — so you can rely on that. Using selector() function in @supports we can check whether the browser supports it or not.

/* Safari Only */
@supports selector(:nth-child(1 of x)) {

Note that MobileSafari supports this selector() function only since iOS 14.6 which, at the time of writing, is only the very latest non-beta version.

If you also want to cater for earliers MobileSafari versions, add an extra check for (-webkit-touch-callout: none) which only MobileSafari supports:

/* (Safari + MobileSafari >= 14.6) or (All MobileSafari versions) */
@supports  (selector(:nth-child(1 of x))) or (-webkit-touch-callout: none) {

The extra parens around selector() are crucial here. When omitted MobileSafari prior to version 14.6 breaks on parsing the @supports rule, ignoring it entirely.

# Targeting Not-Safari (e.g. Chromium + Firefox)

You could negate the rules above to target Chromium and Firefox, but instead I chose to check for support for contain: paint which both browsers support:

/* Chromium + Firefox */
@supports (contain: paint) {

The reason why I choose this selector becomes clear in the next part 😉

# Targeting Chromium Only

To target Chromium only you can select both Chromium and Firefox (using @supports (contain: paint)), and then exclude Firefox from there (using @supports (not (-moz-appearance: none))).

Combined you get this @supports rule:

/* Chromium Only */
@supports (contain: paint) and (not (-moz-appearance: none)) {

# Targeting Not-Chromium (e.g. Safari + Firefox)

As @supports understands OR-logic, you can combine the rules for both Safaris and Firefox into one:

@supports (-webkit-touch-callout: none) or (selector(:nth-child(1 of x))) or (-moz-appearance: none) {


# Combined Demo

Below is a combined demo. The background-color of the body should be:

  • Chromium: blue
  • Firefox: lime
  • Desktop Safari: red
  • Mobile Safari: orange

See the Pen CSS @support rules to target only Firefox / Safari / Chromium by Bramus (@bramus) on CodePen.

Additionally you can also check this combined image (“cheat sheet”):


# In Closing

I’m glad I could flesh things out and create @supports rules to target the three major rendering engines. Let’s hope we can drop these workarounds soon, when CSSWG Issue 2463 gets resolved (and implemented) 🙂


To help spread the contents of this post, feel free to retweet its announcement tweet:

Follow @bramus (= me, the author) and/or @bramusblog (= the feed of this blog) on Twitter to stay-up-to date with future posts. RSS also available.


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.

TablesNG — Improvements to <table> rendering in Chromium

Shipped with Chromium 91 is TablesNG, a under-the-hood rewrite regarding tables.

The old table implementation — from WebKit before — was very old, and limited further development. The rewrite that landed emphasizes correctness, fixing 72 bugs in one sweep.

Let’s take a look a some of the to me outstanding fixed bugs from the developer notes, and what they allow us to do.


Table of Contents

  1. Subpixel geometry
  2. <TD> supports orthogonal writing modes
  3. visibility: collapse; for table columns
  4. Sections/rows can have position: that is not static
  5. In Closing


# Subpixel geometry

With Subpixel Geometry three cells in a 100px table will now be given a width of 33.333333px each. Before the first two cells would get a width of 33px, and the third one would get a width of 34px.


# <TD> supports orthogonal writing modes

This allows us to have rotated table headers in tables, without needing to resort to extra spans and CSS rotations:

See the Pen TablesNG — TD supports orthogonal writing modes by Bramus (@bramus) on CodePen.

The code looks like this:

thead th:not(:first-child) {
	writing-mode: vertical-lr;    /* Switch to vertical writing mode, rendering the label text at 90 degrees */
	text-align: right;            /* Align labels to visual bottom edge */
	padding-top: 1em;             /* Add padding to visual top */

If you’re not using Chromium 91+, you can check this visual reference:

To me the labels are visually rotated in the wrong direction though: they are rotated 90 degrees to the right. To make it visually more pleasing (to me) I want them to be rotated 90 degrees tot eh left (e.g. -90 degrees). Changing from vertical-lr to vertical-rl has no effect on this, but thankfully we can shake some more CSS Magic out of our sleeves using scale(-1):

thead th:not(:first-child) {
	writing-mode: vertical-lr;     /* Switch to vertical writing mode, rendering the label text at 90 degrees */
	transform: scale(-1);          /* Flip it 180 degrees */
	text-align: left;              /* Align labels to visual bottom edge */
	padding-bottom: 1em;           /* Add padding to visual top */

See the Pen TablesNG — TD supports orthogonal writing modes (rotated) by Bramus (@bramus) on CodePen.


# visibility: collapse; for table columns

This allows us to hide entire columns by setting visibility: collapse; on a column in a <colgroup>

col.hidden {
	visibility: collapse;

See the Pen TablesNG — visibility: collapse; for table columns by Bramus (@bramus) on CodePen.

If you’re not using Chromium 91+, you can check this visual reference:


# Sections/rows can have position: that is not static

This one is a huge addition, as it — finally — allows us to set position: sticky on table headers!

thead {
  position: sticky;
  top: 0;

Yes, that piece of code will work as expected 🥳

Over at CSS-Tricks, Chris Coyier has done a write-up + demo:

See the Pen Sticky Table Headers and Footers by Chris Coyier (@chriscoyier) on CodePen.

You can also set position: sticky; on individual <th> elements instead of the <thead> if you want.


# In Closing

While these changes are very welcome, there unfortunately are some compatibility issues: Safari still uses the “old” tables rendering engine and drags every other browser down with it that way. Firefox led the way before regarding table rendering, and can quite keep up with Chromium’s TablesNG.


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

The CSS Podcast

I don’t follow many podcasts to be honest. I can count the number of followed ones on one hand: exactly 5. As I tend to listen to music throughout the day, 3 out of those 5 are music-related podcasts, featuring mixes by DJs — Great way to discover new music (still miss you, BeyondJazz!).

When it comes to non-music related podcasts, I’m actively listening to The CSS Podcast hosted by Una Kravets and Adam Argyle

Cascading Style Sheets (CSS) is the web’s core styling language. For web developers, It’s one of the quickest technologies to get started with, but one of the hardest to master. Follow Una Kravets and Adam Argyle, Developer Advocates from Google, who gleefully breakdown complex aspects of CSS into digestible episodes covering everything from accessibility to z-index.

I chimed in at the end of its first season and have been listening every since, very good content. It’s quite fast-paced, so you’ll need to direct your full attention to it though.

The CSS Podcast →
The CSS Podcast Season 2, Episode 16: Scroll-Timeline →


The latest episode of the show is about CSS Scroll-Timeline, something I’ve been writing, tweeting, and speaking about a lot ever since I discovered it mid-January (after seeing a tweet by Adam on it).

🤩 I’m very excited about this episode, especially since my talk covering Scroll-Timeline was picked up and got featured in it.

Here are the direct links to the visualisations mentioned in the episode:

💁‍♂️ More info, context, and a ton demos can be found in my talk on the subject.