In Design Systems use, either created ad-hoc, and managed from within a web application, when we want to support flexibiilty in color management, one recurring theme is the color variation to use as the background for a given color. Tailwind CSS helps managing some of the coplexity by allowing class names, but we still end up with the same problem. A Text color might be better with a very specific level of luminosity of the background color.
Last active
October 22, 2025 16:00
-
-
Save renoirb/fccec5d60caed020d01bb164d3de8f7d to your computer and use it in GitHub Desktop.
Calculate and manage color contrast between text and its background color
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| export type HexColor = `#${string}`; | |
| /** | |
| * Calculate relative luminance (WCAG formula) | |
| * Returns value between 0 (black) and 1 (white) | |
| */ | |
| export const getLuminance = (r: number, g: number, b: number): number => { | |
| const [rs, gs, bs] = [r, g, b].map((c) => { | |
| const srgb = c / 255; | |
| return srgb <= 0.03928 | |
| ? srgb / 12.92 | |
| : Math.pow((srgb + 0.055) / 1.055, 2.4); | |
| }); | |
| return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs; | |
| }; | |
| /** | |
| * Convert hex color to RGB values as a 3 items long tuple. | |
| */ | |
| export const hexToRgb = (hex: HexColor): [number, number, number] => { | |
| const clean = hex.replace('#', ''); | |
| const bigint = parseInt( | |
| clean.length === 3 | |
| ? clean | |
| .split('') | |
| .map((c) => c + c) | |
| .join('') | |
| : clean, | |
| 16 | |
| ); | |
| return [(bigint >> 16) & 255, (bigint >> 8) & 255, bigint & 255]; | |
| }; | |
| /** | |
| * Get contrasting text color (black or white) for given background. | |
| * | |
| * @param bgColor - Background color in hex format | |
| * @returns '#000000' or '#ffffff' | |
| * | |
| * @example Usage Example | |
| * ```typescript | |
| * const fillColor: HexColor = '#ff6b35'; | |
| * getContrastingTextColor(fillColor); // Returns '#000000' or '#ffffff' | |
| * ``` | |
| */ | |
| export const getContrastingTextColor = ( | |
| bgColor: HexColor, | |
| lightTextColor: HexColor = '#000', | |
| darkTextColor: HexColor = '#FFF', | |
| ): HexColor => { | |
| const [r, g, b] = hexToRgb(bgColor); | |
| const luminance = getLuminance(r, g, b); | |
| // WCAG threshold: 0.5 works well for most cases | |
| return luminance > 0.5 ? darkTextColor : lightTextColor; | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment