CSS techniques for accessibility testing

CSS can act as a visual linting tool during development. By using attribute selectors and pseudo-elements, you can highlight accessibility violations directly in the browser — no extensions needed.

Missing alt text on images

Images without alt attributes or with empty alt on non-decorative images :

img:not([alt]) {
    outline: 5px solid red !important;
    filter: grayscale(100%) !important;
}

img[alt=""] {
    outline: 5px dashed orange !important;
}

Images with alt="" are treated as decorative. The orange outline reminds you to verify they truly are decorative.

Interactive elements with no text content are invisible to screen readers :

a:empty,
button:empty {
    outline: 5px solid red !important;
}

a:not([href]) {
    outline: 3px dashed orange !important;
}

Missing form labels

Inputs without associated labels :

input:not([type="hidden"]):not([type="submit"]):not([type="button"]):not([aria-label]):not([aria-labelledby]) {
    outline: 5px solid red !important;
}

select:not([aria-label]):not([aria-labelledby]) {
    outline: 5px solid red !important;
}

textarea:not([aria-label]):not([aria-labelledby]) {
    outline: 5px solid red !important;
}

Note : This is a rough heuristic. Inputs with programmatically associated <label> elements via for/id won’t be caught by CSS alone since CSS can’t detect the association.

Missing ARIA labels on landmarks

Landmarks that need differentiation when there are multiples on a page :

nav:not([aria-label]):not([aria-labelledby]) {
    outline: 3px dashed orange !important;
}

aside:not([aria-label]):not([aria-labelledby]) {
    outline: 3px dashed orange !important;
}

section:not([aria-label]):not([aria-labelledby]) {
    outline: 3px dashed orange !important;
}

Focus visibility

Detect elements that suppress focus outlines :

*:focus {
    outline: none !important;
}

If you see the above in your codebase, it’s a red flag. Replace it with :

*:focus-visible {
    outline: 2px solid #818cf8;
    outline-offset: 2px;
}

*:focus:not(:focus-visible) {
    outline: none;
}

This removes outline only for mouse clicks while keeping it for keyboard navigation.

Touch target visualization

Highlight elements that may be too small for touch interaction :

a,
button,
input,
select,
textarea,
[role="button"],
[role="link"],
[role="tab"] {
    position: relative;
}

a::after,
button::after,
[role="button"]::after {
    content: '';
    position: absolute;
    inset: 0;
    outline: 1px dotted rgba(255, 0, 0, 0.5);
    pointer-events: none;
}

Heading hierarchy check

Headings should follow a logical order (h1 → h2 → h3). Use CSS to display heading levels :

h1::before,
h2::before,
h3::before,
h4::before,
h5::before,
h6::before {
    display: inline-block;
    padding: 0.1em 0.4em;
    margin-right: 0.5em;
    font-size: 0.7em;
    font-weight: bold;
    border-radius: 0.25em;
    vertical-align: middle;
    color: white;
}

h1::before { content: 'H1'; background: #22c55e; }
h2::before { content: 'H2'; background: #3b82f6; }
h3::before { content: 'H3'; background: #8b5cf6; }
h4::before { content: 'H4'; background: #ec4899; }
h5::before { content: 'H5'; background: #f59e0b; }
h6::before { content: 'H6'; background: #ef4444; }

Showing landmark regions

Visualize the landmark structure of a page :

header { outline: 2px solid #22c55e !important; }
nav { outline: 2px solid #3b82f6 !important; }
main { outline: 2px solid #8b5cf6 !important; }
aside { outline: 2px solid #ec4899 !important; }
footer { outline: 2px solid #f59e0b !important; }
section { outline: 2px dashed #94a3b8 !important; }
article { outline: 2px dashed #818cf8 !important; }

header::before { content: '<header>'; }
nav::before { content: '<nav>'; }
main::before { content: '<main>'; }
aside::before { content: '<aside>'; }
footer::before { content: '<footer>'; }

header::before,
nav::before,
main::before,
aside::before,
footer::before {
    display: block;
    font-size: 0.7rem;
    font-family: monospace;
    background: rgba(0, 0, 0, 0.8);
    color: white;
    padding: 0.15em 0.5em;
    width: fit-content;
    border-radius: 0 0 0.25em 0;
}

Color contrast debugging

Display contrast information using DevTools-free techniques :

body {
    filter: grayscale(100%);
}

If your page becomes unreadable in grayscale, you’re relying too heavily on color to convey information.

Simulating color blindness

body { filter: url('#protanopia'); }
<svg style="display: none">
    <filter id="protanopia">
        <feColorMatrix type="matrix" values="
            0.567, 0.433, 0,     0, 0
            0.558, 0.442, 0,     0, 0
            0,     0.242, 0.758, 0, 0
            0,     0,     0,     1, 0
        "/>
    </filter>
</svg>

Creating a debug stylesheet

Bundle all checks into a single file you can toggle during development :

/* a11y-debug.css */

img:not([alt]) {
    outline: 5px solid red !important;
}

a:empty, button:empty {
    outline: 5px solid red !important;
}

[tabindex]:not([tabindex="0"]):not([tabindex="-1"]) {
    outline: 3px solid orange !important;
}

[aria-hidden="true"]:focus-within {
    outline: 5px solid red !important;
}

[role="button"]:not(button) {
    outline: 3px dashed yellow !important;
}

Toggle it with a bookmarklet :

javascript:void(function(){
    var s = document.getElementById('a11y-debug');
    if (s) { s.remove(); return; }
    s = document.createElement('link');
    s.id = 'a11y-debug';
    s.rel = 'stylesheet';
    s.href = '/a11y-debug.css';
    document.head.appendChild(s);
}())

Browser DevTools

Chrome

  • Elements → Accessibility pane → view computed ARIA properties
  • Rendering → Emulate prefers-reduced-motion / prefers-color-scheme
  • Lighthouse → Accessibility audit

Firefox

  • Accessibility tab → full accessibility tree
  • Check for Issues → Contrast, Keyboard, Text Labels
  • Simulate → color blindness types

Resources

If you found this helpful, share it with someone who's building for the web.

- Vinay Ranjan