Tag: color

Apple Messages & Color Contrast

Well, color me this! I was griping to myself last night about just how gosh dang hard it is to read text messages in Apple Messages. You know, not the blue bubbles that you get when messaging other iPhone users. Those are iMessages.

What I’m talking about are the green bubbles you get when messaging non-iPhone users. Those are standard text messages.

iMessage (left) and text message (right)

Let’s run the green through a contrast checker to see what’s up.

Permalink

Oomph. Now I know why I always reach for my reading glasses when a text message pops up. That 2.17:1 ratio is below the WCAG 2.0 AA requirement of 4.5:1 and wayyyyy below the AAA level of 7:1.

Turns out I’m not the only one griping. A quick search turned up a little trove of news and blog posts — some as recent as last week — about the readability of those green text message bubbles.

I’m no conspiracy theorist and like to give benefit to doubt. Buuuuut…

Apple Messages in iOS 6 (left) and iOS 7 (right)
Credit: Phoceis
  • iOS 6: Dark text on a green gradient background
  • iOS 7: White text on a #5AB539 background (or something close to it)
  • iOS 16.1: White text on a #6ACC46 background

That second one is based on old screenshots and might not be the most accurate color value. But still, the transition from iOS 6 with dark text to what we have today in iOS 16.1 shows a clear regression. I’d like think the design team checked the updated values against WCAG guidelines, sure, but at least against their own Human Interface Guidelines.

