Frontend Masters Boost RSS Feed https://frontendmasters.com/blog Helping Your Journey to Senior Developer Fri, 22 Aug 2025 14:57:24 +0000 en-US hourly 1 https://wordpress.org/?v=6.8.3 225069128 Quick Dark Mode Toggles https://frontendmasters.com/blog/quick-dark-mode-toggles/ https://frontendmasters.com/blog/quick-dark-mode-toggles/#comments Fri, 22 Aug 2025 14:57:23 +0000 https://frontendmasters.com/blog/?p=6826 I’ve moved off of Arc browser, but it had a browser-level feature of being able to toggle beween light and dark mode that I liked. I still have some muscle memory for that, so in the time I’m spending back on other Chrome-based browsers, I was looking for another browser-level toggle for it.

Chrome

Chrome can do it, but it’s a little buried in DevTools. The main setting is under the “Rendering” tab (which I always remember how to get to be going under the main Console tab then pressing ESC, then choosing Rendering in the three-dot menu). In there, you’ll see an option for Emulate CSS media feature prefers-color-scheme where you can select light or dark.

Screenshot of Chrome DevTools showing the 'Rendering' tab with options for emulating CSS media feature prefers-color-scheme.

Alternatively, it’s a bit quicker to use the Command Palette (Command-Shift-P) and starting typing emulate and you’ll see a quick option to toggle to the value you want.

Screenshot of Chrome DevTools Command Palette with options for emulating CSS prefers-color-scheme.

Or, use the little paintbrush icon under the Elements tab then in the Styles section.

Screenshot of a browser's DevTools showing the Styles panel, with options to toggle between light and dark color schemes.

The Non-Standard Chrome Thing

See in the first screenshot there is a setting called Enable automatic dark mode as well. I have made this mistake recently of thinking this was how you flip on dark mode testing, and it was pointed out to me, hence this blog post. That feature is some Chrome-specific thing which takes a page which may have been designed as light-mode-first (or only) and forces a dark mode on it whether it was designed to or not.

Screenshot of a coding environment showing the Chrome DevTools interface with the Console and Rendering tabs open, highlighting options for enabling automatic dark mode and simulating CSS media features.
This blog post in the WordPress editor with forced dark mode. This page doesn’t support a dark mode normally.

The Enabled automatic dark mode feature isn’t particularly relevant. I think it’s some Chrome team idea that hasn’t gone anywhere yet. It’s not something you’d really need to test with/for in my opinion.

Firefox

Firefox has some mutually exclusive buttons in DevTools under the Inspector panel. The little sun icon is simulating preferring light mode and the moon(ish) icon is preferring light mode. Or they can both be off defaulting to the system.

Screenshot of Chrome DevTools, showing the Inspector panel with options for toggling dark color scheme simulation for web pages.

Safari

In Safari DevTools under the Elements panel there is an icon to control the color modes as well as some other accessibility media preference simulators.

A screenshot of browser developer tools displaying the appearance settings, specifically the color scheme set to dark, under the Elements tab.

OS

Other than the non-standard Chrome thing, all this is doing is pretending as if the user has set one of the modes specifically at their OS level.

A user interface displaying color mode options for appearance settings, including Light, Dark, and Auto modes.
The macOS version of setting Dark Mode.

Personally, I find it a little cumbersome to need DevTools or go into System Settings to test dark/light mode on sites. It was nice in Arc to have a simple command to do it, but as I write, color modes aren’t really a browser-level settings for the most part (let alone site-specific settings).

So I was happy to find Nightfall, a little macOS menu bar utility for swapping out color modes.

There is something extra nice about doing the toggling at the system level as it feels like the “real” way to do it.

]]>
https://frontendmasters.com/blog/quick-dark-mode-toggles/feed/ 3 6826
One Thing @scope Can Do is Reduce Concerns About Source Order https://frontendmasters.com/blog/one-thing-scope-can-do-is-reduce-concerns-about-source-order/ Thu, 20 Mar 2025 15:32:23 +0000 https://frontendmasters.com/blog/?p=5434 There is an already-classic @scope demo about theme colors. Let’s recap that and then I’ll show how it relates to any situation with modifier classes. (The @scope rule is a newish feature in CSS that is everywhere-but-Firefox, but is in Interop 2025, so shouldn’t be too long to be decently usable.)

