Focus Appearance (2.4.13) for Grid Cells

Permalink to "Focus Appearance (2.4.13) for Grid Cells"

SC 2.4.13 Focus Appearance (AAA in WCAG 2.2) sets a measurable bar for focus indicators: the indicator must be at least as large as a 2 CSS pixel thick perimeter around the component and have at least 3:1 contrast between the same pixels in focused and unfocused states. It is AAA, but it is the clearest definition of a good focus indicator, and grid cells — small, dense, often coloured by selection — are where indicators most often fall short.

This guide belongs to focus indicator contrast auditing.

Spec reference

Permalink to "Spec reference"
  • SC 2.4.13 Focus Appearance (AAA) — area at least that of a 2 px perimeter; 3:1 contrast change between focused and unfocused states.
  • SC 2.4.7 Focus Visible (AA) — some visible indicator.
  • SC 1.4.11 Non-text Contrast (AA) — indicator contrast against adjacent colours.
  • Not obscured — 2.4.13 also expects the indicator not to be hidden by author content.
Indicator area for a 120 × 32 px cell Bar chart of indicator area in square pixels: required area for a 2 px perimeter 608; 1 px outline 304; 2 px inset outline 592; 2 px outline outside the cell 624. Indicator area for a 120 × 32 px cellRequired (2 px perimeter)px²1 px outlinepx²2 px inset outlinepx² (slightly short)2 px outline outsidepx²
A 2 px outline outside the cell meets the area requirement.

When to use — and when not to

Permalink to "When to use — and when not to"

Aim for 2.4.13 in data grids even if you only target AA; it gives designers and testers a precise rule. Apply it to every focusable cell, header and control in grids.

Do not rely on a background colour change alone for focus; it often fails the 3:1 change and collides with selection. The misapplication: a 1 px light grey outline on a white cell, technically “visible” and practically invisible.

Weak focus versus 2.4.13-compliant focus Comparison: thin, low-contrast or colour-only focus states fail 2.4.13; a 2 px outline with high contrast change, distinct from selection, passes. Weak focus versus 2.4.13-compliant focus✗ 1 px light grey outlineBackground tint onlySame as selected styleClipped by neighbour cells✓ 2 px dark outlineOutline plus optional tintDistinct from selectionoutline-offset inside cell box or z-index

Annotated code example

