Reading List

The most recent articles from a list of feeds I subscribe to.

On Yak Shaving and <md-block>, a new HTML element for Markdown

This week has been Yak Shaving Galore. It went a bit like this:

  1. I’ve been working on a web component that I need for the project I’m working on. More on that later, but let’s call it <x-foo> for now.
  2. Of course that needs to be developed as a separate reusable library and released as a separate open source project. No, this is not the titular component, this was only level 1 of my multi-level yak shaving… 🤦🏽‍♀️
  3. I wanted to showcase various usage examples of that component in its page, so I made another component for these demos: <x-foo-live>. This demo component would have markup with editable parts on one side and the live rendering on the other side.
  4. I wanted the editable parts to autosize as you type. Hey, I’ve written a library for that in the past, it’s called Stretchy!
  5. But Stretchy was not written in ESM, nor did it support Shadow DOM. I must rewrite Stretchy in ESM and support Shadow DOM first! Surely it won’t take more than a half hour, it’s a tiny library.
  6. (It took more than a half hour)
  7. Ok, now I have a nice lil’ module, but I also need to export IIFE as well, so that it’s compatible with Stretchy v1. Let’s switch to Rollup and npm scripts and ditch Gulp.
  8. Oh look, Stretchy’s CSS is still written in Sass, even though it doesn’t really need it now. Let’s rewrite it to use CSS variables, use PostCSS for nesting, and use conic-gradient() instead of inline SVG data URIs.
  9. Ok, Stretchy v2 is ready, now I need to update its docs. Oooh, it doesn’t have a README? I should add one. But I don’t want to duplicate content between the page and the README. Hmmm, if only…
  10. I know! I’ll make a web component for rendering both inline and remote Markdown! I have an unfinished one lying around somewhere, surely it won’t take more than a couple hours to finish it?
  11. (It took almost a day, two with docs, demos etc)
  12. Done! Here it is! https://md-block.verou.me
  13. Great! Now I can update Stretchy’s docs and release its v2
  14. Great! Now I can use Stretchy in my <x-foo-live> component demoing my <x-foo> component and be back to only one level of yak shaving!
  15. Wow, it’s already Friday afternoon?! 🤦🏽‍♀️😂

Hopefully you find useful! Enjoy!

On Yak Shaving and <md-block>, a new HTML element for Markdown

This week has been Yak Shaving Galore. It went a bit like this:

  1. I’ve been working on a web component that I need for the project I’m working on. More on that later, but let’s call it <x-foo> for now.
  2. Of course that needs to be developed as a separate reusable library and released as a separate open source project. No, this is not the titular component, this was only level 1 of my multi-level yak shaving… 🤦🏽‍♀️
  3. I wanted to showcase various usage examples of that component in its page, so I made another component for these demos: <x-foo-live>. This demo component would have markup with editable parts on one side and the live rendering on the other side.
  4. I wanted the editable parts to autosize as you type. Hey, I’ve written a library for that in the past, it’s called Stretchy!
  5. But Stretchy was not written in ESM, nor did it support Shadow DOM. I must rewrite Stretchy in ESM and support Shadow DOM first! Surely it won’t take more than a half hour, it’s a tiny library.
  6. (It took more than a half hour)
  7. Ok, now I have a nice lil’ module, but I also need to export IIFE as well, so that it’s compatible with Stretchy v1. Let’s switch to Rollup and npm scripts and ditch Gulp.
  8. Oh look, Stretchy’s CSS is still written in Sass, even though it doesn’t really need it now. Let’s rewrite it to use CSS variables, use PostCSS for nesting, and use conic-gradient() instead of inline SVG data URIs.
  9. Ok, Stretchy v2 is ready, now I need to update its docs. Oooh, it doesn’t have a README? I should add one. But I don’t want to duplicate content between the page and the README. Hmmm, if only…
  10. I know! I’ll make a web component for rendering both inline and remote Markdown! I have an unfinished one lying around somewhere, surely it won’t take more than a couple hours to finish it?
  11. (It took almost a day, two with docs, demos etc)
  12. Done! Here it is! https://md-block.verou.me
  13. Great! Now I can update Stretchy’s docs and release its v2
  14. Great! Now I can use Stretchy in my <x-foo-live> component demoing my <x-foo> component and be back to only one level of yak shaving!
  15. Wow, it’s already Friday afternoon?! 🤦🏽‍♀️😂

Hopefully you find useful! Enjoy!

Custom properties with defaults: 3+1 strategies

When developing customizable components, one often wants to expose various parameters of the styling as custom properties, and form a sort of CSS API. This is still underutlized, but there are libraries, e.g. Shoelace, that already list custom properties alongside other parts of each component’s API (even CSS parts!). Note: I’m using “component” here broadly, […]

Custom properties with defaults: 3+1 strategies

Reading Time: 4 minutes When developing customizable components, one often wants to expose various parameters of the styling as custom properties, and form a sort of CSS API. This is still underutlized, but there are libraries, e.g. Shoelace, that already list custom properties alongside other parts of each component’s API (even CSS parts!). Note: I’m using “component” here broadly, […]

Custom properties with defaults: 3+1 strategies