There are lots of different ways to implement color themes, but imagine a way where you do it with class names. I think it’s a valid way to do it, rather than, say, only responding to system preferences, because the classes might give you some on-page control. So you apply a theme at the top level. Then perhaps you have some elements that have other themes. Perhaps your site footer is always dark.

<body class="theme-light">
  <main>
    ...
  </main>
   
  <footer class="site-footer theme-dark">
    &copy;2025 <a href="/">Frontend Masters</a>
  </footer>
</body>

You set up those classes with colors, including other elements that need colors depending on that theme.

.theme-dark {
  background: black;
  color: white;

  a {
    color: #90caf9;
  }
}

.theme-light {
  background: white;
  color: black;

  a {
    color: #1976d2;
  }
}

There is already a problem with the HTML and CSS above.

The <a> in the footer will have the color of the light theme, not the dark theme. This is that classic @scope demo you’re likely to see a lot (sorry). This is because of source order. The selector .theme-light a has the exact same specificity as .theme-dark a but the light theme comes after so it “wins”.

One change to the above CSS will fix this:

@scope (.theme-dark) {
  background: black;
  color: white;

  a {
    color: #90caf9;
  }
}

@scope (.theme-light) {
  background: white;
  color: black;

  a {
    color: #1976d2;
  }
}

This is referred to as proximity. It’s like a new part of the cascade (Bramus has a nice diagram here). Above, because the specificity is the same in both cases, the closer-in-the-DOM proximity “wins”. And closer meaning “fewest generational or sibling-element hops”. Like:

So, appropriately, the link styling nested under @scope (.theme-dark)wins because the specificity is the same but the proximity of the theme-dark class is closer.

What I like about this is that now the source order for those themes doesn’t matter. That’s nice as sometimes that’s hard to control. A good bundler should maintain source order after building, but perhaps these “variation classes” are in different files and the way they get built and loaded isn’t entirely predictable. Perhaps some lazy loading gets involved or the built files are purposefully spit or who-knows-what. I’ve seen too many “it’s fine on dev but broken on prod” bugs for one lifetime.

Color themes was just an excuse to look at variation classes.

Here’s another example:

.card-big {
  padding: 2rem;
}

.card {
  padding: 1rem;
}

Without even looking at HTML, you might consider this a “mistake” because, probably, card-big is a variation class of .card, except the thing that should be an override (the padding) won’t actually be overridden because of source order and equal specificity. I’d guess we all have some muscle memory for just ordering variation classes properly so this isn’t a problem, but it’s not ideal to me that we have to remember that, and that build tools and loading strategies might interfere anyway.

Here’s some real-world-ish CSS I was playing with where I could use @scope to put my variation class first without worry:

@scope (.card-big) {
  :scope {
    grid-column: span 2;
    display: flex;

    img {
      width: 50%;
      height: 100%;
    }
  }
}

.card {
  img {
    width: 100%;
    aspect-ratio: 16 / 9;
    object-fit: cover;
  }
}

Now if I’ve got two cards…

<div class="card">
  ...
</div>

<div class="card card-big">
  ...
</div>

I can rest easy knowing the .card-big styles will apply and appropriately override because of the proximity of the class to the things I want to style (including itself!)

]]>
5434
Tweaking One Set of Colors for Light/Dark Modes https://frontendmasters.com/blog/tweaking-one-set-of-colors-for-light-dark-modes/ https://frontendmasters.com/blog/tweaking-one-set-of-colors-for-light-dark-modes/#respond Mon, 25 Nov 2024 19:44:01 +0000 https://frontendmasters.com/blog/?p=4579 Often when dealing light/dark modes, you’re thinking about entirely changing colors. A light color becomes a dark color and vice versa. That’s the nature of the situation!

But think about the oranges. The pinks. The greens and blues. Those are colors that have a decent chance of working OK in both a dark and light theme.

Here’s the thing:

  • If you’ve got a pink that looks great on light, it probably should be brightened a bit on dark.
  • Or, if you’ve got a pink that looks great on dark, it probably should be darkened a bit on light.