Permalink to "Annotated code example"
.grid [role="gridcell"]:focus,
.grid [role="columnheader"]:focus {
  outline: 2px solid var(--focus-color, #111714);       /* 2 px perimeter */
  outline-offset: -2px;                                  /* inside, avoids clipping by neighbours */
  box-shadow: inset 0 0 0 4px var(--focus-halo, #ffffff); /* second tone for coloured cells */
  position: relative; z-index: 1;
}
.grid [aria-selected="true"] { background: var(--selected-bg, #d6f9e8); }   /* selection: background */
@media (forced-colors: active) {
  .grid [role="gridcell"]:focus { outline-color: Highlight; box-shadow: none; }
}
// Rough audit: compare pixels of a cell focused versus unfocused (Playwright screenshots)
const before = await cell.screenshot(); await cell.focus(); const after = await cell.screenshot();
const changed = diffPixels(before, after, { minContrast: 3 });   // count pixels whose contrast change ≥ 3:1
expect(changed).toBeGreaterThanOrEqual(perimeterArea(box, 2));

Keyboard & AT behaviour

Permalink to "Keyboard & AT behaviour"
State Visual Requirement
Focused cell 2 px dark outline + light inner halo Area and 3:1 change
Selected cell Tinted background Distinct from focus
Focused and selected Outline on tinted background Outline still 3:1 against tint
Hover Subtle background Must not look like focus
Forced colours Highlight outline System colour
Indicator techniques for grid cells Matrix of techniques — outline outside, outline inset, box-shadow inset, background change, underline — against area, contrast change, clipping risk and selection clash. Indicator techniques for grid cellsAreaContrast changeClippingSelection clashOutline outsideFullHighClipped by neighboursLowOutline insetSlightly lessHighNoneLowInset box-shadowDependsHighNoneLowBackground changeLargeOften lowNoneHighUnderlineSmallVariesNoneLow
Inset outlines plus a halo are robust in dense grids.

Step-by-step implementation

Permalink to "Step-by-step implementation"
  1. Compute the requirement. For a cell of width w and height h, a 2 px perimeter has area about 4(w + h) pixels; that is the minimum indicator area.
  2. Choose a technique. An inset 2 px outline plus an inner halo works in dense grids without clipping.
  3. Check contrast change. Compare the indicator pixels focused versus unfocused; they must differ by 3:1. Against coloured selection backgrounds, the second tone ensures this.
  4. Separate states. Selection uses background; focus uses outline; hover uses a subtle background that cannot be confused with either.
  5. Verify themes. Check dense mode, dark mode and forced colours; take screenshots of each state in CI.

Integration context

Permalink to "Integration context"

Contrast auditing is covered in auditing focus indicator contrast and dense grid design in designing focus indicators for dense grids. Screenshot automation is in automating focus indicator screenshots with Playwright. Obscured focus is in focus not obscured by sticky headers, and tokens in design system tokens and component contracts.

Gotchas

Permalink to "Gotchas"

Outline clipping. Neighbouring cells with backgrounds paint over outlines outside the cell; inset outlines or raised z-index avoid it.

:focus-visible. Grids that use roving tabindex often want focus visible on mouse clicks too, since users switch between mouse and keyboard; decide deliberately.

Rounded corners. The perimeter is measured on the component’s shape; rounded outlines are fine if thick enough.

Edge cases

Permalink to "Edge cases"
  • Very small cells. Icon cells of 24 px still need a 2 px perimeter indicator; inset outlines may cover content, so use an outside outline with z-index.
  • Merged cells. Larger cells need larger indicators by area; a 2 px outline scales automatically.
  • Images in cells. Thumbnails can reduce contrast; the inner halo helps.

Worked example

Permalink to "Worked example"

A spreadsheet-style grid used a 1 px blue outline for focus and a light blue background for selection. On selected cells, the focus outline was nearly invisible, and low-vision testers lost their place. The team switched to a 2 px dark inset outline with a white inner halo, kept selection as a background tint and verified a 3:1 change with screenshot diffs. Testers could track focus across selected ranges.

Common mistakes

Permalink to "Common mistakes"
  • 1 px outlines.
  • Background-only focus.
  • Focus that looks like selection.
  • Outlines clipped by neighbours.

Testing checklist

Permalink to "Testing checklist"

Testing it with a screen reader

Permalink to "Testing it with a screen reader"

Focus appearance is visual, but sighted screen reader users depend on it to follow the virtual cursor. With NVDA running in focus mode, arrow through a grid and confirm the outline moves with each announcement; NVDA’s own focus highlighter can be turned on to compare. Mismatches between NVDA’s highlight and your outline reveal focus management bugs as well as appearance issues.

Measuring without special tools

Permalink to "Measuring without special tools"

You can audit 2.4.13 with a browser and a colour picker. Zoom a screenshot of a focused cell, count the outline thickness in pixels and compare its length to the cell perimeter. Sample the outline colour focused and the same pixels unfocused, and compute the contrast ratio with any contrast checker. Repeat on a selected cell and in dark mode. Record the numbers in the design system documentation, so designers can see why the indicator looks the way it does.

Key points

Permalink to "Key points"
  • Make the indicator at least a 2 px perimeter, inset in dense grids.
  • Guarantee a 3:1 change with a second tone on coloured cells.
  • Keep focus, selection and hover visually distinct.
  • Verify in dense, dark and forced colours modes.

Design system notes

Permalink to "Design system notes"

Define focus tokens (width, colours, halo) for grid cells, document the 2.4.13 calculation and test every grid density.

FAQ

Permalink to "FAQ"
Is 2.4.13 required?

It is AAA; many teams adopt it as a design rule.

Does an outline outside the cell count?

Yes, if not clipped.

Can I use box-shadow?

Yes, if area and contrast requirements are met; check forced colours.

How do I measure contrast change?

Compare the same pixels focused and unfocused.

What about selected cells?

Use a second tone so the indicator contrasts with selection colours.

Do headers need the same indicator?

Yes, every focusable element in the grid.

Permalink to "Related"

← Back to Focus Indicator Contrast Auditing