Prompt and context
A design system needs one keyboard-focus treatment for buttons, inputs, menus, and custom controls. The current implementation changes only the border color, disappears on a low-contrast theme, and changes component size when focus arrives. Design a reusable approach and a test gate.
WCAG 2.2 adds Focus Appearance as a success criterion about indicator area and contrast with adjacent colors. The interview tests whether a candidate can turn that requirement into component states, CSS tokens, keyboard paths, and measurable checks instead of merely adding an outline.
What the interviewer is assessing
Look for a distinction between the indicator and the component, area and contrast calculations, a treatment that is not color-only, non-rectangular and rounded controls, no layout shift, native semantics, and coverage of :focus-visible, pointer input, and forced-colors mode.
Clarifying questions
- Is the target WCAG 2.2 AA or AAA, and which paths are keyboard operable?
- Is the ring outside, inside, or offset from the component?
- Which theme tokens and forced-colors rules exist?
- Do custom controls use native elements or correct roles and states?
- How do focus, hover, selected, and invalid states compose?
- How will rounded controls, scrolling, and image backgrounds be tested?
30-second answer
“I would inventory operable elements, use native controls where possible, and provide a non-color-only :focus-visible indicator. Use an outline or pseudo-element that does not change layout, with tokens for ring geometry and contrast against both the component and adjacent background. Preserve system colors in forced-colors mode. Validate complete keyboard paths, rendered pixels, zoom, and manual assistive checks.”
Step-by-step solution
Step 1: Define focus targets and states
Prefer native buttons, inputs, selects, and links. A custom div needs keyboard handling, role, name, value, and state synchronization. focus-visible helps distinguish pointer and keyboard focus but cannot remove keyboard feedback.
Define priority for default, hover, focus-visible, selected, invalid, and disabled states so the ring remains identifiable when error or selection styles apply.
Step 2: Draw without changing layout
Do not add border width on focus; it changes the box and pushes neighbors. Prefer outline, outline-offset, or a pseudo-element. For complex shapes, combine inner and outer shadows but verify they are not clipped.
.control:focus-visible {
outline: 3px solid var(--focus-ring);
outline-offset: 2px;
}Evaluate the rendered perimeter of rounded buttons, icon buttons, and sliders rather than a rectangular line that is clipped by a parent.
Step 3: Turn WCAG 2.4.13 into tokens and thresholds
Centralize ring width, offset, focus color, background, and error-state tokens. Test contrast against the unfocused component and adjacent background, not only text. Sample the worst local color when gradients, images, or translucent layers are present.
focus-ring-width >= 2 CSS px
focus-ring-contrast-against-adjacent >= required threshold
focus-ring-area >= minimum perimeter-area ruleUse rendered geometry and pixels for the area and contrast gate; design-file colors are not evidence of runtime compliance.
Step 4: Support themes, forced colors, and system overrides
Give light, dark, and brand themes separate focus tokens. Under forced-colors: active, do not hide the platform indicator; system colors such as ButtonText can preserve visibility.
@media (forced-colors: active) {
.control:focus-visible {
outline: 2px solid ButtonText;
outline-offset: 2px;
}
}Never use outline: none unless the same state supplies an equally clear visible indicator.
Step 5: Cover dynamic controls and scrolling
Menus, dialogs, comboboxes, and virtual lists must be tested for focus movement, return focus on close, and rings that remain visible while scrolling. Transforms, clip paths, and overflow must not erase the indicator.
With aria-activedescendant, the visual indicator follows the active item even though DOM focus stays on a container, and the keyboard and screen-reader views must agree.
Step 6: Build automated and manual acceptance
Automate checks for focusable elements, computed outline or shadow, theme tokens, forced-colors rules, and screenshots across states and backgrounds. Manually use Tab, Shift+Tab, Enter, Space, arrows, and Escape; test 200% zoom and high-contrast settings with different input devices.
Record component, state, background, ring geometry, contrast result, keyboard steps, and screenshots. A blue line visible in a design file is not proof of compliance.
Model answer
“I inventory operable controls and prefer native semantics. :focus-visible uses a layout-neutral outline and offset, while tokens keep ring width and contrast consistent. Focus must not add border width or depend only on color. I test light, dark, and forced-colors modes and preserve system visibility.”
“For menus, dialogs, virtual lists, and non-rectangular controls I verify focus movement, return focus, clipping, and active-item indication. Automation captures computed styles and pixels; manual checks cover keyboard paths, 200% zoom, and high contrast.”
Common mistakes
- Change only text or border color → the ring blends into the background → test ring and adjacent colors separately.
- Add border width → layout shifts → use an outline or layout-neutral element.
- Force
outline: none→ keyboard users lose feedback → supply an equivalent visible indicator. - Test only light theme → dark or forced colors disappear → cover every theme mode.
- Use only rectangular screenshots → rounded and clipped geometry is missed → test rendered shapes and scrolling.
- Put focus on a div → semantics and assistive output diverge → prefer native controls and valid ARIA.
Follow-ups and responses
Follow-up 1: Is outline better than box-shadow?
There is no universal winner. Outline is layout-neutral and has platform semantics; shadows can create layered rings. Choose from clipping, rounded geometry, forced-colors behavior, and rendered-pixel results.
Follow-up 2: Why can’t :focus-visible replace :focus?
It is a browser heuristic for when to show focus. It must not remove focus semantics or leave keyboard and assistive-technology paths without feedback.
Follow-up 3: Does an error red border replace the focus ring?
No. Error communicates a problem; the ring communicates the current target. Both must remain visible without relying only on color.
Follow-up 4: How do you test an image or gradient background?
Sample the worst local color adjacent to the rendered ring, adding an opaque underlay or a second ring when needed. An average design color is not evidence.
Follow-up 5: How do you prevent overflow from clipping the ring?
Inspect ancestor overflow, clip paths, and transforms; use inner rings, padding, or a component-level pseudo-element to fit the geometry instead of disabling focus styling.