An example of something like this is syntax highlighting colors so that’s what we’ll do here. But it could be anything: illustration parts, your <hr /> elment, buttons, whatever.

Here’s a pink:

.some-tag {
  /* pink! */
  color: oklch(0.75 0.2 328);
}

Which looks fine on white (and has accessible contrast):

Set it on black, and it still has accessible contrast, and looks… also fine.

But I think we could do better. We certainly could have picked a color that didn’t meet accessible contrast requirements. But also, I think it would look a bit nicer if that pink was darkened up a smidge on light and lightened up a bit dark.

Fortunately we’ve set the color in OKLCH which has perceptually uniform brightness. Meaning if we have a bunch of colors, and we notch them all up in brightness by the same number, they will all appear about the same amount brighter to our human eyes.

A way to approach this is to pick a number of how much we want to bright, darken (or both). Like:

html {
  color-scheme: light dark;

  --colorAdjuster: -0.1;
  @media (prefers-color-scheme: light) {
    --colorAdjuster: 0.133;
  }
}

Then use this number to adjust our color(s):

.some-tag {
  color: oklch(calc(0.75 - var(--colorAdjuster)) 0.2 328);
}

Here’s an example doing that with syntax highlighting. You’ll need to adjust your color mode preference to see the colors change.

Here’s both modes as images:

In my opinion, these colors have a very similar feel, but each color, from a base, was darkened a bit in light mode and lightened a bit in dark mode.

]]>
https://frontendmasters.com/blog/tweaking-one-set-of-colors-for-light-dark-modes/feed/ 0 4579
No Fuss Light/Dark Modes https://frontendmasters.com/blog/no-fuss-light-dark-modes/ https://frontendmasters.com/blog/no-fuss-light-dark-modes/#comments Mon, 18 Nov 2024 20:14:58 +0000 https://frontendmasters.com/blog/?p=4412 There was some user feedback this site should have a light mode instead of all-dark-all-the-time. The theme of this site is simple enough that some quick design tweaks is all it took.

But here’s the thing: it just relies on your system preference.

(Or whatever is controlling what prefers-color-scheme is returning in your browser. I use the browser Arc sometimes and it’s Light/Dark modes override what is set in my System Preferences. It can get confusing.)

It’s more on-trend when offering color modes to offer users a toggle. That way users can easily choose between which they prefer without ever leaving your site. And they might have a preference that is the opposite of what their overall system preference is. Those are perfectly fair and legitment things.

Those things also complicate the work.

I think it’s also perfectly fair to make development choices that are purposefully uncomplicated.

In this case, choosing to support light and dark modes entirely within CSS alone was the uncomplicated choice.

The Basics

It really is just this:

html {
  --brand-red: oklch(0.67 0.24 27.98);

  --bg: black;
  --text: #ffdbdb;

  --link-color: #4ac6ff;
  --link-color-hover: #9ce0ff;
  --bright-color: white;
  --faded-color: #373232;
}

@media (prefers-color-scheme: light) {
  html {
    --bg: white;
    --text: #323232;

    --link-color: #068dcb;
    --link-color-hover: #67cfff;
    --bright-color: black;
    --faded-color: #dedede;
  }
}

Then steadfastly use those color variables everywhere any color is set.

The red stays the same across both. Fortunately red is fine with that.

The main point of modes is that most of the color should be dominantly dark or dominantly light, which is mostly about backgrounds. So the --bg background does the work there and the --text variable is an accessible color that sits on top of that background.

But it’s never quite that easy. You always need to need a couple of more colors, even on simple sites. So here I’m setting up variables for links and a couple of variations.

Purposefully simple.

I kinda like approach of just changing same-named --custom-properties myself, but there are alternatives. For instance you could use named colors (e.g. --my-gray-8) then use the now well-supported light-dark() function to do something like:

.card {
  background: light-dark(var(--my-gray-4), var(--my-gray-9));
  color: light-dark(var(--my-gray-9), var(--my-gray-1))
}

