Tag: color

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

, ,

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]

How-to guide for creating edge-to-edge color bars that work with a grid

Hard-stop gradients are one of my favorite CSS tricks. Here, Marcel Moreau combines that idea with CSS grid to solve an issue that’s otherwise a pain in the butt. Say you have like a 300px right sidebar on a desktop layout with a unique background color. Easy enough. But then say you want that background color to stretch to the right edge of the browser window even though the grid itself is width-constrained. Tricker.

Direct Link to ArticlePermalink

The post How-to guide for creating edge-to-edge color bars that work with a grid appeared first on CSS-Tricks.

CSS-Tricks

, , , , , , ,
[Top]

The Expanding Gamut of Color on the Web

CSS was introduced to the web all the way back in 1996. At the time, most computer monitors were pretty terrible. The colors of CSS — whether defined with the RGB, HSL, or hexadecimal format — catered to the monitors of the time, all within the sRGB colorspace.

Most newer devices have a wide-gamut display. A gamut is the range of colors that can be displayed. A wide-gamut display is capable of showing more colors than sRGB. They use the Display P3 colorspace. (There’s also Rec.2020, an even larger colorspace, but that’s pretty rare and not currently worth thinking about.) As Lea Verou of the CSS working group put it, “Our websites are washed out because screens advanced faster than CSS Color did.” If we want to make full use of the range of colors that the majority of screens are capable of displaying, we need to use new CSS colors formats: lab, lch or display-p3.

Examples in the wild can be found on the website of Panic (creators of the once popular Coda text editor and the still very popular Untitled Goose Game) or the marketing site for a product called Playdate. They both make use of strikingly vibrant and intense colors that are uniquely vivid by making use of display-p3.

Screenshot taken from the Panic website showing bright pink text against a stark black background.
Panic’s website features an eye-catching shade of pink.

To get some idea of the range of colors that are missing from sRGB, check out the following Pen. The inner boxes contain a color beyond the sRGB gamut. The outer boxes show that color clamped to the sRGB color gamut (meaning the nearest equivalent color that a browser is capable of showing without using display-p3, lab, or lch). (Note that support is currently limited to Safari users.)

The color picker in Safari Technology Preview helpfully shows which colors lie outside of the sRGB color gamut.

Screenshot of a color picker going from bright green to black with a light curved line signifying the point where colors go past the typical sRGB range.
Any color above or to the right of the white line lie outside of the sRGB gamut

A tale of new syntaxes

Before jumping into the syntax for lab(), lch(), and the color() function, let’s take a look at the new rgb() and hsl() syntaxes (which are supported in all web browsers, minus Internet Explorer).

Type Old Syntax New Syntax
RGB rgb(0, 128, 255) rgb(0 128 255)
RGBa rgba(0, 128, 255, 0.5) rgb(0 128 255 50%)
HSL hsl(198, 28%, 50%) hsl(198 28% 50%)
HSLa hsla(198, 28%, 0.5) hsl(198deg 28% 50% / 50%)
Source: @mathias 

In the older syntax, each number is comma separated: rgb(200, 100, 20);. Commas are no longer necessary, so the space separated value rgb(200 100 20); is valid. To specify transparency, we can now use rgb(200 100 20 / 50%) rather than using  rgba() or hsla(). There’s no real benefit to the newer syntaxes but it’s worth looking at because they match the syntax for lch(), lab() and color()

