Improve Web Accessibility on WordPress Sites: Images and Tables

Web accessibility is often treated as an afterthought, but a few targeted improvements can make a significant difference for screen-reader users and assistive technology in general. This article covers two concrete areas: image role attributes and accessible table markup.

Problem: A WordPress site fails basic accessibility checks — images lack descriptive alt text, decorative images are announced by screen readers, and data tables have no headers or captions.

Solution: Add descriptive alt attributes to meaningful images via the media library or ACF alt fields. Set alt="" and role="presentation" on purely decorative images so screen readers skip them. For data tables, add a <caption> and use scope attributes on all <th> elements.

Images — adding role="presentation" for decorative images. Decorative images (icons, dividers, backgrounds rendered as <img> tags) should be marked so that screen readers skip them. If you use wp_get_attachment_image(), hook into wp_get_attachment_image_attributes:

<?php
add_filter( 'wp_get_attachment_image_attributes', 'add_presentation_role_to_images' );

function add_presentation_role_to_images( $attr ) {
    $attr['role'] = 'presentation';
    return $attr;
}

Apply this filter conditionally (e.g. only for a specific image size or post type) if you need role="img" with a meaningful alt on content images.

Tables — adding scope attributes for better screen-reader navigation. Screen readers use scope="col" and scope="row" to associate data cells with their headers. The filter below adds these attributes automatically to post and page content:

<?php
add_filter( 'the_content', 'improve_table_accessibility' );

function improve_table_accessibility( $content ) {
    $post_type = get_post_type();
    if ( $post_type !== 'post' && basename( get_page_template() ) !== 'page.php' ) {
        return $content;
    }

    // Add scope="col" to column header cells
    $content = str_replace( '</th><th>', '</th><th scope="col">', $content );

    // Convert the first <td> in each row to a row-header <th scope="row">
    $content = preg_replace( '/<tr><td>(.*?)<\/td>/', '<tr><th scope="row">$1</th>', $content );

    return $content;
}

Bonus — responsive table wrapper. Wrap all tables in a scrollable container and apply Bootstrap classes automatically, while also cleaning up stray spaces in class attributes that the W3C validator flags:

<?php
// Extend the improve_table_accessibility() function above with:
$content = str_replace( '<table',      '<div class="table-wrap"><table', $content );
$content = str_replace( '</table>',    '</table></div>',                $content );
$content = str_replace( '<table class="', '<table class="table ',         $content );

// Remove leading/trailing spaces from class attribute values (W3C validator warning)
$content = preg_replace( '/class=["']\s*(?P<class>[^"'<>]+?)\s*["']/i', 'class="$1"', $content );

NOTE: The preg_replace that promotes the first <td> to a row header assumes a simple table structure where the leftmost cell is always a header. If your tables have a different layout — for example, no row headers — skip that line to avoid incorrectly wrapping data cells in <th> elements.