Skip to content

Commit

Permalink
Swap between black and white text based on background color
Browse files Browse the repository at this point in the history
  • Loading branch information
jakelazaroff committed Dec 3, 2024
1 parent 0ddbe97 commit 4f18c4e
Showing 1 changed file with 62 additions and 0 deletions.
62 changes: 62 additions & 0 deletions css/swap-between-black-and-white-text-based-on-background-color.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Swap between black and white text based on background color

The title is overselling it a little, but not by much. Devon Govett posted [this clever trick](https://bsky.app/profile/devongovett.bsky.social/post/3lcedcdj4qk2y) on Bluesky using [CSS relative colors](https://developer.chrome.com/blog/css-relative-color-syntax) and [LCH](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/lch):

```css
.magic {
--bg: red;
background: var(--bg);
color: lch(from var(--bg) calc((49.44 - l) * infinity) 0 0);
}
```

So it won't automagically work with any color behind the element; you need to have it stored as a CSS variable.

LCH has three properties: lightness, chroma and hue.

- Lightness is a number between `0` and `100` representing how bright a color is. `0` corresponds to black, while `100` corresponds to white.
- Chroma is a technically unbounded number that represents "how much" color is present.
- Hue is an angle that represents the hue angle, as in HSL or HSV.

You supply them to the `lch` function like this:

```css
.magic {
--lightness: 50;
--chroma: 72.2;
--hue: 56.2;
color: lch(var(--lightness) var(--chroma) var(--hue));
}
```

When used with relative color syntax, the color gets "broken up" into its constituent parts which are referred to by `l`, `c` and `h`. So, for example, this would set the background color to the same as the text color; `l`, `c` and `h` take their values from the `from` color and are placed in the appropriate slots unmodified.

```css
.magic {
--bg: red;
background: var(--bg);
color: lch(from var(--bg) l c h);
}
```

Devon's example makes two big changes:

1. It discards the chroma and hue values, replacing them with `0`.
2. It inverts the color's lightness and multiplies it by `infinity` to obtain white or black.

#2 might be confusing, so let's dig into some examples. Remember, the calculation is `(49.44 - l) * infinity`, clamped within the range `[0, 100]`:

- CSS `red` has an LCH lightness of `54.29`.
1. `49.44` - `54.29` = `-4.85`
2. `-4.85` \* `infinity` = `-infinity`
3. `-infintiy` gets clamped to `0` (black)
- CSS `blue` has an LCH lightness of `29.57`.
1. `49.44` - `29.57` = `19.87`
2. `19.87` \* `infinity` = `-infinity`
3. `infintiy` gets clamped to `100` (white)
- CSS `white` has an LCH lightness of `100`.
1. `49.44` - `100` = `-50.56`
2. `-4.85` \* `infinity` = `-infinity`
3. `-infintiy` gets clamped to `0` (black)

Why `49.44`? Devon tested it with all RGB colors and found it had the least number of WCAG 4.5:1 contrast failures.

0 comments on commit 4f18c4e

Please sign in to comment.