Dark mode in 5 minutes, with inverted lightness variables

Lea Verou shows a method to implement dark mode, not by swapping entire colors, but by simply changing their lightness

The basic idea is to use custom properties for the lightness of colors instead of the entire color. Then, in dark mode, you override these variables with 100% - lightness. This generally produces light colors for dark colors, medium colors for medium colors, and dark colors for light colors, and still allows you to define colors inline, instead of forcing you to use a variable for every single color.

For best results she also taps into LCH colors.

LCH is a much better color space for this technique, because its lightness actually means something, not just across different lightnesses of the same color, but across different hues and chromas.

Dark mode in 5 minutes, with inverted lightness variables →

Style Pseudo-elements with Javascript Using Custom Properties

Over at CSS { In Real Life }, author Michelle Barker has detailed a clever way to style pseudo-elements (such as ::before and ::after) through JavaScript.

In Javascript we have a few ways of selecting elements, but we can’t directly target pseudo-elements. […] Luckily, CSS custom properties can help.

👉 If you set a custom property on the element that “owns” the pseudo-element the pseudo-element itself can pick it up, thus enabling a way to style it.

Quick Tip: Style Pseudo-elements with Javascript Using Custom Properties →

Debug/Inspect z-index stacking with the “CSS Stacking Context Inspector” DevTools extension

The Stacking Contexts Inspector is a DevTools extension for Google Chrome that allows you to analyse the stacking contexts available on a webpage. This extension will add a new panel to the DevTools and a new sidebar on the elements panel.

Handy for when you’re having stacking issues.

CSS Stacking Context Inspector →
Stacking Contexts Inspector →

☝️ If you’re running “Edgium”, you can use it’s built-in 3D View to visualize the stacking contexts.

Hat tip, Josh!

CSS Container Queries: A First Look + Demo

Back in November 2020 it was announced that Chromium would experiment with Container Queries — back then just a proposal but earlier this year (February 2021) adopted to become part of the CSS Containment Module Level 3 Specification.

Just before the weekend a first version of this experimental implementation landed in Chromium Canary for us to play with (behind a flag). Let’s take it for a test drive …

👨‍🔬 The CSS features described in this post are still experimental and not finalized at all! If you’re feeling adventurous you can play with these new features today, but you’ll need at least Chromium 91.0.4459.0 with the #enable-container-queries flag enabled through chrome://flags.

~

Wanting to test Container Queries out I quickly threw a demo together using a classic card component. By default our component shows and image on top and a description below that. If enough space becomes available, they will be shown next to each other. Should even more space become available, then the image will grow even more.

In the recording below you can see the different layouts we want to achieve:

~

The markup for all those cards is the same and is pretty straightforward. Only extra thing I’ve added is an extra wrapper div .animalcard-wrapper so that our container queries will play nice when being used inside CSS Grid

<div class="animalcard-wrapper">
	<div class="animalcard">
		<div class="animalcard__image">
			…
		</div>
		<div class="animalcard__description">
			…
		</div>
	</div>
</div>

The default layout of our card uses CSS Grid to position the image and the description:

/* SMALL LAYOUT: Image stacked on top of Description */
.animalcard {
	display: grid;
	grid-template: "image" "description" / 1fr;
	gap: 1em;
	padding: 1em;
}

To be able to use Container Queries, we first need to create a Containment Context (Container Root) on the .animalcard-wrapper. We instruct the browser to keep track of the inline-size, which translates to the width, as we will be changing the layout of its children based on that dimension.

/* Container Queries: Create Container Root */
.animalcard-wrapper {
	contain: layout inline-size;
}

With this Container Root in place, we can now add extra styles to apply when the Container Root reaches a certain width

/* MEDIUM LAYOUT: Image next to Description (1fr each) */
@container (min-width: 38rem) {
	.animalcard {
		gap: 2em;
		padding: 2em;
		grid-template: "image description" / 1fr 1fr;
	}

	.animalcard__description {
		text-align: left;
	}
}

/* LARGE LAYOUT: Large Image next to Description */
@container (min-width: 70rem) {
	.animalcard {
		grid-template-columns: 2fr 1fr;
	}
}

~

All together our demo finally becomes this:

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

😊

☝️ If you’re looking for more demos, Miriam Suzanne is collecting a bunch in this CodePen Collection. Be sure to check out Una‘s Episode Card for The CSS Podcast

~

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.

CSS Logical Properties Are the Future of the Web & i18n

Linked below is yet another article urging one to use CSS Logical Properties. What caught my eye in this one is the fact that author Daniel Yuschick reworked his personal website to use them, as shown in this video below.

Impressive!

CSS Logical Properties Are the Future of the Web & I18N →

~

Progress Nav with IntersectionObserver

In Table of Contents with IntersectionObserver on CSS-Tricks, Chris Coyier talks about sticky table of contents on long pages whose active state updates as you scroll. When talking about those, you can not not mention the wonderful Progress Nav by Hakim El Hattab.

~

