Decorative images are the visual extras that don't carry information — dividers, ornamental patterns, icons paired with text labels, background flourishes. Marked correctly, screen readers skip them and SEO ignores them. Marked incorrectly — left without an alt attribute, or given keyword-stuffed alt — they create accessibility friction and dilute your SEO signal. The fix is simple but easy to get wrong; this guide covers the markup, the role test, and when CSS backgrounds are the better choice. For related fixes, see the Image Optimisation Fixes index.
The test: would the user lose any information if this image was removed entirely? If no — decorative. If yes — informative or functional.
<!-- Correct: explicit empty alt --> <img src="divider.svg" alt=""> <!-- Wrong: missing alt attribute --> <img src="divider.svg">
The difference matters. With alt="", screen readers skip the image silently. Without any alt attribute, screen readers fall back to reading the filename — turning divider.svg into "image divider dot s v g", noise users hate.
<img src="divider.svg" alt="" aria-hidden="true">
Defensive double-marking. Empty alt is sufficient for screen readers; aria-hidden is belt-and-braces and also hides the image from assistive technology that might be confused by empty alt in older code.
For images that exist solely for visual styling, CSS background-image is cleaner — the image lives in the styling layer, not the document layer, so there's no accessibility consideration at all.
<!-- Before: img tag with empty alt -->
<section class="hero">
<img src="hero-bg.jpg" alt="" class="hero-bg">
<h1>Welcome</h1>
</section>
<!-- After: CSS background -->
<section class="hero">
<h1>Welcome</h1>
</section>
<style>
.hero {
background: url('/hero-bg.jpg') center/cover no-repeat;
}
</style>
If the image needs lazy-loading or might benefit from image-search indexing, use img with empty alt. If it's pure decoration, CSS background is cleaner.
<img src="..." alt="" role="presentation"> <!-- or --> <img src="..." alt="" role="none">
Removes any semantic meaning the element might have. Belt-and-braces with empty alt. Both role="presentation" and role="none" work; "none" is the modern preferred value.
Not required if you have alt="". Just defensive.
<!-- Search button with icon + label --> <button> <img src="search.svg" alt="" aria-hidden="true"> Search </button> <!-- Screen reader announces "Search button" once, not "search icon Search button" -->
<!-- Icon-only button --> <button aria-label="Search"> <img src="search.svg" alt="" aria-hidden="true"> </button> <!-- aria-label on the button describes the function -->
The icon image is still marked decorative (empty alt + aria-hidden), but the parent button has an aria-label describing what clicking does. Screen reader announces "Search button" because of the aria-label.
<button>
<svg role="img" aria-labelledby="search-label">
<title id="search-label">Search</title>
<path d="...">
</svg>
</button>
<!-- Bad --> <img src="ornament.png" alt="ornament.png"> <img src="bg-pattern.jpg" alt="background pattern"> <!-- Right --> <img src="ornament.png" alt=""> <img src="bg-pattern.jpg" alt="">
<!-- Bad --> <img src="spacer.gif" alt="spacer"> <!-- Right --> <img src="spacer.gif" alt=""> <!-- Or better: use CSS margin/padding instead of spacer images -->
<!-- Bad — keyword stuffing on a decorative divider --> <img src="divider.svg" alt="best SEO tools website divider"> <!-- Right --> <img src="divider.svg" alt="">
// Allow editors to mark images as decorative
function add_decorative_field($form_fields, $post) {
$form_fields['decorative'] = [
'label' => 'Decorative (skip in screen readers)',
'input' => 'html',
'html' => '<input type="checkbox" name="attachments[' . $post->ID . '][decorative]" ' .
checked(get_post_meta($post->ID, '_decorative', true), '1', false) . '>'
];
return $form_fields;
}
add_filter('attachment_fields_to_edit', 'add_decorative_field', 10, 2);
// Output empty alt when marked decorative
function filter_decorative_alt($attr, $attachment) {
if (get_post_meta($attachment->ID, '_decorative', true) === '1') {
$attr['alt'] = '';
$attr['aria-hidden'] = 'true';
}
return $attr;
}
add_filter('wp_get_attachment_image_attributes', 'filter_decorative_alt', 10, 2);
function DecorativeImage({ src, ...rest }) {
return <img src={src} alt="" aria-hidden="true" {...rest} />;
}
// Usage
<DecorativeImage src="/divider.svg" className="my-section-divider" />
Forces explicit decoration choice at the component level. Linters can flag any bare <img> without alt.
Verify decorative image findings are cleared.
Run Image Audit →