When developing customizable components, one often wants to expose various parameters of the styling as custom properties, and form a sort of CSS API. This is still underutlized, but there are libraries, e.g. Shoelace, that already list custom properties alongside other parts of each component’s API (even CSS parts!).

Note: I’m using “component” here broadly, as any reusable chunk of HTML/CSS/JS, not necessarily a web component or framework component. What we are going to discuss applies to reusable chunks of HTML just as much as it does to “proper” web components.

Let’s suppose we are designing a certain button styling, that looks like this:

We want to support a --color custom property for creating color variations by setting multiple things internally:

.fancy-button {
	border: .1em solid var(--color);
	background: transparent;
	color: var(--color);
}

.fancy-button:hover {
	background: var(--color);
	color: white;
}

Note that with the code above, if no --color is set, the three declarations using it will be IACVT and thus we’ll get a nearly unstyled text-only button with no background on hover (transparent), no border on hover, and the default black text color (canvastext to be precise).

That’s no good! IT’s important that we set defaults. However, using the fallback parameter for this gets tedious, and WET:

.fancy-button {
	border: .1em solid var(--color, black);
	background: transparent;
	color: var(--color, black);
}

.fancy-button:hover {
	background: var(--color, black);
	color: white;
}

To avoid the repetition and still ensure --color always has a value, many people do this:

.fancy-button {
	--color: black;
	border: .1em solid var(--color);
	background: transparent;
	color: var(--color);
}

.fancy-button:hover {
	background: var(--color);
	color: white;
}

However, this is not ideal for a number of reasons:

  • It means that people cannot take advantage of inheritance to set --color on an ancestor.
  • It means that people need to use specificity that overrides your own rules to set these properties. In this case this may only be 0,1,0, but if your selectors are complex, it could end up being quite annoying (and introduce tight couplings, because developers should not need to know what your selectors are).

If you insist going that route, :where() can be a useful tool to reduce specificity of your selectors while having as fine grained selection criteria as you want. It’s also one of the features I proposed for CSS, so I’m very proud that it’s now supported everywhere. :where() won’t solve the inheritance problem, but at least it will solve the specificity problem.

What if we still use the fallback parameter and use a variable for the fallback?

.fancy-button {
	--color-initial: black;
	border: .1em solid var(--color, var(--color-initial));
	background: transparent;
	color: var(--color, var(--color-initial));
}

.fancy-button:hover {
	background: var(--color, var(--color-initial));
	color: white;
}

This works, and it has the advantage that people could even customize your default if they want to (though I cannot think of any use cases for that). But isn’t it so horribly verbose? What else could we do?

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:

.fancy-button {
	--_color: var(--color, black);
	border: .1em solid var(--_color);
	background: transparent;
	color: var(--_color);
}

.fancy-button:hover {
	background: var(--_color);
	color: white;
}

I tend to use the same name prepended with an underscore. Some people may flinch at the idea of private properties that aren’t really private, but I will remind you that we’ve done this in JS for over 20 years (we only got real private properties fairly recently).

Bonus: Defaults via @property registration

If @property is fair game (it’s only supported in Chromium, but these days that still makes it supported in 70% of users’ browsers — which is a bit sad, but that’s another discussion), you could also set defaults that way:

@property --color {
	syntax: "<color>";
	inherits: true;
	initial-value: black;
}

.fancy-button {
	border: .1em solid var(--color);
	background: transparent;
	color: var(--color);
}

.fancy-button:hover {
	background: var(--color);
	color: white;
}

Registering your property has several benefits (e.g. it makes it animatable), but if you’re only registering it for the purposes of setting a default, this way has several drawbacks:

  • Property registration is global. Your component’s custom properties may clash with the host page’s custom properties, which is not great. The consequences of this can be quite dire, because @property fails silently, and the last one wins so you may just get the initial value of the host page’s property. In this case, that could very likely be transparent, with terrible results. And if your declaration is last and you get your own registered property, that means the rest of the page will also get yours, with equally potentially terrible results.
  • With this method you cannot set different initial values per declaration (although you usually don’t want to).
  • Not all custom property syntaxes can be described via @property yet.

Bonus: Customizable single-checkbox pure CSS switch

Just for the lulz, I made a switch (styling loosely inspired from Shoelace switch) that is just a regular <input type=checkbox> with a pretty extensive custom property API:

It is using the pseudo-private properties approach. Note how another bonus of this method is that there’s a little self-documentation right there about the component’s custom property API, even before any actual documentation is written.

As an aside, things like this switch make me wish it was possible to create web components that subclass existing elements. There is an existing — somewhat awkward — solution with the is attribute, but Apple is blocking it. The alternative is to use a web component with ElementInternals to make it form-associated and accessible and mirror all checkbox methods and properties, but that is way too heavyweight, and prone to breakage in the future, as native checkboxes add more methods. There is also a polyfill, but for a simple switch it may be a bit overkill. We really shouldn’t need to be painstakingly mirroring native elements to subclass them…

Enjoyed this article and want to learn more? I do teach courses on unlocking the full potential of CSS custom properties. You can watch my Frontend Masters Dynamic CSS course (currently in production), or attend my upcoming Smashing workshop.