RSS FeedTwitterMastodonBlueskyShare IconHeart IconGithub IconArrow IconClock IconGUI Challenges IconHome IconNote IconBlog IconCSS IconJS IconHTML IconShows IconGit IconSpeaking IconTools IconShuffle IconNext IconPrevious IconCalendar IconCalendar Edit Icon
Screenshot of the final code snippet from this blog post.
A series of images of an avatar doing a bunch of skateboard tricks.4 min read

A color-contrast() strategy for complimentary translucent backgrounds

css

The goal I had in mind was to have maximized text contrast against any color. This meant the text color should contrast well, but also I wanted a supportive translucent background to help bump that contrast even further, for situations where it may have otherwise had low contrast.

To do this:

  1. I find a good contrasting text color with color-contrast().
  2. Then I find a contrasting color for that text color with color-contrast().
  3. Use that text contrast color as a supportive translucent background with relative color syntax oklch(from black l c h / 40%)

tldr;

  1. If the text color is white, it's supportive background should be translucent black.
  2. If the text color is black, it's supportive background should be translucent white.

It's so much easier to see, but you can try it here:

See how I've automated the text color and a supportive background on that text? I don't have to care about the color I'm overlaying now, the browser handles all of it dynamically!

The essentials of the effect #

I use 2 future CSS features that aren't well supported yet (but are very fun to play with):

  1. color-contrast() - spec
  2. "Relative color syntax" - spec

Safari Tech Preview is the only browser with support for relative color syntax at the time of writing this post. Chrome Canary has color-contrast() support but won't have the dynamic supportive background (see generic bg in CSS below).

First, the container background needs to be in a custom property so we can share it with other functions:

section {
  --bg: hsl(var(--hue) 50% 50%);
}

Then, the h1 need to put it's contrasting text color into a custom property so we can share it too:

h1 {
  /* pick either black or white based on --bg */
  --text: color-contrast(var(--bg) vs black, white);
  color: var(--text);
  
  /* generic semitransparent bg */
  background: hsl(0 0% 0% / 40%);
}

For the cherry on top, if the browser understands relative color syntax, then create a new semi-transparent color from the contrasting color of --text.

/* if relative color syntax is supported */
@supports (background: hsl(from red h s l)) {
  h1 {
    /* pick either black or white 
       depending which contrasts with --text,
       extract it and make it 40% semitransparent */
    background: oklch(from color-contrast(var(--text) vs black,white) l c h / 40%);
  }
}

All of it together:

@layer demo {
  section {
    --bg: hsl(var(--hue) 50% 50%);
  }
  
  h1 {
    --text: color-contrast(var(--bg) vs black, white);
    color: var(--text);
    background: hsl(0 0% 0% / 40%);
  }
  
  @supports (background: hsl(from red h s l)) {
    h1 {
      background: oklch(from color-contrast(var(--text) vs black,white) l c h / 40%);
    }
  }
}

Conclusion #

There you have it, dynamically contrasting text with a supportive translucent background. I think that's pretty rad stuff.

Here's the Codepen embedded, so in the future it'll just work 🤓

Mentions #

Join the conversation on

@argyleink last i remember, this feature was blocked by discussions around WCAG contrast algorithm not being perfect. is that still the case?

my take is that the algorithm could be changed to match what WCAG 3.0 recommends (years from now) and that it shouldn't block this feature today.

MayankMayank

@hi_mayank yes, blocked by WCAG algo chat. WCAG 3.0 is years out yep.

I have my own spec proposal for this function, find it here! https://observablehq.com/@argyleink/contrast-color

my take is the browser should allow manually setting it but make it's own "best judgement" in the interim. aka, mine allows the auto keyword where the algo goes, where in the spec right now it's required.

my take is also that by default it should use the user's contrast pref from the MQ.

i have many many thoughts on this..!

CSS contrast-color() Proposal Explorer
Adam ArgyleAdam Argyle

@argyleink that's so unfortunate that it's still blocked. especially since it's not something that can be easily emulated at build time (css vars being computed at runtime and whatnot) 😔

MayankMayank

@argyleink also holy hell ColorJS is so cool

MayankMayank

@hi_mayank so is ObservableHQ, i like coding my docs. it's all honest, there's the prose and code in harmony, proved and interactive. <3

Adam ArgyleAdam Argyle

@argyleink slightly off topic: Is that gif in your website a custom character from 'OlliOlli World'?

jerryjerry

@jerryD yep! i'm a big fan of their games and especially the new one. glad you noticed!

Adam ArgyleAdam Argyle

@argyleink I loved OO1 and OO2. Finally got started on OOW and I love it for multiple reasons.

jerryjerry

@jerryD i get so much zen vibes from those games. and OOW is just top notch work. I love the UI and the music, just as much as I love trickin out a whole level.

Adam ArgyleAdam Argyle

@argyleink Great post, as always! 👏 In browsers only supporting color-contrast() but not the relative color syntax, you could compose the background in the same way, they do it in Tailwind: https://adamwathan.me/composing-the-uncomposable-with-css-variables/#:~:text=Using%20CSS%20variables%20for%20partial%20values

Composing the Uncomposable with CSS Variables – Adam Wathan
Christian "Schepp" SchaeferChristian "Schepp" Schaefer

Crawl the CSS Webring