Type Syntax
Lab lab(56.29% -10.93 16.58 / 50%)
color() color(sRGB 0 0.5 1 / 50%)
LCH lch(56.29% 19.86 236.62 / 50%

lab(), lch() and color() always use space separated numbers (no commas allowed) and a forward slash followed by a percentage to specify transparency. Let’s take a look at how they work.  

The CSS color() function and display-p3 colorspace

The color() function allows a color to be specified in a particular colorspace (rather than using the sRGB colorspace used by rgb(), hsl(), or hex). The colorspace we need to specify in order to use wide-gamut color is display-p3, which uses three numeric values, representing the red, green, and blue channels of the color: 1 0 0 is total red, 0 0 1 is total blue, and 0 1 0 is total green.

background-color: color(display-p3 1 0 0.331); /* vibrant pink color */

At the time of writing, display-p3 is the only way to access high-gamut colors, having been supported in Safari since 2017. However, lab() and lch() will be better options once they are implemented (Chrome and Safari are currently working on it). Here’s a take from Lea Verou

display-p3 is not perceptually uniform, and is difficult to create variants (lighter or darker, more or less vivid etc) by tweaking its parameters. Furthermore, it’s a short-term solution. It works now, because screens that can display a wider gamut than P3 are rare. Once hardware advances again, color(display-p3 ...) will have the same problem as sRGB colors have today. LCH and Lab are device independent, and can represent the entire gamut of human vision so they will work regardless of how hardware advances.

A better lightness: Lab and LCH

You may have seen articles around the web arguing that HSL is easier to reason about than RGB or Hexadecimal values. 

Here’s Chris Coyier in 2015:

The real appeal of HSLa is that it makes more intuitive sense what changing the values will do to the color. Increasing the second value will increase the saturation of that color. Decreasing the third value will decrease the lightness of that color. That makes creating your own color variations on the fly way easier.

While HSL might be easier to understand than hexadecimal or RGB, it’s far from perfect. The way it calculates lightness simply doesn’t match human perception. According to HSL, hsl(240deg 100% 50%) and hsl(60deg 100% 50%) have the same lightness, 50%. Let’s compare the two.

To the human eye, the blue looks darker. As Brian Kardell puts it: 

Doing things like mixing colors, lightening, darkening, can be done well only if they include a sense of how our eyes really work rather than how machines like to think about storing and displaying.

Here’s a visual example from Lea Verou that demonstrates the superiority of Lab/LCH over HSL. She comments

A trick for aesthetically pleasing gradients of the same color at different lightnesses is to convert to Lab, vary the L instead, and then convert back to HSL/RGB.

“The perceived brightness of all of the hues in a spectrum with the same saturation and lightness. […] It’s quite clear they’re different.” —Brian Kardell (Image: Rob Waychert)

Lab and LCH both use the CIELAB colorspace which is designed to align with human vision. If you give two colors the same lightness value, they appear to the human eye to have the same lightness, regardless of their hue.

Lab

background-color: lab(40% 83 -104); /* a shade of purple */

The L in lab() stands for lightness and is written as a percentage (which can go up to 400% for extra bright white, but will generally be between 0% and 100% ). A and B don’t stand for anything — they’re color channels. A is a numerical value between green (negative values) and red (positive values) while B is a numerical value between blue (negative values) and yellow (positive values). Lightness is pretty easy for us to understand. The red/green value and blue/yellow value, however, aren’t exactly intuitive. LCH is probably a better alternative.

LCH

background-color: lch(69% 56 244); /* a shade of blue */

lch() is the most human-readable of the new color values. L again stand for lightness (and works in exactly the same way), C is for chroma, and H is for hue. Chroma is largely analogous to saturation, but it can also be thought of as the color intensity or vibrancy. Unlike the other new color formats, you can actually predict the sort of effect changing these individual values will have — its similar to HSL in this way. The best way to get your head around it is to try out this LCH color picker.

Defining fallbacks 

We have two kinds of support to think about: browser support for the new CSS color values and the ability of screens to display these colors.

Falling back to the closest matching sRGB value for browsers that don’t support color functions is easy and exactly like we’re used to defining fallback properties:

.pink-text {   color: rgb(255, 0, 79); /* Will be used as a fallback */   color: color(display-p3 1 0 0.331); /* Will be used if supported */ }

The second line of code in the example above will be ignored if the browser doesn’t understand it and the rgb() value will be used instead, thanks to the cascade. It would be laborious to type out two lines of CSS every time you want to specify a color. CSS variables are a great way to deal with this. In this example we’ll use @supports to tell if the browser has support for color functions in CSS:

/* https://webkit.org/blog/10042/wide-gamut-color-in-css-with-display-p3/ */ :root {   --bright-green: rgb(0, 255, 0); } 
 /* Display-P3 color, when supported. */ @supports (color: color(display-p3 1 1 1)) {   :root {     --bright-green: color(display-p3 0 1 0);   } } 
 header {   color: var(--bright-green); }

If the color is particularly important to your design, you could utilize a background-image as most browsers do support high-gamut colors in images.

@supports not (color: color(display-p3 1 0 0.331)) {   @supports (-webkit-background-clip: text){     .pink-text {       background-image: url("pink-P3.png");       background-size: cover;       -webkit-background-clip: text;       -webkit-text-fill-color: transparent;     }   } } 
 .pink-text {   color: rgb(255, 0, 79);   color: color(display-p3 1 0 0.331); }

There is a PostCSS plugin that converts lab() and lch() functions to rgb(). If you’re into Sass there is a tool from Miriam Suzanne called Blend.

A media query for color

@supports tells us whether the browser supports the relevant CSS syntax. What it doesn’t tell us  is whether a user’s monitor can actually display certain color values. If a monitor doesn’t support high-gamut color, the screen will display the nearest equivalent sRGB color. This means all monitors are catered for without writing any extra code.

However, if you’d rather choose the fallback color manually yourself rather than let the browser calculate one for you, you can pass a second color value to the color()  function. This would, however, require browser support for the color function (but support for the second argument hasn’t landed in any browser yet).

background-color: color(display-p3 1 0 0.331, #f54281);

Should you need greater control to do something fancy, the Media Queries Level 4 spec brings a new color-gamut media query that can help us here.

@media (color-gamut: p3) {    /* Code to run only on hardware that supports P3 color */ }

In this example, we are obviously checking for P3 support, but we could also check for the rec-2020 colorspace we alluded to earlier, which has an even wider gamut than P3. The number of screens supporting rec-2020 is currently minimal and only include high-definition televisions, meaning they won’t be a common target for developers in the near future. You can also check for sRGB support, but that is almost all monitors nowadays. The color-gamut query, on the other hand, has reasonably good browser support at the time of writing.

Sidenote: dynamic-range media query

In the Safari 13.1 release notes, the dynamic-range media query is is used to conditionally apply a P3 color.  Apparently, that’s not a good use case. According to Florian Rivoal (editor of the Media Queries specification), this query is designed to be used for video:

[S]ome screen can show ultra-bright lights for brief amounts of times, that are used in videos for things like sparks, direct sunlight, etc. This is much brighter than white, and isn’t meant to be used with static images. It would be uncomfortable, and would also damage the screen.

One more sidenote: Design tool support

Unfortunately popular web design tools like Figma, Sketch and XD do not currently support Lab, LCH or P3 colorspaces. Photoshop, however, does have a Lab color picker.


There we have it! CSS colors are expanding at a time when screens support more colors than ever. It’s an exciting time for color nerds out there!

The post The Expanding Gamut of Color on the Web appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]

No-Comma Color Functions in CSS

There have been a couple of viral tweets about this lately, one from Adam Argyle and one from Mathias Bynes. This is a nice change that makes CSS a bit more clear. Before, every single color function actually needs two functions, one for transparency and one without, this eliminates that need and brings the syntax more-inline with CSS grammar overall.

Lemme make some code block from Mathias’ tweet here.

/* Old Syntax */ rgb(0, 128, 255)  rgba(0, 128, 255, 0.5)  hsl(198, 38% 50%)  hsla(198, 28%, 50%, 0.5)

/* New Syntax */ rgb(0 128 255)  rgb(0 128 255 / 50%)  hsl(198deg 28% 50%)  hsl(198deg 28% 50% / 50%)  lab(56.29% -10.93 16.58 / 50%)  lch(56.29% 19.86 236.62 / 50%)  color(sRGB 0 0.50 1 / 50%)

Thought party:

  • The browser support is pretty good: everything but IE 11.
  • If you need IE 11 support, you can preprocess it (or not use it). PostCSS’s preset-env does it as well as the very specific plugin postcss-color-rgb (weird it doesn’t do HSL also).
  • If you don’t like it, you literally never need to use it. No browser will ever pull support for such an important feature.
  • The reason to switch is muscle memory and consistent-looking codebases as new color functions (e.g, lab, lch, and color) will only support this new syntax.
  • There is a weird hybrid between old and new. You can pass an opacity value to rgb() and it still works like rgb(255, 0, 0, 0.5);.
  • If you need it in Sass (which is apparently a pain to support), there is a weird workaround. I would guess Sass will get around to supporting it. If they can’t, this is the kind of barb that drives people away from projects.
  • Prettier, which is in the business of cleaning up your code from the perspective of spacing and syntax, could intervene here and convert syntax, but it’s not going to (the Prettier stance is to not change the AST).
  • I imagine DevTools will start showing colors in this format, which will drive adoption.
  • Remember even hex code colors have a fancy new format.

The post No-Comma Color Functions in CSS appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]

