I wanted to experiment with the new CSS function light-dark() and get a sense of how to use it in a CSS architecture of nested (web) components. I think it’s going to be a powerful tool in the new responsive world of component architecture but I don’t want to recommend something unless I have experience with it in a project first.

My first pass was to add light-dark() to my components…

/* global.css */
:root {
  --dark: #000;
  --light: #fff;
}

/* Inside <my-component>'s Shadow DOM */
:host {
  background-color: light-dark(var(--light), var(--dark));
  color: light-dark(var(--dark), var(--light));
}

But if every component is in charge of it’s own light-dark() handling for border, background, and color on every element… the codebase will get messy managing dark mode in a lot of different places, leading to a lot of inconsistencies over time. A more elegant solution for me would be to handle this job in a single location at the root scope level and leverage the cascade a bit.

:root {
  color-scheme:  light dark;
  --surface-color: light-dark( #fff, #000 );
  --text-color: light-dark( #000, #fff );
}

The nice thing about using light-dark() at the root token level is your components can be dumber. You provide default light-dark experience and, like good children, your components abide in their parent’s decision. Of course, due to the nature of CSS custom properties, your components aren’t locked into the system and your component level styles can opt out (read: not include) or override the global variables if necessary.

/* Inside <my-component>'s Shadow DOM */
:host {
  background: var(--surface-color);
  color: var(--text-color);
}
/* this is a real example from my past */
:host[theme="lunar-new-year"] {
  --surface-color: red;
  --text-color: black;
}

At this point in the experiment I was pleased with the results… until I deployed it to production. I overestimated the browser support for light-dark() and it didn’t work on my phone running Safari 17.4 (but it’s coming in Safari 17.5). I replicated the issue by changing light-dark() to light-d0rk() to verify and fixed it by adding a tried-and-true CSS @supports query.

:root {
  --surface-color: #000;
  --text-color: #fff;

	/* NOTE: For Safari 17.4 (2024-05) */
  @supports (color: light-dark(black, white)) {
    color-scheme:  light dark;
	  --surface-color: light-dark( #fff, #000 );
	  --text-color: light-dark( #000, #fff );
  }
}

Now Safari 17.4 and other browsers that lack support will only have a dark theme and newer browsers will get the light-dark() enhancement. I also threw a little NOTE: and datestamp in there so future versions of me will know when and why I built this fence.