As Hakim’s demo from 2017 (!!) does not use IntersectionObserver — which was in it’s very early stages back then — Chris hinted that someone should make a version that uses it. Anders Grimsrud took up the challenge, and built it:


See the Pen
Hakim’s Progress Nav Concept using the Intersection Observer API
by Anders Grimsrud (@agrimsrud)
on CodePen.

Cool! 🙂

💡 Be sure to check Hakim’s “Building Better Interfaces” talk, which is full of inspiration to make your interfaces more delightful. Recommended stuff!

😊 I was very happy to see Chris including my 2020 Smooth Scrolling Sticky ScrollSpy Navigation in his post too.

Building a Settings Component

In this episode of GUI Challenges, Adam Argyle builds a Settings Component with sliders and checkboxes.

In this post I want to share thinking on building a Settings component for the web that is responsive, supports multiple device inputs, and works across browsers.

Tons of CSS-knowledge in there:

Building a Settings Component (Demo) →Building a Settings Component (Writeup) →

Three important things you should know about CSS :is()

Back in 2019 I shared how the CSS :is() selector will simplify things when writing CSS. What I didn’t know back then, and only have learnt quite recently, are these three important facts about CSS :is():

  1. The selector list of :is() is forgiving
  2. The specificity of :is() is that of its most specific argument
  3. :is() does not work with pseudo-element selectors (for now)

Let’s take look at what that means.

~

# 1. The selector list of :is() is forgiving

What if you include a selector that’s pure gibberish inside :is()? Will the rule-set be declared invalid or what?

p:is(.foo, #bar, $css:rocks) {
  color: hotpink;
}

Thankfully :is() is very forgiving here: the $css:rocks part — which in itself is an invalid CSS selector — will simply be ignored, while keeping the rest of the selector list in place.. So using the snippet above, both p.foo and p#bar will be colored hotpink. Yay!

Should you try this without :is(), the whole rule-set would become invalid. In the snippet below, none of the paragraphs will be hotpink due to that faulty $css:rocks selector invalidating the whole selector list.

p {
  font-family: sans-serif;
}

p.foo, p#bar, p$css:rocks { /* ❌ This whole rule-set is declared invalid */
  color: hotpink;
}

Note that the paragraphs will have font-family: sans-serif applied, as it’s only the invalid rule-set that ends up being ignored.

🔮 In the near future this latter behavior will no longer be the case as the CSSWG intends to modify these rules such that an invalid selector will simply be ignored rather than invalidating the whole selector list. Relevant CSS WG Issue: 3264

~

# 2. The specificity of :is() is that of its most specific argument

Take the code below. What color will p.foo have?

p:is(.foo, #bar, $this:invalid) {
  color: hotpink;
}

p.foo {
  color: lime;
}

I won’t be lime but hotpink! This because when calculating the specificity, the specificity of the :is() pseudo-class is replaced by the specificity of its most specific argument.

  • p.foo has a specificity of (0,1,1)
  • p:is(.foo, #bar) has a specificity of (1,0,1)

As p:is(.foo, #bar) has a higher specificity, it will “win” here.

☝️ The :not() and :has() pseudo-classes also have their specificity calculated this way.

☝️ If you don’t want to be affected by this, you can use :where() instead of :is(). It works in the same way :is() does, but will always have a specificity of 0. You can cleverly wrap this around other selectors to undo their specificity. Think of :where(:not(…)) for example.

😬 Although I wouldn’t recommend it, you could perfectly do something like :is(#bump#up#the#spe#ci#fi#city#yo, .foo) to override selectors more specific than .foo

~

# 3. :is() does not work with pseudo-element selectors (for now)

If you read up on the definition of :is() you’ll read that it accepts a “Selector List” which is a comma-separated list of simple, compound, or complex selectors.

When looking up simple selectors, there’s an interesting thing to note:

A type selector, universal selector, attribute selector, class selector, ID selector, or pseudo-class is a simple selector.

Do you see it? Here: pseudo-element selectors are not included in this list. As a result, :is() does not play nice with pseudo-element selectors such as ::before, ::after, ….

🔮 In the future this will become possible though, but not just yet. Relevant CSSWG Issue: 2284

~

Knowing these three facts about :is() will surely help you understand it better and make using it more fun!

See the Pen
The CSS :is() pseudo-class. What color will .foo have?
by Bramus (@bramus)
on CodePen.

If you understood well, the Pen above should hold no secrets to you anymore 🙂

~

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.

HSL: a color format for humans

Over at the Cloud Four blog, Paul Hebert digs into HSL colors

Colors on the web are confusing — but they don’t have to be! The HSL format makes it easy for humans and computers to work with color.

Included are some nice CodePen-hosted Color Pickers, such as this one:

See the Pen HSLA Color Picker using Custom Props by Paul Hebert (@phebert) on CodePen.

HSL: a color format for humans →

☝️ Remember that nowadays you can drop the Functional Color Notation (e.g. hsla(180, 50%, 50%, 100%)) and use the Space-Separated Color Notation (e.g. hsla(180 50% 50% / 100%)) instead. It’s supported in all major browsers.