The current green background (#65C466) appears to be different than what is listed as the green “system color” (#30D158, converted from a RGB of 48, 209, 88) in the iOS palette listed in the guidelines. But it’s not like that gets us any closer to a passing WCAG AA or AAA rating.

Permalink

🤷‍♂️


Apple Messages & Color Contrast originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

CSS-Tricks

, , ,

Customizing Color Fonts on the Web

Myles C. Maxfield on the WebKit Blog published a nifty how-to for color fonts. It comes on the heels of what Ollie wrote up here on CSS-Tricks the other day, and while they cover a lot of common ground, there’s some nice nuggets in the WebKit post that make them both worth reading.

Case in point: there’s a little progressive enhancement in there using @supports for older browsers lacking support the font-palette property. Then the post gets into a strategy that shows the property’s light and dark values at play to make the font more legible in certain contexts. There’s also a clever idea about how creating multiple @font-palette-values blocks with the same name can be used for fallbacks.

To Shared LinkPermalink on CSS-Tricks


Customizing Color Fonts on the Web originally published on CSS-Tricks. You should get the newsletter.

CSS-Tricks

, ,
[Top]

A Whistle-Stop Tour of 4 New CSS Color Features

I was just writing in my “What’s new in since CSS3?” article about recent and possible future changes to CSS colors. It’s weirdly a lot. There are just as many or more new and upcoming ways to define colors than what we have now. I thought we’d take a really quick look.

First, a major heads up. This stuff is so complicated. I barely understand it. But here are some aspects:

  • Before all this upcoming change, we only had RGB as a color model, and everything dealt with that.
  • We had different “color spaces” that handled it differently (e.g. the rgb() function mapped that RGB color model as a cube with linear coordinates, the hsl() function mapped that RGB color model as a cylinder) but it was all sRGB gamut.
  • With the upcoming changes, we’re getting new color models and (!) we’re getting new functions that map that color model differently. So I think it’s kind of a double-triple whammy.

I can’t personally educate you on all the nitty-gritty details — I’m writing this because I bet there are a lot of you like me, wondering why you should care at all about this, and this is my attempt to understand why I should care about all of it.

Display-P3 is one that opens up a ton of more vibrant color that was able to be expressed before.

body {   background: color(display-p3 1 0.08 0); /* super red! */ }

It turns out that modern monitors can display way more colors, particularly extra vibrant ones, but we just have no way of defining those colors with classic CSS color syntaxes, like HEX, RGB, and HSL. Super weird, right?! But if you use Display-P3, you get a wider range of access to these vibrant colors.

Screenshot of a super bright pink in a CodePen preview using the display-p3 CSS color syntax.
That white line in Safari DevTools is showing us the “extra” range of Display-P3

The dev shop Panic latched onto this early on and started using these colors as a “secret weapon”:

Jen Simmons also covers how to use them, including a fallback for non-supporting browsers:

Resources

HWB is the one that is more “for humans” except that’s a bit debatable and it’s still based on sRGB.

I had no idea hwb() was a thing — shout out to Stefan Judis for blogging about it.

I normally think of HSL as the CSS color format that is “for humans” (and good for programmatic control) because, well, manipulating 360° of Hue and 0-100% of both Saturation and Lightness make some kind of obvious sense.

But in hwb(), we’ve got Hue (the same as HSL, I think), then Whiteness and Blackness. Stefan:

Adding White and Black to a color affects its saturation. Suppose you add the same amount of White and Black to a color, the color tone stays the same, but color loses saturation. This works up to 50% White and 50% Black (hwb(0deg 50% 50%)), which results in an achromatic color.

Showing six gradients going from red to black and the impact that change CSS color values in hwb has on the transition between colors.

Stefan expressed some doubt that this is any easier to understand than HSL, and I tend to agree. I probably just need to get more used to it, but it seems to be more abstract than simply changing the lightness or saturation.

HWB is limited to the same color gamut (sRGB) as all the old color formats all. No new colors are unlocked here.

Resources

LAB is like rgb() of a much wider gamut

div {   background: lab(150% -400 400); }

I liked Eric Portis’ explanation of LAB when I went around asking about it:

LAB is like RGB in that there are three linear components. Lower numbers mean less of the thing, bigger numbers mean more of the thing. So you could use LAB to specify the brightest, greenest green that ever bright-greened, and it’ll be super bright and green for everybody, but brighter and greener on monitors with wider gamuts.

So, we get all the extra color, which is awesome, but sRGB had this other problem (aside from being limited in color expression), that it isn’t perceptually uniform. Brian Kardell:

The sRGB space is not perceptually uniform. The same mathematical movement has different degrees of perceived effect depending on where you are at in the color space. If you want to read a designer’s experience with this, here’s an interesting example which does a good job struggling to do well.

The classic example here is how, in HSL, colors with the exact same “Lightness” really don’t feel the same at all.

But in LAB, apparently, it is perceptually uniform, meaning that programmatically manipulating colors is a much more sane task. And another bonus is that LAB colors are specced as being device-independent. Here’s Michelle Barker:

LAB and LCH are defined in the specification as device-independent colors. LAB is a color space that can be accessed in software like Photoshop and is recommended if you want a color to look the same on-screen as, say, printed on a t-shirt.

Resources

LCH is like HSL of a much wider gamut

Remember how I said HSL is “for humans” in that it is easier understand than RGB? Changing the Hue, Saturation, and Lightness makes a lot of logical sense. Similar here with lch() where we’ve got Lightness, Chroma, and Hue. Back to my conversation with Eric Portis:

LCH is more like HSL: a polar space. H = hue = a circle. So doing math to pick complementary colors (or whatever transforms you’re after) becomes trivial (just add 180 — or whatever!)

I suppose you’d pick LCH just because you like the syntax of it or because it makes some complicated programmatic thing you’re trying to do easier — and you get the fact that it can express 50% more colors for free.

We get the perceptual uniformity here, too. Here’s Lea Verou who seems excited that lightness will actually mean something:

In HSL, lightness is meaningless. Colors can have the same lightness value, with wildly different perceptual lightness. […] With LCH, any colors with the same lightness are equally perceptually light, and any colors with the same chroma are equally perceptually saturated.

Another benefit of the new model is that we can wipe our hands clean of the “gray dead zone” in CSS color gradients. I think because of this perceptual uniformity stuff, two rich colors won’t get cheeky and gradient themselves through non-rich territory.

Two gradients going from blue to pink, one on top of the other. The first uses the LCH CSS color syntax and the second use HSL. HSL has noticeable gray areas.
There will always be tradeoffs in color models, especially with gradients. (Demo)

Here’s a small personal prediction: I’d say that lch() is probably going to be a designer favorite. Soon there are going to be a ton of new color choices and it’s too difficult and weird to always be picking different ones. LCH seems to have the most bang for the mental buck.

Resources

“OK”

LAB ‘n’ friends seems so new because it is new… to CSS. But LAB was invented in the 1940s. In a conversation with Adam Argyle, he used a memorable phrase: All the color spaces have an Achilles’ heel. That is, something they kinda suck at. For sRGB, it’s the grey dead zone thing, as well as the limited color gamut. LAB is great and all, but it certainly has its own weaknesses. For example, a blue-to-white gradient in LAB travels pretty awkwardly through purpletown.

In December 2020, Björn Ottosson is all like “Hey, a new color space just dropped,” and now OKLAB exists. Apparently the CSS powers-that-be see enough value in that color space that both oklab() and oklch() are already specced. I guess we should care because they are just generally better, but don’t quote me on that.

Why is it Display P3 uses the color() function but the other’s don’t?

I don’t really know. I think the CSS color() function is a bit newer and that’s just how Safari dunked it in there to start. I have no idea if Display P3 will get its own dedicated function, or if we all should just start using CSS color(), or what.

/* This is how you use Display P3 */ color(display-p3 1 0.08 0);   /* But this doesn't work */ color(oklch 42.1% 0.192 328.6);  /* You gotta do this instead 🤷‍♀️ */ oklch(42.1% 0.192 328.6);  /* But you can use the color space within a gradient... */ background-image: linear-gradient(     to right      in oklch,     lch(50% 100 100),      lch(50% 100 250)   );

The relative color syntax is super useful.

There is this really cool ability called “relative color syntax” where you can basically deconstruct a CSS color while moving it into another format. Say you have the (obviously) most famous CSS HEX color ever, fog dog, and you wanna kick it into HSL instead:

body {   background: hsl(from #f06d06 h s l); }

Maybe that’s not all that useful immediately, but hey, now we’re able to add alpha to it! There is literally no other way to apply alpha to an existing HEX color, so that’s kinda huge:

body {   background: hsl(from #f06d06 h s l / 0.5); }

But I can also mess with it. Say I wanna saturate fog dog a bit before I add opacity because the lower opacity will naturally dull it out and I wanna combat that. I can use calc() on the implied variables there:

body {   background: hsl(from #f06d06 h calc(s + 20%) l / 0.5); }

That’s so cool. I’m sure we’ll see some amazing things come from this. And it certainly isn’t limited to HSL. I was just using HSL because it’s what is comfortable to me right now. I could start with the named color red and mess with it in LCH if I want:

body {   background: lch(from red l calc(c + 15) h / 0.25); }

This stuff is going to be most useful when liberally combined with custom properties.

There are no special functions just for alpha anymore.

Just to be clear: no commas preceding the alpha value in a CSS color function — just a forward slash instead:

/* Old! */ rgb(255, 0, 0); rgba(255, 0, 0, 0.5);  /* New! */ rgb(255 0 0); rgb(255 0 0 / 0.5); hsl(0deg 40% 40%) hsl(0deg 40% 40% / 90%) /* can be percentage rather than 0.9 or whatever */  /* The New color stuff ONLY has the single base function, no alpha secondardy function */ lab(49% 39 80) lab(49% 39 80 / 0.25)  /* Display P3, with the color function, essentially works the same way with the slash */ color(display-p3 1 0.08 0 / 0.25); 

You can even define your own CSS color space.

But I literally can’t even think about that. It blows my mind, sorry.


A Whistle-Stop Tour of 4 New CSS Color Features originally published on CSS-Tricks. You should get the newsletter and become a supporter.

CSS-Tricks

, , ,
[Top]

Using Different Color Spaces for Non-Boring Gradients

A little gradient generator tool from Tom Quinonero. You’d think fading one color to another would be an obvious, simple, solved problem — it’s actually anything but!

Tom’s generator does two things that help make a gradient better:

  1. You can pick an “interpolation space.” Gradients that use the sRGB color space (pretty much all the color stuff we have in CSS today) have a bad habit of going through a gray dead zone, and if you interpolate the gradient in another color space, it can turn out nicer (and yet convert it back to RGB to use today).
  2. Easing the colors, though the use of multiple color-stops, which can result in a less abrupt and more pleasing look.

Showing a color wheel with a line indicating the two colors in a gradient that goes from yellow to light blue. The resulting gradient is at top showing some gray tones as a result of the color space.
See the gray in the middle there?

Different gradient apps with different color spaces

Josh has another similar app, as does Erik Kennedy. So stinkin’ interesting how different gradients are in different color spaces. Think of the color spaces as a physical map where individual colors are points on the map. Gradients are dumb. They just walk straight from one point on the map to the next. The colors underneath their feet as they walk make a massive difference in how the gradient turns out.

To Shared LinkPermalink on CSS-Tricks


Using Different Color Spaces for Non-Boring Gradients originally published on CSS-Tricks. You should get the newsletter and become a supporter.

CSS-Tricks

, , , , ,
[Top]

Color Alpha Anywhere

In my “Different Degrees of Custom Property Usage” article, I noted a situation about colors and CSS custom properties where I went “too far” with breaking up HSL color values. Breaking every single color into its H, S, and L parts is may be a step too far.

But you probably do want to split it up like this:

html {   --color-1-hsl: 200deg 15% 73%;   --color-1: hsl(var(--color-1-hsl)); }

So, two custom properties per color in your color system. Why? Because now you’ve got a really easy way to use it and you’ve got a way to apply alpha to the color if you want.

.card {   background: var(--color-1); } .card-with-alpha {   background: hsl(var(--color-1) / 0.5); }

There’s not really any other way to take an existing color in CSS and apply alpha transparency to it. Well, I say that, but actually

/* Future CSS! (works in Safari TP right now) */ .card-with-alpha {   background: hsl(from var(--color-1) h s l / 0.5); }

That’s neat, but I’m not entirely sure when we’ll be able to rely on that in production.

You know what else we can’t use for anything super important in production? Houdini paint worklets. No Firefox or Safari yet on those.

A bummer, because Dave almost had this thing cracked! The insight here is that Houdini paint worklets basically return an image that you paint with <canvas> APIs. You can paint a rectangle in Canvas with any color format, then set the globalAlpha, return that as an image, and so that basically unlocks alpha on any color format! It works (in Chrome):

Dave chucked the code on GitHub and blogged it. Of course, it made a good video as well:

Like and subscribe.

But if you need a system like this on production, just do the custom properties technique I listed first.

A previous version of this post was tweetbombed, but I’m blogging it here because real bloggers blog.


The post Color Alpha Anywhere appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, ,
[Top]

Easy Dark Mode (and Multiple Color Themes!) in React

I was working on a large React application for a startup, and aside from just wanting some good strategies to keep our styles organized, I wanted to give this whole “dark mode” thing a shot. With the huge ecosystem around React, you might think that there would be a go-to solution for style themes, but a little web searching shows that really isn’t the case.

There are plenty of different options out there, but many of them tie into very specific CSS strategies, like using CSS Modules, some form of CSS-in-JS, etc. I also found tools specific to certain frameworks, like Gatsby, but not a generic React project. What I was looking for was a basic system that’s easy to set up and work with without jumping through a ton of hoops; something fast, something easy to get a whole team of front-end and full-stack developers onboarded with quickly.

The existing solution I liked the best centered around using CSS variables and data attributes, found in this StackOverflow answer. But that also relied on some useRef stuff that felt hack-y. As they say in every infomercial ever, there’s got to be a better way!

Fortunately, there is. By combining that general CSS variable strategy with the beautiful useLocalStorage hook, we have a powerful, easy-to-use theming system. I’m going to walk through setting this thing up and running it, starting from a brand new React app. And if you stick around to the end, I also show you how to integrate it with react-scoped-css, which is what makes this my absolutely preferred way to work with CSS in React.

Project setup

Let’s pick this up at a very good place to start: the beginning.

This guide assumes a basic familiarity with CSS, JavaScript, and React.

First, make sure you have a recent version of Node and npm installed. Then navigate to whatever folder you want your project to live in, run git bash there (or your preferred command line tool), then run:

npx create-react-app easy-react-themes --template typescript

Swap out easy-react-themes with the name of your project, and feel free to leave off the --template typescript if you’d rather work in JavaScript. I happen to like TypeScript but it genuinely makes no difference for this guide, other than files ending in .ts/.tsx vs .js/.jsx.

Now we’ll open up our brand new project in a code editor. I’m using VS Code for this example, and if you are too, then you can run these commands:

cd easy-react-themes code .
Not much to look at yet, but we’ll change that!

Running npm start next starts your development server, and produces this in a new browser window:

And, finally, go ahead and install the use-local-storage package with:

npm i use-local-storage

And that’s it for the initial setup of the project!

Code setup

Open the App.tsx file and get rid of the stuff we don’t need.

We want to go from this…

…to this.

Delete the entire content in App.css:

Woot! Now let’s create our themes! Open up the index.css file and add this to it:

:root {   --background: white;   --text-primary: black;   --text-secondary: royalblue;   --accent: purple; } [data-theme='dark'] {   --background: black;   --text-primary: white;   --text-secondary: grey;   --accent: darkred; }

Here’s what we have so far:

See what we just did there? If you’re unfamiliar with CSS Custom Properties (as also known as CSS variables), they allow us to define a value to be used elsewhere in our stylesheets, with the pattern being --key: value. In this case, we’re only defining a few colors and applying them to the :root element so they can be used be used wherever else we need them across the whole React project.

The second part, starting with [data-theme='dark'], is where things get interesting. HTML (and JSX, which we’re using to create HTML in React) allows us to set completely arbitrary properties for our HTML elements with the data-* attribute. In this case, we are giving the outermost <div> element of our application a data-theme attribute and toggling its value between light and dark. When it’s dark, the CSS[data-theme='dark'] section overrides the variables we defined in the :root, so any styling which relies on those variables is toggled as well.

Let’s put that into practice. Back in App.tsx, let’s give React a way to track the theme state. We’d normally use something like useState for local state, or Redux for global state management, but we also want the user’s theme selection to stick around if they leave our app and come back later. While we could use Redux and redux-persist, that’s way overkill for our needs.

Instead, we’re using the useLocalStorage hook we installed earlier. It gives us a way to store things in local storage, as you might expect, but as a React hook, it maintains stateful knowledge of what it’s doing with localStorage, making our lives easy.

Some of you might be thinking “Oh no, what if the page renders before our JavaScript checks in with localStorage and we get the dreaded “flash of wrong theme?” But you don’t have to worry about that here since our React app is completely rendered client-side; the initial HTML file is basically a skeleton with a with a single <div> that React attaches the app to. All of the final HTML elements are generated by JavaScript after checking localStorage.

So, first, import the hook at the top of App.tsx with:

import useLocalStorage from 'use-local-storage'

Then, inside our App component, we use it with:

const defaultDark = window.matchMedia('(prefers-color-scheme: dark)').matches; const [theme, setTheme] = useLocalStorage('theme', defaultDark ? 'dark' : 'light'); 

This does a few things for us. First, we’re checking if the user has set a theme preference in their browser settings. Then we’re creating a stateful theme variable that is tied to localStorage and the setTheme function to update theme. useLocalStorage adds a key:value pair to localStorage if it doesn’t already exist, which defaults to theme: "light", unless our matchMedia check comes back as true, in which case it’s theme: "dark". That way, we’re gracefully handling both possibilities of keeping the theme settings for a returning user, or respecting their browser settings by default if we’re working with new users.

Next, we add a tiny bit of content to the App component so we have some elements to style, along with a button and function to actually allow us to toggle the theme.

The finished App.tsx file

The secret sauce is on line 14 where we’ve added data-theme={theme} to our top-level <div>. Now, by switching the value of theme, we are choosing whether or not to override the CSS variables in :root with the ones in the data-theme='dark' section of the index.css file.

The last thing we need to do is add some styling that uses those CSS variables we made earlier, and it’ll up and running! Open App.css and drop this CSS in there:

.App {   color: var(--text-primary);   background-color: var(--background);   font-size: large;   font-weight: bold;   padding: 20px;   height: calc(100vh - 40px);   transition: all .5s; } button {   color: var(--text-primary);   background-color: var(--background);   border: 2px var(--text-primary) solid;   float: right;   transition: all .5s; }

Now the background and text for the main <div>, and the background, text, and outline of the <button> rely on the CSS variables. That means when the theme changes, everything that depends on those variables update as well. Also note that we added transition: all .5s to both the App and <button> for a smooth transition between color themes.

Now, head back to the browser that’s running the app, and here’s what we get:

Tada! Let’s add another component just to show how the system works if we’re building out a real app. We’ll add a /components folder in /src, put a /square folder in /components, and add a Square.tsx and square.css, like so:

Let’s import it back into App.tsx, like so:

Here’s what we have now as a result:

And there we go! Obviously, this is a pretty basic case where we’re only using a default (light) theme, and a secondary (dark) theme. But if your application calls for it, this system could be used to implement multiple theme options. Personally, I’m thinking of giving my next project options for light, dark, chocolate, and strawberry—go nuts!

Bonus: Integrating with React Scoped CSS:

Using React Scoped CSS is my favorite way to keep each component’s CSS encapsulated to prevent name collision messiness and unintended style inheritance. My previous go-to for this was CSS Modules, but that has the downside of making the in-browser DOM look like a robot wrote all of the class names… because that’s exactly the case. This lack of human-readability makes debugging far more annoying than it has to be. Enter React Scoped CSS. We get to keep writing CSS (or Sass) exactly the way we have been, and the output looks like a human wrote it.

Seeing as the the React Scoped CSS repo provides full and detailed installation instructions, I’ll merely summarize them here.

First, install and configure Create React App Configuration Override (CRACO) according to their instructions. Craco is a tool that lets us override some of the default webpack configuration that’s bundled into create-react-app (CRA). Normally, if you want to adjust webpack in a CRA project, you first have to “eject” the project, which is an irreversible operation, and makes you fully responsible for all of the dependencies that are normally handled for you. You usually want to avoid ejecting unless you really, really know what you’re doing and have a good reason to go down that road. Instead, CRACO let’s us make some minor adjustments to our webpack config without things getting messy.

Once that’s done, install the React Scoped CSS package:

npm i craco-plugin-scoped-css

(The README instructions use yarn for installation instead of npm, but either is fine.) Now that it’s installed, simply rename the CSS files by adding .scoped before the .css, like so:

app.css -> app.scoped.css

And we need to make sure we’re using a new name when importing that CSS into a component:

import './app.css'; -> import './app.scoped.css';

Now all of the CSS is encapsulated so that it only applies to the components they’re imported into. It works by using data-* properties, much like our theme system, so when a scoped CSS file is imported into a component, all of that component’s elements are labeled with a property, like data-v-46ef2374, and the styles from that file are wrapped so that they only apply to elements with that exact data property.

That’s all wonderful, but the little trick to making that work with this theming system is that we explicitly don’t want the CSS variables encapsulated; we want them applied to the whole project. So, we simply don’t change index.css to have scoped in it… in other words, we can leave that CSS file alone. That’s it! Now we have a powerful theming system working in harmony with scoped CSS— we’re living the dream!

Thank you so much taking a read through this guide, and if it helped you build something awesome, I would love to know about it!


The post Easy Dark Mode (and Multiple Color Themes!) in React appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , , ,
[Top]

Meta Theme Color and Trickery

Starting with Version 15, Safari supports the theme-color <meta> tag both on macOS and iOS. That’s exciting news because now the first desktop browser supports this <meta> tag and it also supports the media attribute and the prefers-color-scheme media feature.

I never really took much note of the theme-color meta tag, but now is a good time to learn about its features and limitations and try to discover some interesting use cases.

Features and limitations

Here’s how I’ve been using the theme-color meta tag for the past few years: just a good ‘ol hex code for the content attribute.

<meta name="theme-color" content="#319197">

According to tests I made earlier this year, this works in Chrome, Brave and Samsung Internet on Android, installed PWAs in Chrome and now also in Safari Technology Preview.

Hex color support is great in all supported browsers.

CSS color support

One of the first questions that came to my mind was “Can we use color keywords, hsl(), rgb(), too?” According to the HTML spec, the value of the attribute can be any CSS color. I’ve created this theme-color testing CodePen to verify that.

<meta name="theme-color" content="hsl(24.3, 97.4%, 54.3%)">
Blank webpage with orange header.
The theme-color meta tags supports CSS colors in any form: keywords, rgb(), hsl() or hex code.
Blank webpage with a hot pink header. There are controls to the right of the webpage for browser testing.
Looking at Chrome 90 on an Android Galaxy S20

All supported browsers also support hsl() and rgb(). This is awesome because it allows us to do some pretty cool stuff with JavaScript. We’ll talk about that later, but first let’s look at some limitations.

Transparency

HEX codes, rbg(), hsl() and keywords are well and consistently supported, but colors that include transparency: not so much. Actually, they are supported in most browsers, but the results aren’t very consistent and sometimes unexpected.

transparent is a CSS color and used in the theme-color meta tag most browsers do what you’d expect. All regular mobile browsers don’t change color and display the default tab bar, but Safari on macOS and the Chrome Canary PWA on macOS turn the tab bar black. The PWA on Android falls back to theme-color defined in the manifest.json, which we’ll talk about in a bit.

Examples of the same white webpage with either white or dark headers with the browser vendor labeled above each one.
Browser with a transparent theme-color meta tag

All browsers interpret hsla() and rgba(), but they set the alpha value to 1. The only exception is Safari on macOS; it interprets the transparency, but it seems like the transparent color has a black baseline. This has the effect that the light orange color looks like dark orange.

Same browser comparison but all with orange headers, except Safari which is a darker brown.
hsla() applied to the theme-color meta tag

New color functions

Safari 15 is the first browser to support lab(), lch(), and hwb() color functions. These functions work if you use them in CSS, but not if you use them in the theme-color meta tag.

All three declarations work fine in Safari 15:

body {   background-color: hwb(27 10% 28%);   background-color: lch(67.5345% 42.5 258.2);   background-color: lab(62.2345% -34.9638 47.7721); }

If you use any of the new color functions in the theme-color meta tag, Safari doesn’t interpret them and falls back to its own algorithm of picking the color. It’s likely that Safari uses the background color of your <body> for the theme-color, which means that you might get the expected result without defining the theme-color explicitly.

<meta name="theme-color" content="lab(29.2345% 39.3825 20.0664)">
Green webpage with green header.

Please be aware that at the time of writing Safari 15 is the only browser to support these new colors functions.

currentColor

If CSS colors are supported, currentColor should work, too, right? No, unfortunately not in any browser. It’s probably an uncommon use case, but I would expect that we can set the theme-color to the current color of the <body> or <html> element.

<style>   body {     color: blue;   } </style>  <meta name="theme-color" content="currentColor">

I found a ticket in the WebKit bug tracker titled <meta name="theme-color" content="..."> should also support CSS currentcolor.” Support might change in the future, if someone picks the ticket up.

Prohibited colors

When I was testing CSS color keywords, I used the color red and it didn’t work. First, I thought that keywords weren’t supported, but blue, hotpink, and green worked fine. As is turns out, there’s a narrow range of colors that Safari doesn’t support, colors that would get in the way of using the interface. red doesn’t work because it’s visually too close to the background color of the close button in the tab bar. This limitation is specific to Safari, in all other supported browsers any color seem to work fine.

Wbite webpage with a color picker set to red. The header of the browser is white.
If you set the theme-color to red, Safari uses any color it deems appropriate.

Custom properties

I don’t know enough about the internals of browsers and custom properties and if it’s even possible to access custom properties in the <head>, but I tried it anyway. Unfortunately, it didn’t work in any browser.

<style>   :root {     --theme: blue;   } </style>  <meta name="theme-color" content="var(--theme)">

That’s pretty much everything I wanted to know about basic support of the theme-color meta tag. Next, let’s see how to and how not to implement dark mode for the tab bar.

Dark mode

Safari 15 is the first desktop browser to support the media attribute and the prefers-color-scheme media feature on theme-color meta tags. Starting with version 93, Chrome supports it too, but only for installed progressive web apps.

According to the web app manifest page on web.dev, if you define multiple theme-color meta tags, browsers pick the first tag that matches.

<meta name="theme-color" content="#872e4e" media="(prefers-color-scheme: dark)">

I was eager to find out what happens in browsers that don’t support the media attribute. I’ve created a demo page for testing dark mode that includes the meta tags above and also allows you to install the site as a PWA. The webmanifest.json includes another color definition for the theme-color.

{   "name": "My PWA",   "icons": [     {       "src": "https://via.placeholder.com/144/00ff00",       "sizes": "144x144",       "type": "image/png"     }   ],   "start_url": "/theme-color-darkmode.html",   "display": "standalone",   "background_color": "hsl(24.3, 97.4%, 54.3%)",   "theme_color": "hsl(24.3, 97.4%, 54.3%)" }

Here’s how supported browsers display the tab bar in light mode. It doesn’t matter if a browser supports the media attribute or not, it will interpret the first meta tag regardless.

Here’s how the tab bar on the same page looks like in dark mode. These results are more interesting because they vary a bit. The Canary PWA and Safari support and show the dark color. All mobile browsers use their default dark tab bar styling, except for Samsung Internet, which uses the light styling because it doesn’t support the prefers-color-scheme media feature. (TIL: This should change in the near future.)

I did one last test. I wanted to see what happens if I only define a theme color for dark mode, but access the page in light mode.

<meta name="theme-color" content="#872e4e" media="(prefers-color-scheme: dark)">

These results surprised me the most because I expected all mobile browsers to ignore the media attribute and just use the dark color in the meta tag regardless, but ordinary Chrome Canary completely ignores the whole meta tag, even though it doesn’t support the media attribute. As expected, both Canary PWAs fall back to the color defined in the manifest file.

The other interesting thing is that Safari displays a theme-color even though I haven’t defined one for light mode. That’s because Safari will pick a color on its own, if you don’t provide a theme-color. In this case, it uses the background color of the page, but it also might use the background color of the <header> element, for example.

If you want to define a theme color for light and dark mode, your best bet is to define both colors and use the first meta tag as a fallback for browsers that don’t support the media feature.

<meta name="theme-color" content="#319197" media="(prefers-color-scheme: light)"> <meta name="theme-color" content="#872e4e" media="(prefers-color-scheme: dark)">

Safari has proven that theme-color works great on desktop browsers, too. I’m sure that designers and developers will find many creative ways to use this meta tag, especially considering that the value can be changed via JavaScript. I’ve collected and created some interesting demos for your inspiration.

Demos and use cases

Theming

poolsuite.net provides different themes for the site and changes the theme-color accordingly.

Max Böck also changes the theme-color on his website when you change the theme.

Page theming

Most websites don’t provide custom themes, but you can still give your pages that certain something. Dave uses different key colors in his blog posts for links and icons, and now also in the tab bar.

Gradients

If you’re using gradients on your page, you can highlight your styling by making the gradient span the whole browser. The theme-color meta tag doesn’t support gradients, but you can use the same color for the meta tag and the start color of the gradient of you page’s background.

<meta name="theme-color" content="rgb(0, 235, 255)">  <style>   body {     background: linear-gradient(rgb(0, 235, 255), #08124a);   } </style>
Form validation

I built this proof of concept of a form that changes theme-color on form validation. It starts with a blue tab bar which turns red if the submitted data is invalid or green if it’s valid.

const email = document.querySelector('input') const themeColor = document.querySelector('meta[name="theme-color"]') const msg = document.querySelector('[aria-live]') let color = '#FA0000' let message = 'Error message'  document.querySelector('button').addEventListener('click', (e) => {   e.preventDefault()    email.reportValidity()   email.setAttribute('aria-invalid', true)    if (email.validity.valid) {     color = '#00FF00'     message = "Success message!"     email.setAttribute('aria-invalid', false)   }    msg.textContent = message   themeColor.setAttribute('content', color) });
Disco mode

I’m not saying that you should, but you could put your site in 💃 Disco Mode 🕺 by combining setInterval and hsl() colors.

/* Inspired by https://twitter.com/argyleink/status/1408184587885309952 */  const motion = window.matchMedia("(prefers-reduced-motion: no-preference)");  // Check if users don't have a preference for reduced motion if (motion.matches) {   let scheme = document.querySelector('meta[name="theme-color"]')   let hue = 0   let color    setInterval(() => {     color = `hsl($ {hue+=5} 50% 30%)`     document.body.style.background = color;     scheme.setAttribute('content', color)   }, 50)
Scrolling

Stuart had a great idea, he suggested changing theme color on scroll. I built this quick prototype, again using hsl() colors.

Please only do this if it doesn’t affect performance negatively.

Max built a demo in which he changes the theme-color according to the background color of the current section in the viewport using Intersection Observer.

const setThemeColor = (color) => {   const meta = document.querySelector('meta[name="theme-color"]')   if (meta) {     meta.setAttribute('content', color)   } }  if ("IntersectionObserver" in window) {   const observer = new IntersectionObserver(entries => {       entries.forEach(entry => {         const { isIntersecting, target } = entry         if (isIntersecting) {           const color = window.getComputedStyle(target).getPropertyValue("background-color");           setThemeColor(color)         }       })   }, {     root: document.getElementById('viewport'),     rootMargin: "1px 0px -100% 0px",     treshold: 0.1   })      document.querySelectorAll('.section').forEach(section => {     observer.observe(section)   }) }
Extracting color

Another interesting idea is to extract the dominant or average color from your header images automatically and use it as the theme-color.

<script type="module">   import fastAverageColor from "https://cdn.skypack.dev/fast-average-color@6.4.0";   const fac = new fastAverageColor();        fac.getColorAsync(document.querySelector('img'))     .then(color => {       document.querySelector('meta[name="theme-color"]').setAttribute('content', color.rgba)     })     .catch(e => {       console.log(e);     }); </script>     <img src="/amy-humphries-2M_sDJ_agvs-unsplash.jpg" alt="A sea star on blue sand." />

That is just a handful of ideas, but I already like where this is going and I’m sure that you’ll come up with even more creatives ways of using the theme-color meta tag.

Resources


The post Meta Theme Color and Trickery appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , ,
[Top]

Flash of inAccurate coloR Theme (FART)

There is a lot to think about when implementing a dark mode theme on a website. We have a huge guide on it. There are some very clever quick wins out there, but there are also some quite tricky things to pull off. One of those tricky things is how it’s not a dark mode “toggle” between dark and light, but really three modes you need to support: dark, light, and user system preference. That’s similar to how audio preferences work in many apps, which allow you to very specifically cohose which audio input or output you want, or just default to the system preference.

CSS and JavaScript can handle the system preference angle, via the prefers-color-scheme API, but if the user preference has changed, and that preference is now different than the user preference, you’re in the territory of “Flash of inAccurate coloR Theme” or FART. Ok ok, it’s a tounge-in-cheek acronym, but it’s potentially quite a visually obnoxious problem so I’m keeping it. It’s in the same vein that FOUT (Flash of Unstyled Text) is for font loading.

Storing a user preference means something like a cookie, localStorage, or some kind of database. If access to that data means running JavaScript, e.g. localStorage.getItem('color-mode-preference');, then you’re in FART territory, because your JavaScript is very likely running after a page’s first render, lest you’re otherwise unnecessarily delaying page render.

User preference is “dark” mode, but the system preference is “light” mode (or unset), so when the page refreshes, you get FART.

You can access a cookie with a server-side language before page-render, meaning you could use it to output something like <html class="user-setting-dark-mode"> and style accordingly, which deftly avoids FART, but that means a site that even has access to a server-side language (Jamstack sites do not, for example).

Allllll that to say that I appreciated Rob Morieson’s article about dark mode because it didn’t punt on this important issue. It’s very specifically about doing this in Next.js, and uses localStorage, but because Next.js is JavaScript-rendered, you can force it to check the user-saved preference as the very first thing it does. That means it will render correctly the the first time (no flash). You do have to turn off server-side rendering for this to work, which is a gnarly trade-off though.

I’m not convinced there is a good way to avoid FART without a server-side language or force-delayed page renders.


The post Flash of inAccurate coloR Theme (FART) appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , ,
[Top]

A DRY Approach to Color Themes in CSS

The other day, Florens Verschelde asked about defining dark mode styles for both a class and a media query, without repeat CSS custom properties declarations. I had run into this issue in the past but hadn’t come up with a proper solution.

What we want is to avoid redefining—and thus repeating—custom properties when switching between light and dark modes. That’s the goal of DRY (Don’t Repeat Yourself) programming, but the typical pattern for switching themes is usually something like this:

:root {   --background: #fff;   --text-color: #0f1031;   /* etc. */ }  @media (prefers-color-scheme: dark) {   :root {     --background: #0f1031;     --text-color: #fff;     /* etc. */   } }

See what I mean? Sure, it might not seem like a big deal in an abbreviated example like this, but imagine juggling dozens of custom properties at a time—that’s a lot of duplication!

Then I remembered Lea Verou’s trick using --var: ;, and while it didn’t hit me at first, I found a way to make it work: not with var(--light-value, var(--dark-value)) or some nested combination like that, but by using both side by side!

Certainly, someone smarter must have discovered this before me, but I haven‘t heard of leveraging (or rather abusing) CSS custom properties to achieve this. Without further ado, here’s the idea:

--color: var(--light, orchid) var(--dark, rebeccapurple);

If the --light value is set to initial, the fallback will be used (orchid), which means --dark should be set to a whitespace character (which is a valid value), making the final computed value look like this:

--color: orchid  ; /* Note the additional whitespace */

Conversely, if --light is set to a whitespace and --dark to initial, we end up with a computed value of:

--color:   rebeccapurple; /* Again, note the whitespace */

Now, this is great but we do need to define the --light and --dark custom properties, based on the context. The user can have a system preference in place (either light or dark), or can have toggled the website‘s theme with some UI element. Just like Florens‘s example, we’ll define these three cases, with some minor readability enhancement that Lea proposed using “on” and “off” constants to make it easier to understand at a glance:

:root {    /* Thanks Lea Verou! */   --ON: initial;   --OFF: ; }  /* Light theme is on by default */ .theme-default, .theme-light {   --light: var(--ON);   --dark: var(--OFF); }  /* Dark theme is off by default */ .theme-dark {   --light: var(--OFF);   --dark: var(--ON); }  /* If user prefers dark, then that's what they'll get */ @media (prefers-color-scheme: dark) {   .theme-default {     --light: var(--OFF);     --dark: var(--ON);   } }

We can then set up all of our theme variables in a single declaration, without repetition. In this example, the theme-* classes are set to the html element, so we can use :root as a selector, as many people like to do, but you could set them on the body, if the cascading nature of the custom properties makes more sense that way:

:root {   --text: var(--light, black) var(--dark, white);   --bg: var(--light, orchid) var(--dark, rebeccapurple); }

And to use them, we use var() with built-in fallbacks, because we like being careful:

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

Hopefully you’re already starting to see the benefit here. Instead of defining and switching armloads of custom properties, we’re dealing with two and setting all the others just once on :root. That’s a huge improvement from where we started.

Even DRYer with pre-processors

If you were to show me this following line of code out of context, I’d certainly be confused because a color is a single value, not two!

--text: var(--light, black) var(--dark, white);

That’s why I prefer to abstract things a bit. We can set up a function with our favorite pre-processor, which is Sass in my case. If we keep our code above defining our --light and --dark values in various contexts, we need to make a change only on the actual custom property declaration. Let’s create a light-dark function that returns the CSS syntax for us:

@function light-dark($  light, $  dark) {   @return var(--light, #{ $  light }) var(--dark, #{ $  dark }); }

And we’d use it like this:

:root {    --text: #{ light-dark(black, white) };    --bg: #{ light-dark(orchid, rebeccapurple) };    --accent: #{ light-dark(#6d386b, #b399cc) }; }

You’ll notice there are interpolation delimiters #{ … } around the function call. Without these, Sass would output the code as is (like a vanilla CSS function). You can play around with various implementations of this but the syntax complexity is up to your tastes.

How’s that for a much DRYer codebase?

More than one theme? No problem!

You could potentially do this with more than two modes. The more themes you add, the more complex it becomes to manage, but the point is that it is possible! We add another theme set of ON or OFF variables, and set an extra variable in the list of values.

.theme-pride {   --light: var(--OFF);   --dark: var(--OFF);   --pride: var(--ON); }  :root {   --text:     var(--light, black)     var(--dark, white)     var(--pride, #ff8c00)   ; /* Line breaks are absolutely valid */    /* Other variables to declare… */ }

Is this hacky? Yes, it absolutely is. Is this a great use case for potential, not-yet-existing CSS booleans? Well, that’s the dream.

How about you? Is this something you’ve figured out with a different approach? Share it in the comments!


The post A DRY Approach to Color Themes in CSS appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, ,
[Top]

Color Theming with CSS Custom Properties and Tailwind

Custom properties not only enable us to make our code more efficient, but allow us to work some real magic with CSS too. One area where they have huge potential is theming. At Atomic Smash we use Tailwind CSS, a utility class framework, for writing our styles. In this article, we’ll look at how custom properties can be used for theming, and how we can integrate them with Tailwind to maximize the reusability of our code. We won’t cover getting up and running with Tailwind — check out the official documentation for that — but even if you’re new to it you might find some of these tips useful.

Theming overview

Let’s say we have a “Call To Action” (CTA) component with a heading, body copy, and button.

A box with a light red heading that reads join our mailing list above a dark red body that reads be the first to hear about our new offerings right before a red signup button.

Writing regular (non-Tailwind) CSS for this color scheme would look something like this:

.cta {   background-color: #742a2a; // dark red   color: #ffffff; //white }      .cta__heading {   background-color: #e53e3e; // medium red   color: #742a2a; } 
 .cta__button {   background-color: #e53e3e; }

Using Tailwind, we would apply these colors as utility classes in our HTML:

<div class="bg-red-900 text-white">   <h3 class="bg-red-600 text-red-900">Join our mailing list</h3>   <div>     <p>Be the first to hear about our new offerings</p>     <button class="bg-red-600" type="button">Sign up</button>   </div> </div>

I’ve deliberately left out classes relating to anything other than the basic color scheme, but you can see the example in its entirety in this demo:

Now, if we wanted to apply a different color scheme to our component, we would need to override the color values of our original component. Without Tailwind, a common way to do that would be to append a theme class to the component itself, and redefine the color values lower down in the cascade. So for a component with a modifier class of .cta--blue (using the BEM convention) we’ll apply the CSS values for a blue color scheme:

.cta--blue {   background-color: #2a4365; // dark blue } 
 .cta--blue .cta__heading {   background-color: #3182ce; // medium blue   color: #2a4365; } 
 .cta--blue .cta__button {   background-color: #3182ce; }
A box with a light blue heading that reads join our mailing list above a dark bluebody that reads be the first to hear about our new offerings right before a blue signup button.

If we’re using Sass or another preprocessor, it’s likely we’ll make life easier for ourselves by using variables for those color names, and we might nest the .cta__heading and .cta__body selectors. It doesn’t exactly make our code more concise, but it does make it more manageable by having a single place to update those values.

Now, suppose we have 10 different color schemes, as was my experience on a recent project. Our code starts to get longer, as we’re basically duplicating the above example 10 times in order to change those color values. Now imagine every component in our design system needs 10 color schemes, and many of those components are far more complex than our simple CTA. Maybe our themes need different fonts too. Suddenly we have a lot of CSS to write.

Theming with Tailwind

If we’re using Tailwind, on the other hand, we’d need to change multiple classes in the HTML itself. Even if we’re using a JavaScript framework, like React or Vue, this is not exactly a trivial task. In order to ensure unused styles are removed in a production build, Tailwind discourages the use of string concatenation for class names (at the time of writing). So building our themes means potentially piling a lot of logic into our components.

Theming with Custom Properties

By using custom properties for our color themes, we can drastically reduce the amount of code we need to write, and alleviate the maintenance burden. Let’s first take a look at how we can do this in regular CSS.

We define our custom properties as variables on the :root selector, making them global variables. (The body selector would serve us just as well.) Then we can use those variables in a selector, in place of our color property values:

:root {   --primary: #742a2a; // dark red;   --secondary: #e53e3e; // medium red } 
 .cta {   background-color: var(--primary);   color: white; } 
 .cta__heading {   background-color: var(--secondary);   color: var(--primary); } 
 .cta__button {   background-color: var(--secondary); }

This is where the real magic happens: now the code for creating each of our themes becomes a case of only updating those custom property values. The new values will be inherited wherever we apply our theme class:

.th-blue {   --primary: #2a4365; // dark blue   --secondary: #3182ce; // medium blue }

If we want a blue color scheme, we can apply that .th-blue class to the component, or even use it on the <body> tag to apply to apply a page-wide theme, which can be overridden on individual components as desired. Using a utility class potentially saves us writing even more code compared to a component-specific class (such as .cta--blue in the original code), as it could be applied anywhere in our codebase.

Handling older browsers

Like many agencies, plenty of our clients at Atomic Smash still require us to support Internet Explorer 11. While I’m okay with a progressive enhancement approach in most cases (by providing simpler fallback layouts for browsers that don’t support CSS Grid, for instance), I find theming is one area that often doesn’t allow for easy compromise. Clients want their brand colors and fonts seen, even on older browsers. Providing fallbacks using feature queries would entail a lot of extra work that would negate the benefits of using custom properties in the first place. To overcome this, we need a polyfill.

There are a couple of options for polyfilling custom properties in IE 11.

postcss-custom-properties

The first is using a PostCSS plugin called postcss-custom-properties. If you’re already using PostCSS in your workflow, this is fairly simple to add. It works by processing your CSS and outputting the result of the variable as the property value. So if you have the following CSS:

:root {   --color: red; } 
 h1 {   color: var(--color); }

The processed result will be:

h1 {   color: red;   color: var(--color); }

Browsers that don’t support custom properties will ignore the second rule and fall back to the regular property value. There is also an option to remove the rules with the custom properties in the output, so the file size will be smaller. This means that no browsers will get the custom property — which is an issue if you’re updating variables dynamically — but you’ll be able to use them for static values in your code with no ill effects.

Unfortunately this polyfill has some limitations:

  1. You need to specify the file (or files) in your config where you’re defining the custom properties.
  2. Custom properties can only be defined on the :root selector.

The first limitation is relatively trivial, but the second unfortunately renders this polyfill entirely useless for our theming use case. It means we can’t redefine variables on a selector to create our themes.

ie11CustomProperties

This polyfill option involves serving a client-side script, rather than preprocessing the CSS. We can add the following script to our head to ensure the polyfill will only be loaded in IE 11:

<script>window.MSInputMethodContext && document.documentMode && document.write('<script src="https://cdn.jsdelivr.net/gh/nuxodin/ie11CustomProperties@4.1.0/ie11CustomProperties.min.js"></script>');</script>

This permits us to enjoy the full benefits of custom properties as in the examples here, so it’s the solution I decided to go with. It has a limitation where custom properties set in style attributes aren’t polyfilled. But I’ve tested it for the theming example above and it works just fine.

But what does this have to do with Tailwind? 

As we’ve already seen, utility classes — single-purpose classes that can be applied anywhere in our HTML — can make our code more reusable. That’s the main selling point of Tailwind and other utility class frameworks — the size of the CSS file you ship should end up smaller as a result. Tailwind makes multiple color classes available: .bg-red-medium would give us a red background-color property value, .text-red-medium for color and so on for border, box-shadow, or any place you can think of that you might need a color value. 

Colors can be defined in a config file:

module.exports = {   theme: {     colors: {       red: {         medium: '#e53e3e',         dark: '#742a2a'       },       blue: {         medium: '#3182ce',         dark: '#2a4365'       }     }   } }

If we want to use custom property values for our Tailwind classes, we can specify them in the config:

module.exports = {   theme: {     colors: {       'th-primary': 'var(--primary)',       'th-secondary': 'var(--secondary)'     }   } }

I’m prefixing my colors and theme-related class names with th- so that it’s obvious they’re specifically related to theming, but feel free to use whatever convention suits you.

Now those classes will be available to us through Tailwind. Using .bg-th-primary gives us the equivalent of writing:

.some-element {   background-color: var(--primary); }

In our CSS we can define our custom properties for our themes as before:

:root {   --primary: #742a2a;   --secondary: #742a2a; } 
 .th-blue {   --primary: #2a4365;   --secondary: #3182ce; }

Let’s apply those classes to our HTML. The first example gives us a component with our default theme (the variables defined on the :root). The second has our blue theme. The only difference is the addition of the .th-blue class on the component. (Once again, I’ve omitted the classes unrelated to the theme, for brevity and clarity.)

<!--Component with default (red) theme--> <div class="bg-th-primary">   <h3 class="bg-th-secondary text-th-primary">Join our mailing list</h3>   <div>     <p>Be the first to hear about our new offerings</p>     <button class="bg-th-secondary" type="button">Sign up</button>   </div> </div> 
 <!--Component with blue theme--> <div class="th-blue bg-th-primary">   <h3 class="bg-th-secondary text-th-primary">Join our mailing list</h3>   <div>     <p>Be the first to hear about our new offerings</p>     <button class="bg-th-secondary" type="button">Sign up</button>   </div> </div>

Using the config as a style guide

Tailwind encourages you to define all variables in the config, and personally I agree that it’s a better approach. It means that the config file can be a single source of truth rather than (potentially) ending up with multiple places to define your colors and other theme values. Luckily, we can also use values from the Tailwind config file for our custom properties. We’ll need to first define all of our colors in the config (assuming we’re not using the default color palette included with Tailwind):

module.exports = {   theme: {     colors: {       red: {         medium: '#e53e3e',         dark: '#742a2a'       },       blue: {         medium: '#3182ce',         dark: '#2a4365'       },       'th-primary': 'var(--primary)',       'th-secondary': 'var(--secondary)'     }   } }

Then we can access the theme object in the CSS:

:root {   --th-primary: theme('colors.red.dark');   --th-secondary: theme('colors.red.medium'); } 
 .th-blue {   --th-primary: theme('colors.blue.dark');   --th-secondary: theme('colors.blue.medium'); }

Wrapping up

I’m really excited about the benefits of being able to use custom properties without having to worry about browser support, even more so to be able to integrate them smoothly with our existing workflow. It’s hard to overstate the amount of time they will save us for theming. I hope that even if you’re not a Tailwind user, this article might encourage you to give custom properties a go for this use case.


The post Color Theming with CSS Custom Properties and Tailwind appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , ,
[Top]