Why is offering a site-level toggle so challenging?

  • The site level choice needs to override any other choice, so it means you can’t leverage @media very cleanly. But you still need to use @media for the default if there isn’t a choice, so you can’t back away from it entirely.
  • You have to persist the choice, otherwise simply refreshing the browser could wipe away the choice, which is pretty weak sauce. Persisting data means at a minimum using localStorage or cookies, but you’d probably want to do better than that.
  • The user choice can be different on the site than what their system or browser-level choice might be, so you need to load what that choice is before you render anything. Otherwise you risk Flash of inAccurate coloR Theme (FART) which is incredibly awkward.

I’d say it’s still worth doing if you’re working on a “big” site where you expect quite a bit of time-on-site from your users. You can also do something like I’ve done above as a first step and then move onto a toggle approach.

The Effect

I swear the change is much smoother than this (no thanks to transition or anything, macOS just makes it super smooth somehow). But when I was recording this video it wanted to be a more more abrupt 🤷‍♀️.

This was based on user-feedback, remember? Well one of those users noticed immediately and thanked Marc because it’s a better experience for them.

]]>
https://frontendmasters.com/blog/no-fuss-light-dark-modes/feed/ 1 4412
An HTML Email Template with Basic Typography and Dark/Light Modes https://frontendmasters.com/blog/simple-typographic-email-template/ https://frontendmasters.com/blog/simple-typographic-email-template/#comments Thu, 17 Oct 2024 21:09:13 +0000 https://frontendmasters.com/blog/?p=4210 I don’t mind HTML email, but it really can be overdone. There is a tendency to do too much. Too much layout. Too many images. Too much text. Too many styles. The reason I don’t mind it though is that the HTML part of an HTML email offers some styling control that, when reigned in, makes for a clear and nice-looking email.

A plain text email can be nice too. The constraint of plain text can help an email get to the point and not do too much. But if we go for HTML email, we can apply just enough layout and styles to make it almost like a plain text email, only with a bit more class.

  • A pleasant readable typeface
  • Reasonable line length
  • Breathable line height
  • Anchor links on words
  • Dark/light mode

I’ve put together an HTML document that does those things. I looked at various templates and poked and prodded and tested things and have what seems to be a decent setup to accomplish those things above (and nothing more).

It’s a pretty big chunk of HTML, so I’ll pop it in here as a <details>.

