Design system foundation — Tailwind 4 theme, CSS tokens, fonts #31
51
frontend/src/lib/design-system/contrast.test.ts
Normal file
51
frontend/src/lib/design-system/contrast.test.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
// WCAG 2.2 relative luminance for a single 8-bit channel value
|
||||
function channelLuminance(val: number): number {
|
||||
const sRGB = val / 255;
|
||||
return sRGB <= 0.04045 ? sRGB / 12.92 : ((sRGB + 0.055) / 1.055) ** 2.4;
|
||||
}
|
||||
|
||||
function relativeLuminance(hex: string): number {
|
||||
const r = parseInt(hex.slice(1, 3), 16);
|
||||
const g = parseInt(hex.slice(3, 5), 16);
|
||||
const b = parseInt(hex.slice(5, 7), 16);
|
||||
return 0.2126 * channelLuminance(r) + 0.7152 * channelLuminance(g) + 0.0722 * channelLuminance(b);
|
||||
}
|
||||
|
||||
function contrastRatio(hex1: string, hex2: string): number {
|
||||
const l1 = relativeLuminance(hex1);
|
||||
const l2 = relativeLuminance(hex2);
|
||||
const lighter = Math.max(l1, l2);
|
||||
const darker = Math.min(l1, l2);
|
||||
return (lighter + 0.05) / (darker + 0.05);
|
||||
}
|
||||
|
||||
// Design token values from app.css @theme
|
||||
const tokens = {
|
||||
colorText: '#1C1C18',
|
||||
colorTextMuted: '#6B6A63',
|
||||
colorPage: '#FAFAF7',
|
||||
colorSurface: '#F5F4EE',
|
||||
greenDark: '#2E6E39', // button background — --green (#3D8C4A) only gives 4.16:1, fails AA
|
||||
white: '#FFFFFF'
|
||||
};
|
||||
|
||||
describe('WCAG 2.2 AA contrast ratios', () => {
|
||||
it('--color-text on --color-page meets 4.5:1 (normal text)', () => {
|
||||
expect(contrastRatio(tokens.colorText, tokens.colorPage)).toBeGreaterThanOrEqual(4.5);
|
||||
});
|
||||
|
||||
it('--color-text on --color-surface meets 4.5:1 (card text)', () => {
|
||||
expect(contrastRatio(tokens.colorText, tokens.colorSurface)).toBeGreaterThanOrEqual(4.5);
|
||||
});
|
||||
|
||||
it('--color-text-muted on --color-page meets 4.5:1 (muted labels)', () => {
|
||||
expect(contrastRatio(tokens.colorTextMuted, tokens.colorPage)).toBeGreaterThanOrEqual(4.5);
|
||||
});
|
||||
|
||||
it('white on --green-dark meets 4.5:1 (primary button background)', () => {
|
||||
// --green (#3D8C4A) only gives 4.16:1 — buttons use --green-dark (#2E6E39) instead
|
||||
expect(contrastRatio(tokens.white, tokens.greenDark)).toBeGreaterThanOrEqual(4.5);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user