Figured out how to color text black or white depending on background lightness without breaking the components down to HSL in #css
Edit: this is now a blog post! https://miunau.com/posts/dynamic-text-contrast-in-css/
Quick and dirty filter stackingAssuming --bgColor contains your background color:
span {color: var(--bgColor);
filter: invert(1) grayscale(1) brightness(1.3) contrast(9000);
mix-blend-mode: luminosity;
opacity: 0.95;
}
This will turn to black around #AAAAAA. adjust brightness lower if you want it to turn to black earlier. play around with contrast as well, using low and high values.
After playing around with this (thanks @mia), we noticed there's some fringes happening at certain color values right when it is about to switch from black to white, so this might work best with colors that you get to control to some degree.
Less fringing with SVG filtersBut! Here is a version that has no fringing:
Add this somewhere in your markup- it can be anywhere as long as the id bwFilter can be referenced.
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" height="0">
<defs>
<filter id="bwFilter" color-interpolation-filters="sRGB">
<!-- Convert to grayscale based on luminance -->
<feColorMatrix type="matrix"
values="0.2126 0.7152 0.0722 0 0
0.2126 0.7152 0.0722 0 0
0.2126 0.7152 0.0722 0 0
0 0 0 1 0"/>
<!-- Expand edges slightly to clean up any fringing -->
<feMorphology operator="dilate" radius="2"/>
<!-- Apply the threshold to determine if the color should be black or white -->
<feComponentTransfer>
<feFuncR type="linear" slope="-255" intercept="128"/>
<feFuncG type="linear" slope="-255" intercept="128"/>
<feFuncB type="linear" slope="-255" intercept="128"/>
</feComponentTransfer>
<!-- Composite step to clean up the result -->
<feComposite operator="in" in2="SourceGraphic"/>
</filter>
</defs>
</svg>
Then just reference it in your css:
span {color: var(--bgColor);
filter: url(#bwFilter);
}
tada! no fringing!
svg version here: https://codepen.io/miunau/pen/oNVaJoN?editors=1100