Plain Typographic HTML Template
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="x-apple-disable-message-reformatting" />
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <meta name="color-scheme" content="light dark" />
    <meta name="supported-color-schemes" content="light dark" />
    
    <title>Email</title>
    
    <style type="text/css" rel="stylesheet" media="all">
      
    @import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap');
      
    :root {
      color-scheme: light dark;
      supported-color-schemes: light dark;
    }
      
    body,
    td,
    th {
      font-family: "Inter", Helvetica, Arial, sans-serif;
      font-optical-sizing: auto;
    }
    body {
      width: 100% !important;
      height: 100%;
      margin: 0;
      -webkit-text-size-adjust: none;
       background-color: #FFF;
       color: #333;
    }
    a {
      color: #3869D4;
      text-decoration: underline;
    }
    @media (prefers-color-scheme: dark) {
      body {
        background-color: #333333 !important;
        color: #FFF !important;
      }
      a {
      	color: #82a9ff;
      }
    }
    
    a img {
      border: none;
    }
    
    h1 {
      margin-top: 0;
      color: #333333;
      font-size: 26px;
      font-weight: bold;
      text-align: left;
      text-wrap: balance;
    }
    h2 {
      margin-top: 0;
      color: #333333;
      font-size: 21px;
      font-weight: 900;
      text-align: left;
      text-wrap: balance;
    }
    
    p,
    ul,
    ol,
    blockquote {
      margin: 8px 0 20px;
      font-size: 16px;
      line-height: 1.625;
    }
    
    .sub {
      font-size: 13px;
    }

    .button {
      background-color: #3869D4;
      border-top: 10px solid #3869D4;
      border-right: 18px solid #3869D4;
      border-bottom: 10px solid #3869D4;
      border-left: 18px solid #3869D4;
      display: inline-block;
      color: #FFF !important;
      text-decoration: none;
      border-radius: 3px;
      box-shadow: 0 2px 3px rgba(0, 0, 0, 0.16);
      -webkit-text-size-adjust: none;
      box-sizing: border-box;
    }
    
    .email-wrapper {
      width: 100%;
      margin: 0;
      padding: 0;
      -premailer-width: 100%;
      -premailer-cellpadding: 0;
      -premailer-cellspacing: 0;
    }
    .email-content {
      width: 100%;
      margin: 0;
      padding: 0;
      -premailer-width: 100%;
      -premailer-cellpadding: 0;
      -premailer-cellspacing: 0;
    }
    .email-body {
      width: 100%;
      margin: 0;
      padding: 0;
      -premailer-width: 100%;
      -premailer-cellpadding: 0;
      -premailer-cellspacing: 0;
    }
    
    .email-footer {
      width: 570px;
      margin: 0 auto;
      padding: 0;
      -premailer-width: 570px;
      -premailer-cellpadding: 0;
      -premailer-cellspacing: 0;
      text-align: center;
    }
    .email-footer p {
      color: #A8AAAF;
    }
    
    .content-cell {
      padding: 35px;
    }
      
    @media only screen and (max-width: 600px) {
      .email-footer {
        width: 100% !important;
      }
    }
    </style>
    <!--[if mso]>
    <style type="text/css">
      .f-fallback  {
        font-family: Arial, sans-serif;
      }
    </style>
  <![endif]-->
  </head>
  
  <body>
    <table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
      <tr>
        <td align="center">
          <table class="email-content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
            <tr>
              <td class="email-body" width="570" cellpadding="0" cellspacing="0">
                <table class="email-body_inner" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
                  <tr>
                    <td class="content-cell">
                      <div class="f-fallback">
                        <p><strong>Hey folks!</strong></p>
                        
                        <p>Boy have I got some opinions for you. I don't mind HTML emails. Being allowed a bit of typography is appreciated. And hey, I like my links <a href="https://codepen.io">colorized and underlined</a>, myself, something that plain text just can't do.</p>
                        
                        <p>But don't go overboard. <em>Maybe</em> one image. If you absolutely have to do a two column thing, make sure it collapses to one on mobile.</p>
                        
                        <p>You know a button here and there is fun too.</p>
                        
                        <p><a href="https://codepen.io" class="button">A button</a></p>
                        
                        <p>But that's it. For real. Keep it chill. More people will read it. You gotta admit it looks nice right? I feel like even this email demo is getting a little long. I'd trim it up if it was a transactional email or if I really just had one call to action. An email newsletter could be longer I suppose.</p>
                        
                        <h2>Header</h2>
                        
                        <p>OK fine — a subheader or two can work. But only if the email is really long and it helps scanability.</p>
                      </div>
                    </td>
                  </tr>
                </table>
              </td>
            </tr>
            <tr>
              <td>
                <table class="email-footer" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
                  <tr>
                    <td class="content-cell" align="center">
                      <p class="f-fallback sub">That's all folks.</p>
                      <p class="f-fallback sub"><a href="#">Company Name</a><a href="#">Unsubscribe</a></p>
                    </td>
                  </tr>
                </table>
              </td>
            </tr>
          </table>
        </td>
      </tr>
    </table>
  </body>
</html>

It looks like this:

But “it looks like this” is of course a gross simplification. Looks like this… where? The email client landscape is arguably even more complicated than the browser landscape. There are email clients that use Microsoft Word (?!) as the rendering engine. There are mobile-specific and desktop-specific clients. There are native clients across platforms. There are web-based clients. There are clients from two decades ago, and clients updated last week.

I like the app Litmus for testing this stuff. It’s awfully expensive, but it allows you to see how your email will look across tons of clients and that’s pretty impressive and useful, so hey, you gotta do what you gotta do. Here’s a selection of screenshots of this email rendering:

Those all turned out pretty decent across the board so I’m happy with that. I tend to focus on the extemes. So like does Outlook 2007 on Windows look OK?

It’s not amazing, but for a 17-year-old email client, I’m going to call that a win. Web Gmail is sort of the other extreme. It’s new, and web-based, but also has rendering challenges, so testing that in Firefox on Windows is a good test.

Of course, we’ve gotta test mobile because not only is it a very different screen size it’s also an entirely different platform. Here’s Dark Mode on Android:

I’ll call that a win.

You’ll find that the Frontend Masters newsletter rolls like this!

