color
Reads a six-digit hex color and returns a readable text color, a dark flag, and darker, lighter or inverted shades.
import { color } from '@salla.sa/twilight-theme-engine/utils';In plain words
Merchants choose their own brand color, so a theme cannot know in advance whether white or black text is readable on it. color('#ed1c24') works it out: .text is '#FFFFFF' on a dark color and '#000000' on a light one, .isDark says which, and .darken(0.2) or .lighten(0.2) give shades for borders and hover states.
It is plain arithmetic on the color's red, green and blue values. It reads nothing from the store: pass it the store's color yourself (from useTheme()).
Signature
function color(primary: string): {
primary: string; // your input, unchanged
text: string; // '#FFFFFF' or '#000000'
isDark: boolean;
darken: (amount: number) => string; // amount from 0 to 1
lighten: (amount: number) => string; // amount from 0 to 1
invert: () => string;
rgb: () => { r: number; g: number; b: number };
}Try it live
#3b82f6#2f68c5#629bf8#c47d09color("#3b82f6") → isDark: true, text: #FFFFFF, rgb(): { r: 59, g: 130, b: 246 }
import { color } from '@salla.sa/twilight-theme-engine/utils';
const c = color('#3b82f6');
export function PromoButton({ children }: { children: React.ReactNode }) {
return (
<button type="button" style={{ background: c.primary, color: c.text, borderColor: c.darken(0.2) }}>
{children}
</button>
);
}
Example
import { useTheme } from '@salla.sa/twilight-theme-engine/hooks/useTheme';
import { color } from '@salla.sa/twilight-theme-engine/utils';
export function PromoButton({ children }: { children: React.ReactNode }) {
const { color: themeColor } = useTheme();
const c = color(themeColor.primary);
return (
<button
type="button"
style={{ background: c.primary, color: c.text, border: `1px solid ${c.darken(0.2)}` }}
>
{children}
</button>
);
}
How it behaves
isDarkisluminance < 0.5, where luminance is(0.299 × r + 0.587 × g + 0.114 × b) / 255.textfollows it, in upper case.darken(a)multiplies each channel by1 - a;lighten(a)moves each channelaof the way to 255.ais clamped to 0–1, and the result is a lower-case#rrggbb.The color is parsed once, when
color()is called; the returned functions reuse it. It is cheap enough to call during render.useTheme().coloralso carriestext,reverse_primary,reverse_textandis_dark, sent by Salla. They are separate values, not this calculation: the demo store sendsis_dark: truewithtext: '#000000'for#ed1c24, wherecolor('#ed1c24').textis'#FFFFFF'.
Gotchas
Only six hex digits parse (
#rrggbborrrggbb). A three-digit#fff, a name likered,rgb(…)or an eight-digit#rrggbbaais silently read as black:isDarkistrue,textis#FFFFFFandlighten(0.2)is#333333, whileprimarystill returns your input. Normalise the value first if it can arrive in another form.The JSDoc of
color()and docs/18-hooks-api.md saycolor('#3b82f6').isDarkisfalsewithtext'#000000'. The code returnstrueand'#FFFFFF': that blue's luminance is 0.478.docs/02-theme-engine-core.md shows a
createThemeColor()helper. The engine exports no such function;color()is the one that exists.