This is standardised by W3C's Web Content Accessibility Guidelines (WCAG) and decided based on the contrast ratio between colors, which you can calculate programmatically.
In your case, you would want to use whichever of white or black has a higher contrast ratio with the background color.
Here is just one of many articles on how to calculate contrast ratio in JavaScript: dev.to: Building your own color contrast checker.
From that article:
The contrast ratio between two colors is calculated using a mathematical function that we will see below. The resulting value of that calculation will go from 1 to 21 (normally represented as 1:1 or 21:1 respectively). If both are the exact same color, the contrast ratio will be 1; The highest contrast is between white and black, and exact opposites in the color wheel (complementary colors) will have high values too.
The mathematical formula is documented by WCAG.
contrast ratio
(L1 + 0.05) / (L2 + 0.05), where
- L1 is the relative luminance of the lighter of the colors, and
- L2 is the relative luminance of the darker of the colors.
It uses relative luminance:
For the sRGB colorspace, the relative luminance of a color is defined as L = 0.2126 * R + 0.7152 * G + 0.0722 * B where R, G and B are defined as:
- if RsRGB <= 0.03928 then R = RsRGB/12.92 else R =
((RsRGB+0.055)/1.055) ^ 2.4
- if GsRGB <= 0.03928 then G = GsRGB/12.92 else G = ((GsRGB+0.055)/1.055) ^ 2.4
- if BsRGB <= 0.03928 then B = BsRGB/12.92 else B = ((BsRGB+0.055)/1.055) ^ 2.4
and RsRGB, GsRGB, and BsRGB are defined as:
- RsRGB = R8bit/255
- GsRGB = G8bit/255
- BsRGB = B8bit/255
There exist free online contrast calculators like this one from WebAIM. In your case, add #fff as one value and your other color as the other. In most cases, you only need WCAG AA passing to be considered accessible. WCAG AA considers a 4.5:1 ratio to be passing. Usually it's good to go just a little darker than a minimum passing value, and the contrast checker can help you find a value for that.