With a red brand color as the links.

While I was playing with all this, I was like: do we reallllly need to deal with all that <table> crap? Can’t we get away with something that looks more like just semantic HTML, particularly when we’re just trying to so little with the layout?

The answer is no, we still need the <table> layout, sadly. I gave it a shot with this HTML, which I “modernized” into more basic HTML:

Modern HTML Email (Test)
<!DOCTYPE html>
<html lang="en">
  
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="x-apple-disable-message-reformatting" />
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <meta name="color-scheme" content="light dark" />
    <meta name="supported-color-schemes" content="light dark" />
    
    <title>Email</title>
    
    <style>
      
    @import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap');
      
    :root {
      color-scheme: light dark;
      supported-color-schemes: light dark;
    }
      
    body,
    td,
    th {
      font-family: "Inter", Helvetica, Arial, sans-serif;
      font-optical-sizing: auto;
    }
    body {
      margin: 0;
      -webkit-text-size-adjust: none;
       background-color: Canvas;
       color: CanvasText;
    }
    
    a {
      color: #3869D4;
    }
    a img {
      border: none;
    }
    
    h1, h2 {
      margin-top: 0;
      color: #333333;
      font-size: 26px;
      font-weight: 900;
      text-wrap: balance;
    }
    h2 {
      font-size: 21px;
    }
    
    td,
    th {
      font-size: 16px;
      word-break: break-word;
    }
    
    p,
    ul,
    ol,
    blockquote {
      margin: 8px 0 20px;
      font-size: 16px;
      line-height: 1.625;
    }
    
    .sub {
      font-size: 13px;
    }

    .button {
      background-color: #3869D4;
      border-top: 10px solid #3869D4;
      border-right: 18px solid #3869D4;
      border-bottom: 10px solid #3869D4;
      border-left: 18px solid #3869D4;
      display: inline-block;
      color: #FFF;
      text-decoration: none;
      border-radius: 3px;
      box-shadow: 0 2px 3px rgba(0, 0, 0, 0.16);
      -webkit-text-size-adjust: none;
      box-sizing: border-box;
    }
    @media only screen and (max-width: 500px) {
      .button {
        width: 100% !important;
        text-align: center !important;
      }
    }
    
    .email-wrapper {
      max-width: 570px;
      margin: 0 auto;
    }
    
    .email-footer {
    	margin-top: 100px;
      text-align: center;
    } 
    .email-footer p {
    	font-size: 14px;
      color: #A8AAAF;
    }
    </style>
  </head>
  
  <body>
    
    <div class="email-wrapper">
    
      <p><strong>Hey folks!</strong></p>

      <p>Boy have I got some opinions for you. I don't mind HTML emails. Being allowed a bit of typography is appreciated. And hey, I like my links <a href="https://codepen.io">colorized and underlined</a>, myself, something that plain text just can't do.</p>

      <p>But don't go overboard. <em>Maybe</em> one image. If you absolutely have to do a two column thing, make sure it collapses to one on mobile.</p>

      <p>You know a button here and there is fun too.</p>

      <p><a href="https://codepen.io" class="button">A button</a></p>

      <p>But that's it. For real. Keep it chill. More people will read it. You gotta admit it looks nice right? I feel like even this email demo is getting a little long. I'd trim it up if it was a transactional email or if I really just had one call to action. An email newsletter could be longer I suppose.</p>

      <h2>Header</h2>

      <p>OK fine — a subheader or two can work. But only if the email is really long and it helps scanability.</p>
                   
      <div class="email-footer">
        <p>That's all folks.</p>
        <p><a href="#">Company Name</a><a href="#">Unsubscribe</a></p>
      </div>
      
    </div>
    
  </body>
  
</html>

That looks fine in nice email clients like Apple Mail.

But Outlook 2007 and even modern OL Office can’t deal with the layout.

An explict goal of mine here was reigning in that width and centering it such that the typography has a good line length, and apparently that can’t be done without tables. Oh well! This kind of thing is “do it once and use it for a long time” and hey, that’s the job.