Creating Color Themes With Custom Properties, HSL, and a Little calc()

Before the advent of CSS custom properties (we might call them “variables” in this article as that’s the spirit of them), implementing multiple color schemes on the same website usually meant writing separate stylesheets. Definitely not the most maintainable thing in the world. Nowadays, though, we can define variables in a single stylesheet and let CSS do the magic.

Even if you aren’t offering something like user-generated or user-chosen color themes, you might still use the concept of theming on your website. For example, it is fairly common to use different colors themes across different areas of the site.

We’re going to build out an example like this:

Same layout, different colors.

In this example, all that changes between sections is the color hue; the variations in lightness are always the same. Here’s an example of a simplified color palette for a specific hue:

A palette of multiple hues might look something like this:

This would take effort to do with RGB color value, but in HSL only one value changes.

Enter custom properties

Custom properties have been around for a while and are widely supported. Polyfills and other solutions for IE 11 are also available.

The syntax is very similar to traditional CSS. Here is an overview of the basic usage:

It’s common to see variables defined on the :root pseudo-element, which is always <html> in HTML, but with higher specificity. That said, variables can be defined on any element which is useful for scoping specific variables to specific elements. For example, here are variables defined on data attributes:

Adding calc() to the mix

Variables don’t have to be fixed values. We can leverage the power of the calc() function to automatically calculate values for us while adhering to a uniform pattern:

