Test visible focus indicators
Any keyboard operable user interface must have a mode of operation where the keyboard focus indicator is visible. WCAG 2.2 adds 2.4.11 (Focus Not Obscured) and 2.4.13 (Focus Appearance) for even stricter requirements.
Tab through elements
Current mode info
Anti-patterns vs accessible
/* DON'T: Remove focus entirely */
*:focus {
outline: none;
}
/* DON'T: Hide focus with transparent */
button:focus {
outline-color: transparent;
}
/* DON'T: Use very thin/low-contrast */
a:focus {
outline: 1px dotted #999;
}/* DO: High-contrast focus ring */
button:focus-visible {
outline: 2px solid #3b82f6;
outline-offset: 2px;
}
/* DO: Tailwind focus-visible */
<button class="focus:outline-none
focus-visible:ring-2
focus-visible:ring-blue-500
focus-visible:ring-offset-2">
/* Note: focus-visible only shows
for keyboard, not mouse clicks */Testing hints
// Navigate with keyboard
await page.keyboard.press('Tab');
await page.keyboard.press('Tab');
// Get focused element
const focused = await page.evaluate(
() => document.activeElement?.tagName
);
// Check focus visibility with CSS
const button = page.getByTestId('button-1');
await button.focus();
const styles = await button.evaluate(el => {
const computed = getComputedStyle(el);
return {
outline: computed.outline,
boxShadow: computed.boxShadow
};
});
// Verify focus indicator exists
expect(styles.outline).not.toBe('none');import AxeBuilder from '@axe-core/playwright';
// Note: axe-core can't fully test
// focus visibility (visual check needed)
// But can detect outline: none in CSS
const results = await new AxeBuilder({ page })
.withRules(['focus-visible'])
.analyze();
// Manual check: tab through all elements
const focusable = await page.$$(`
a[href], button, input, select, textarea,
[tabindex]:not([tabindex="-1"])
`);
for (const el of focusable) {
await el.focus();
// Screenshot for visual verification
await page.screenshot({
path: `focus-screenshot.png`
});
}Automation hints
page.keyboard.press('Tab') and verify focus visibilityawait expect(element).toBeFocused()getComputedStyle(el).outline