I’ve been enjoying playing with bolt.new, an AI generator for websites from StackBlitz. Just for kicks I asked it to create an over-the-top HTML email to see if we could get a good example of doing too much for this blog post. It spun up a Next.js app to do this, which is definitely over-the-top (lol) but I also sorta get it since the point is building web apps that StackBlitz is good at running. This is what I got though:

Which is perfect and I take back everything bad I said about HTML emails.

]]>
https://frontendmasters.com/blog/simple-typographic-email-template/feed/ 6 4210
Why is this thing in Dark Mode? https://frontendmasters.com/blog/why-is-this-thing-in-dark-mode/ https://frontendmasters.com/blog/why-is-this-thing-in-dark-mode/#respond Wed, 03 Jul 2024 16:09:48 +0000 https://frontendmasters.com/blog/?p=2907 I was looking at an email in a web app the other day, and it was showing it to me in “Dark Mode”. The email itself (of my own creation) purposely doesn’t bother to set a background or color for the most part, as then the defaults kick in which help naturally support both dark and light modes. So I was pleased! It worked!

Email in “Dark Mode” in Front
Email in “Light Mode” in Front

But then I was like… why am I looking at this email in Dark Mode? While I was working on the email, it was in Light Mode. I made it using MJML and the VS Code Extension which gave me a preview of the email, it was was in Light Mode there, so it looked surprising to me in Dark Mode that first time.

First I checked my System Settings on macOS to see what was going on there:

Light Mode there, so it wasn’t my system that was forcing Dark Mode.

Then I checked my browser. I happened to be using Arc, which has Themes.

That selected option on the left is “Automatic Appearance” which is what some things call “System” meaning match what the OS is doing.

Since that was set to “Automatic Appearance” it was following the OS’ Light Mode so that wasn’t doing it (probably). There is also Command Bar shortcuts in Arc. Try typing “Switch” in there to see actions to change it:

Other browsers do it differently, but can do it. For example you can “Customize Chrome” from a button on the Start Page in which you can force a theme or set to “Device”.

But this wasn’t explaining it for me either, as I was Light Mode through both of those layers.

What it turned out to be was a website-level setting. I was using the email app Front. Front has it’s own settings for themes, and in there indeed it was forcing a Dark Mode.

Changing that would change the colors of my email, which is exactly what I was trying to figure out.

So in terms of power, It’s like:

  1. Website setting
  2. Browser setting
  3. OS/Device setting

And those top two typically have an option to allow the setting to fall through to the next level.

That’s a lot of stuff to check when you’re trying to figure out what is controlling a color theme! I’m tempted to say too many, but when it comes to user control over websites, I tend to be in the camp of giving as much control to the user as possible. That leaves me extra conflicted about adding browser level color mode switches on a per-side basis, as it will likely lead to a 4-level system of diagnosing what mode is active.

]]>
https://frontendmasters.com/blog/why-is-this-thing-in-dark-mode/feed/ 0 2907
What’s Going On in Dark Theme / Light Theme Land https://frontendmasters.com/blog/dark-and-light/ https://frontendmasters.com/blog/dark-and-light/#comments Thu, 18 Apr 2024 23:43:30 +0000 https://frontendmasters.com/blog/?p=1716 There has been a fresh round of enthusiasm and writing around light mode / dark mode support for the web lately. I think it’s driven partially by the new light-dark() function in CSS (CSS Color Module Level 5 spec) that makes it easier to declare values that change depending on the mode. Here’s the basic usage:

html {
  color-scheme: light dark;

  background: light-dark(white, black);
  color: light-dark(black, white);
}

In real life, you’d probably be using custom properties with your colors. So you’d set up your colors for light mode, then when the special dark mode media query matches, you’d re-declare all those variables, and then you’d use them. Paweł Grzybek has a nice basic explanation. Here’s a comparison.

You used to have to do this:

:root {
  color-scheme: light dark;

  --dark-color: #292524;
  --light-color: #f5f5f4;

  --text-color: var(--dark-color);
  --bg-color: var(--light-color);
}

@media (prefers-color-scheme: dark) {
  :root {
    --text-color: var(--light-color);
    --bg-color: var(--dark-color);
  }
}

body {
  color: var(--text-color);
  background-color: var(--bg-color);
}

And now you can do this:

:root {
  color-scheme: light dark;

  --light: #292524;
  --dark: #f5f5f4;
}