Since CSS doesn’t support loops, a preprocessor would be handy to generate a part of the code. But remember: CSS variables are not the same as Sass variables.

Implementing CSS variables

What we’re basically trying to do is change the color of the same component on different sections of the same page. Like this:

We have three sections in tabs with their own IDs: #food, #lifestyle, and #travel. Each section corresponds to a different hue. The  data-theme-attribute on the div.wrapper element defines which hue is currently in use.

When #travel is the active tab, we’re using the --first-hue variable, which has a value of 180°. That is what gets used as the --hue value on the section, resulting in a teal color:

<div class="wrapper" data-theme="travel">
.wrapper[data-theme="travel"] {   --hue: var(--first-hue);  /* = 180° = teal */ }

Clicking any of the tabs updates the data-theme attribute to the ID of the section, while removing the hash (#) from it. This takes a smidge of JavaScript. That’s one of the (many) nice things about CSS: they can be accessed and manipulated with JavaScript. This is a far cry from preprocessor variables, which compile into values at the build stage and are no longer accessible in the DOM.

<li><a href="#food">Food</a></li>
const wrapper = document.querySelector('.wrapper'); document.querySelector("nav").addEventListener('click', e => {   e.preventDefault();   e.stopPropagation();   // Get theme name from URL and ditch the hash   wrapper.dataset.theme = e.target.getAttribute('href').substr(1); })

Progressive enhancement

When we use JavaScript, we should be mindful of scenarios where a user may have disabled it. Otherwise, our scripts — and our UI by extension — are inaccessible. This snippet ensures that the site content is still accessible, even in those situations:

document.querySelectorAll('section').forEach((section, i) => {   if (i) { // hide all but the first section     section.style.display = 'none';   } })

This merely allows the tabs to scroll up the page to the corresponding section. Sure, theming is gone, but providing content is much more important.

While I chose to go with a single-page approach, it’s also possible to serve the sections as separate pages and set [data-theme] on the server side. 

Another approach

So far, we’ve assumed that color values change linearly and are thus subject to a mathematical approach. But even in situations where this is only partially true, we may still be able to benefit from the same concept. For instance, if lightness follows a pattern but hue doesn’t, we could split up the stylesheet like this:

<head>   <style>     :root {       --hue: 260;     }   </style>   <link rel="stylesheet" href="stylesheet-with-calculations-based-on-any-hue.css"> </head>

Supporting web components

Web components are an exciting (and evolving) concept. It’s enticing to think we can have encapsulated components that can be reused anywhere and theme them on a case-by-case basis. One component with many contexts!

We can use CSS variable theming with web components. It requires us to use a host-context() pseudo-selector. (Thanks to habemuscode for pointing this out to me!)

:host-context(body[data-theme="color-1"]) {   --shade-1: var(--outsideHSL); }

In summary…

Theming a website with CSS custom properties is much easier than the workaround approaches we’ve resorted to in the past. It’s more maintainable (one stylesheet), performant (less code), and opens up new possibilities (using JavaScript). Not to mention, CSS custom properties become even more powerful when they’re used with HSL colors and the calc() function.

We just looked at one example where we can change the color theme of a component based on the section where it is used. But again, there is much more opportunity here when we start to get into things like letting users change themes themselves – a topic that Chris explores in this article.

The post Creating Color Themes With Custom Properties, HSL, and a Little calc() appeared first on CSS-Tricks.

CSS-Tricks

, , , , , ,
[Top]