Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/src/styles/tokens.css
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
--color-header-bg: #ffffff;
--color-header-border: #d8d8d8;
--color-footer-bg: #f9f9fa;
--color-footer-text: #898c8e;
--color-footer-text: #6b6375;
--color-footer-border: #e5e4e7;

/* Focus Ring (§3.1.4 – cursor input form) */
Expand Down
27 changes: 27 additions & 0 deletions frontend/src/styles/tokens.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,34 @@
"#721c24",
];

// WCAG 2.x relative luminance / contrast ratio (SC 1.4.3).
function relativeLuminance(hex: string): number {
const rgb = [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16) / 255);
const [r, g, b] = rgb.map((c) => (c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4));
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}

function contrastRatio(hexA: string, hexB: string): number {
const lA = relativeLuminance(hexA);
const lB = relativeLuminance(hexB);
const [lighter, darker] = lA > lB ? [lA, lB] : [lB, lA];
return (lighter + 0.05) / (darker + 0.05);
}

function readToken(block: string, token: string): string {
const match = block.match(new RegExp(`${token}:\\s*(#[0-9a-fA-F]{6})`));
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
if (!match) throw new Error(`${token} not found as a hex value`);
return match[1];
}
Comment thread
seonghobae marked this conversation as resolved.

describe("design tokens", () => {
it("keeps footer text at or above the WCAG AA 4.5:1 contrast minimum (SC 1.4.3)", () => {
const footerBg = readToken(lightBlock, "--color-footer-bg");
const footerText = readToken(lightBlock, "--color-footer-text");
expect(contrastRatio(footerBg, footerText)).toBeGreaterThanOrEqual(4.5);
});
Comment thread
seonghobae marked this conversation as resolved.


it("defines every badge/accent token in both the light and dark blocks", () => {
for (const token of BADGE_AND_ACCENT_TOKENS) {
expect(lightBlock, `${token} missing from the light :root block`).toContain(`${token}:`);
Expand Down
Loading