body {
  color: light-dark(var(--light), var(--dark));
  background-color: light-dark(var(--dark), var(--light));
}

Essentially, it prevents you from having to use the @media query and re-declare variables. I like it. I think it makes code like this more readable and succinct — pending variable naming and usage of course.

Here’s what it’s good for: designing a site that responds to the operating system level setting for light mode / dark mode. And you have to be good with the level of browser support (no Safari just yet, as I write, but it’s in preview). If you are wondering what the fallback is, it’s doing things the old way (see above), and if you’re going to write that you might as well leave it at that.

If you’re going to want to offer more themes than just light and dark, well, you’re out of luck here, you’ll need to implement something else, likely based on changing classes on the <html> element and updating variables when the class matches.

html.nickleoden-theme {
  --bg-color: purple;
  --text-color: green;
}

I could imagine a more composable toggle method in the future, but this is what we have for now.


When I first saw light-dark(), I liked the basic idea, but I figured as soon as you offer your own user toggle for mode, you’d be out of luck. After all, you can’t change what the prefers-color-scheme media query returns, and thus which of the two values light-dark() will pick. But it turns out you can! The trick lies in that color-scheme property. You’ll see recommendations that you use color-scheme: light dark;. If you only set one or the other, you’re forcing light-dark() to pick the relevant side.

So then your user toggle could force one or the other values on the document if it’s set. (If it’s not set, leave it alone!). Here’s that working:

I like that this is based on a CSS property, as it means that you can use the cascade if you need to, setting the color-scheme on a per-element basis.

That’s just cool I think.


Anne Sturdivant blogged some recent learnings about all this color theme stuff and I learned a few things. For one, color-scheme also has normal which is “no color schemes defined, default to the browser or OS setting”, which I would assume means works the same as light dark, but in my testing did not switch over to dark when I switched my OS 🤷. Then there is only as a keyword to, uhm, I guess even more forcibly set the theme? It’s not clear to me, and also not really supported yet anyway.

Annie also clearly points out that when you change the color-theme away from the default light mode, all sorts of stuff changes. It’s certainly not just the results of light-dark(). If you set dark, the UI scrollbars go dark, and all the different form controls go dark. In fact, that might be the number one use-case for color-scheme really, even if we do have accent-color now.


There is also this whole idea of System Colors that tends to come up in writing about this, so I’m going to do the same. These are “named” colors, like rebeccapurple, but they have a fancy baked in ability in that they can change when the color-scheme changes. So if you want to flip out basically white and black, but not bother with variables and fancy functions and whatnot, you’ve got:

body {
  background-color: Canvas;
  color: CanvasText;
  color-scheme: light dark;
}

Note the Canvas and CanvasText, those are the System Colors. Allow me to snipe the images of the available colors and what they look like in the different modes from a great article by Mads Stoumann.

Mads made a cool demo of a theme toggler that uses newfangled style queries that is worth checking out. Remember, though, that these client-side-only theme switchers are really just demos. It’s likely on a real-world site, if you’re offering a switcher, you should try to persist that information. A cookie, sessionStorage, localStorage, to a database… something. Then when you do that, there is the risk that the page renders before you can access and set that information, leading to FART, my most successful coining of an acronym ever. It’s a tricky thing, as delaying rendering just for something like this doesn’t feel right.


Another thing percolating in the industry is, as Bramus puts it: What if you had real control over Light Mode / Dark Mode on a per-site basis? I always say that web standards bodies, and even browsers themselves to some degree, are at their best when they see developers toiling and doing the same sort of things, and then introduce better and standardized methods. Properly implementing color themes and the toggling methods with persistence is real work! It doesn’t look like color themes are just a fad, so I think this is a clear opportunity for help.

I think a bit of browser UI would be perfectly welcome:

A perfect implementation would be that the browser itself remembers what choice you’ve made (light, dark, or default to system preference), and that is applied during rendering, avoiding FART. Sounds like it is going to require a new API and then potentially browsers actually using it themselves, which is kinda funny to think about. I normally think about web APIs as being for developers, not browsers.

]]>
https://frontendmasters.com/blog/dark-and-light/feed/ 2 1716