Test high contrast mode
UI components and graphical objects must have at least 3:1 contrast ratio. High Contrast Mode uses system colors that override custom styles.
CSS media queries and best practices
/* Detect Windows High Contrast Mode */
@media (forced-colors: active) {
/* System colors are forced */
button {
/* Add border so button is visible */
border: 2px solid ButtonText;
}
a {
/* Underline for visibility */
text-decoration: underline;
}
/* Use system color keywords */
.focus-ring {
outline: 2px solid Highlight;
}
}
/* System color keywords:
Canvas, CanvasText, LinkText,
ButtonFace, ButtonText, Highlight,
HighlightText, GrayText *//* DO: Use outline for focus */
button:focus {
outline: 2px solid currentColor;
outline-offset: 2px;
}
/* DON'T: Use box-shadow for focus */
button:focus {
box-shadow: 0 0 0 3px blue;
/* Invisible in HC mode! */
}
/* DO: Add borders to buttons */
button {
border: 2px solid transparent;
}
@media (forced-colors: active) {
button {
border-color: ButtonText;
}
}
/* DO: Use transparent borders */
.card {
border: 1px solid transparent;
/* Becomes visible in HC mode */
}Testing hints
// Emulate forced-colors
await page.emulateMedia({
forcedColors: 'active'
});
// Check for border visibility using $eval
const button = page.getByTestId('button-primary');
const borderWidth = await button.$eval(
'self', el => getComputedStyle(el).borderWidth
);
// Should have visible border in HC mode
expect(parseInt(borderWidth)).toBeGreaterThan(0);
// Check focus uses outline, not box-shadow
await button.focus();
const outline = await button.$eval(
'self', el => getComputedStyle(el).outline
);
const boxShadow = await button.$eval(
'self', el => getComputedStyle(el).boxShadow
);
expect(outline).not.toBe('none');// CSS property audit using $$eval
const elements = await page.$$eval(
'button, a, input',
els => els.map(el => {
const style = getComputedStyle(el);
return {
tag: el.tagName,
// These should not be the only indicators
hasBgColor: style.backgroundColor !== 'transparent',
hasBorder: parseInt(style.borderWidth) > 0,
hasUnderline: style.textDecoration.includes('underline'),
// Focus should use outline
focusUsesOutline: style.outline !== 'none'
};
})
);
// Buttons should have borders
const buttonsNeedBorders = elements
.filter(el => el.tag === 'BUTTON' && !el.hasBorder);
// Links should have underlines
const linksNeedUnderlines = elements
.filter(el => el.tag === 'A' && !el.hasUnderline);Automation hints
page.emulateMedia({ forcedColors: 'active' })