Most WordPress compatibility notices arrive with a deprecated function, a changed hook, or a database migration. This one looks much smaller: two HTML elements effectively trade jobs inside the WordPress admin.

That small semantic correction matters.

In WordPress 7.1, the checkbox cell in post list rows is no longer the row header. The post title becomes the row header instead. This gives screen-reader users a meaningful way to identify each row, but it can also expose brittle CSS and JavaScript in plugins that assumed the checkbox would always live inside a <th> or the title would always live inside a <td>.

The front end of a website is not changing. The standard hooks for adding custom admin columns are not being removed. For most sites, the update should be uneventful. The risk is concentrated in plugins, theme companion plugins, agency customizations, and test suites that couple their behavior to WordPress’s old admin markup.

WordPress 7.1 is scheduled for final release on August 19, 2026, and release-candidate builds are already available at the time of writing. The date remains part of the project’s tentative release schedule, so developers should follow the official WordPress 7.1 release page for any late changes.

The short version

The WordPress 7.1 accessibility change affects the rows shown under Posts, Pages, and custom post type list screens in wp-admin.

Part of the rowWordPress 7.0 and earlierWordPress 7.1Practical risk
Selection checkbox cell<th scope="row" class="check-column"><td class="check-column">Selectors such as th.check-column stop matching
Post title cell<td class="title column-title column-primary…"><th scope="row" … aria-label="Post title">Selectors such as td.column-title stop matching
Responsive row layoutOlder collapsed-cell layoutUpdated flex-based layoutWidth, float, display, and direct-child overrides may behave differently on narrow screens
Column registration hooksExisting hooksUnchangedNormal custom-column PHP usually keeps working
Public theme markupUnchangedUnchangedNo direct front-end effect

The official WordPress 7.1 developer note specifically warns extenders to inspect selectors such as th.check-column, td.column-title, td.column-primary, and descendants such as td .row-actions.

If your plugin targets semantic classes such as .check-column, .column-title, .column-primary, .row-title, or .row-actions without requiring a particular tag name, you may not need to change anything. You still need to test the responsive view.

What exactly changes in the WordPress admin markup?

Here is a simplified version of the old post row structure:

<tr>
    <th scope="row" class="check-column">
        <input type="checkbox" name="post[]" value="123">
    </th>
    <td class="title column-title column-primary page-title">
        <a class="row-title" href="...">Hello world!</a>
    </td>
    <td class="author column-author">admin</td>
</tr>

In WordPress 7.1, the same row is structured like this:

<tr>
    <td class="check-column">
        <input type="checkbox" name="post[]" value="123">
    </td>
    <th
        scope="row"
        class="title column-title column-primary page-title"
        aria-label="Hello world!"
    >
        <a class="row-title" href="...">Hello world!</a>
    </th>
    <td class="author column-author">admin</td>
</tr>

The positions and familiar WordPress classes remain largely intact. The significant changes are the element types, scope="row", and the accessible label on the title cell.

That distinction explains why many plugins will continue to work. A rule targeting .column-title still finds the title. A rule targeting td.column-title does not.

Why WordPress is making the change

This is not a cosmetic HTML cleanup. It corrects an accessibility problem that has been open in WordPress Trac for approximately eleven years.

In an accessible data table, a row header should identify the object represented by that row. On the Posts screen, that object is the post. The post title is therefore the logical row header.

The old markup gave scope="row" to the checkbox cell. As a result, assistive technology could derive the row’s name from selection-related content instead of the actual post title. In some situations, users heard an unhelpful label associated with “Select All.” In others, the value could be empty because the checkbox was unavailable.

Locked posts made the weakness especially obvious. If another user was editing a post, the usual selection control might not be available. The cell still held the semantic responsibility of naming the row, but it no longer contained a useful name.

WordPress Trac ticket #32892 records the long-running discussion. The final Core changeset 62838 explains the resolution: move the row header to the title, turn the selection cell into a regular data cell, and give the new row header a concise aria-label.

The visible title already exists, so why add aria-label as well? Because a WordPress title cell can contain much more than a title. It may also contain post states, lock information, row actions, an excerpt, and plugin-added controls. A clean label allows supporting screen readers to identify the row by its primary object without treating all of that secondary interface text as the row’s name.

Is this really a breaking change?

It is a markup-level breaking change, not a broad WordPress API break.

WordPress is not removing manage_posts_columns, manage_pages_columns, manage_{$post_type}_posts_columns, or the related custom-column rendering actions. A plugin that registers and renders a normal custom column through those hooks should not fail merely because the title cell became a <th>.

The break occurs when code encodes an undocumented structural assumption:

/* Assumes the checkbox cell must be a th. */
.wp-list-table tbody th.check-column { /* ... */ }

/* Assumes the title cell must be a td. */
.wp-list-table tbody td.column-title { /* ... */ }

The same is true for JavaScript:

const titles = document.querySelectorAll(
    '#the-list td.column-title .row-title'
);

After the update, these selectors return no matching elements. The plugin may not throw a fatal error, but its interface can silently lose styling, event handlers, buttons, tooltips, status indicators, or interactive behavior.

That is why “may break some plugins” is accurate, while “WordPress 7.1 will break plugins” is too broad. The outcome depends on how those plugins address admin table elements.

Which plugins and customizations are most exposed?

The highest-risk code changes post list screens after WordPress has rendered them. Common examples include:

  • SEO, editorial, workflow, translation, membership, and eCommerce plugins that add scores, states, badges, or actions to post rows.
  • Plugins that add a custom post type and then heavily restyle its list screen.
  • Admin UI tools that implement sticky columns, column resizing, drag-and-drop ordering, bulk controls, hover panels, or inline forms.
  • JavaScript that inserts controls beside .row-title or inside .row-actions.
  • Theme companion plugins and agency must-use plugins that enqueue global wp-admin styles.
  • End-to-end tests, browser automation, and visual snapshots that expect a title cell to be a td.
  • Custom admin tables built by extending WP_List_Table and relying on its generated wrappers.

The mere presence of a custom column does not put a plugin at high risk. The implementation matters more than the feature category.

Implementation patternRisk levelReason
Adds a column with supported PHP hooks and no admin CSS/JSLowColumn hooks and data flow are unchanged
Targets .column-my_plugin_score by classLowThe plugin’s custom column remains addressable by class
Targets td.column-title or th.check-columnHighThe required element type changes in 7.1
Uses .check-column + .column-titleLowerRelationship and classes remain meaningful
Uses th.check-column + td.column-titleHighBoth tag assumptions become false
Searches for the second td in every rowHighThe second child becomes a th
Overrides responsive display, float, or fixed widthsMedium to highCore’s narrow-screen rows now use flex layout
Tests behavior using class selectorsLowMore resilient across semantic markup changes
Tests exact row HTML or tag-specific selectorsHighSnapshots and locators can fail even if the feature still works

What is unlikely to break

Several parts of WordPress are outside the direct scope of this change:

Public-facing theme templates are unaffected. The markup change lives in the administration interface. A conventional theme that only controls front-end output has no dependency on it.

The block editor content canvas is not the affected table. This notice concerns admin list screens such as Posts → All Posts, Pages → All Pages, and equivalent custom post type screens.

Custom post column hooks continue to work. If PHP adds a column and prints its content, the generated cell can still appear normally.

Class-based selectors usually survive. WordPress is retaining the important column classes. Core’s own CSS changes were deliberately additive so that old and new list-table implementations can coexist.

The database is not involved. No post data, metadata, options, or database schema is changed by the row-header correction.

This means a plugin compatibility problem is likely to be local and observable in wp-admin, not a reason to expect content loss or a broken public website.

The CSS selectors developers should change

The safest rule is simple: target a WordPress admin component by its stable class or purpose, not by the HTML tag Core happened to use in a previous release.

Checkbox column

Fragile:

.edit-php .wp-list-table tbody th.check-column {
    background: #f6f7f7;
}

Resilient:

.edit-php .wp-list-table tbody .check-column {
    background: #f6f7f7;
}

If the rule genuinely needs to be limited to a table cell, support both versions explicitly:

.edit-php .wp-list-table tbody td.check-column,
.edit-php .wp-list-table tbody th.check-column {
    background: #f6f7f7;
}

Title and primary column

Fragile:

.wp-list-table tbody td.column-title .my-plugin-badge {
    margin-inline-start: 8px;
}

Resilient:

.wp-list-table tbody .column-title .my-plugin-badge {
    margin-inline-start: 8px;
}

Or, when the cell element needs to be explicit:

.wp-list-table tbody :is(td, th).column-title .my-plugin-badge {
    margin-inline-start: 8px;
}

Row actions and post states

Fragile:

.wp-list-table td .row-actions { /* ... */ }
.wp-list-table td .post-state { /* ... */ }
.wp-list-table td .row-title { /* ... */ }

Resilient:

.wp-list-table .row-actions { /* ... */ }
.wp-list-table .post-state { /* ... */ }
.wp-list-table .row-title { /* ... */ }

Scope the rule with a screen class, table class, or plugin-owned class if it should not apply to every list table. Removing td should not mean making the selector globally vague.

For example:

.post-type-book .wp-list-table.posts .column-title .my-plugin-badge {
    display: inline-flex;
}

This identifies the screen, table, column, and plugin element without depending on whether the column is a header or data cell.

JavaScript should target behavior, not table tags

JavaScript is where a missed selector can become harder to diagnose. A CSS miss is usually visible. A failed query may simply mean an event listener never attaches.

Fragile code:

document
    .querySelectorAll('#the-list td.column-title .my-plugin-action')
    .forEach((button) => {
        button.addEventListener('click', runAction);
    });

Compatible code:

document
    .querySelectorAll('#the-list .column-title .my-plugin-action')
    .forEach((button) => {
        button.addEventListener('click', runAction);
    });

Delegated events are even more resilient when rows or controls can be replaced dynamically:

const postList = document.querySelector('#the-list');

postList?.addEventListener('click', (event) => {
    const action = event.target.closest('.my-plugin-action');

    if (!action || !postList.contains(action)) {
        return;
    }

    runAction(event);
});

The listener cares about the plugin’s action, not the title cell’s implementation detail.

The same principle applies to jQuery:

// Fragile.
jQuery('#the-list').on('click', 'td.column-title .my-plugin-action', handler);

// Compatible with old and new markup.
jQuery('#the-list').on('click', '.column-title .my-plugin-action', handler);

Also inspect calls to closest(), parents(), children(), find(), and querySelector() after the initial event match. A listener may attach successfully and still fail later because it calls closest('td') to locate the row’s primary cell.

Prefer:

const primaryCell = event.target.closest('.column-primary');
const row = event.target.closest('tr');

over:

const primaryCell = event.target.closest('td');

Do not overlook automated tests

A plugin can work perfectly for users while its test suite fails immediately after moving to WordPress 7.1. The inverse is also possible: broad tests continue passing while a narrowly used admin control no longer initializes.

Review Playwright, Cypress, Selenium, Puppeteer, Codeception, and custom browser-test selectors. A locator such as this is tied to old markup:

page.locator('#the-list td.column-title .row-title');

Use the semantic class for cross-version compatibility:

page.locator('#the-list .column-title .row-title');

Then add a WordPress 7.1-specific assertion that verifies the accessibility improvement itself:

await expect(
    page.getByRole('rowheader', { name: 'Hello world!' })
).toBeVisible();

Snapshot tests deserve special attention. An exact HTML snapshot will change even when the interface looks identical. Update it only after checking that the new <th scope="row"> and aria-label are present for the right reason. Blindly accepting a snapshot defeats the purpose of the test.

The responsive flex change can reveal a second class of bugs

The element swap is receiving most of the attention, but the official dev note also calls out an update to collapsed table cells in the responsive viewport. Core now uses flex layout for narrow post-list rows.

That can expose admin CSS that previously relied on:

  • display: table-cell, display: block, or display: none at the wrong breakpoint;
  • floats used to position badges or action groups;
  • fixed pixel widths applied without a desktop-only media query;
  • nth-child() rules that assume every cell is a td;
  • absolute positioning based on the old primary-cell dimensions;
  • custom columns that cannot wrap or shrink because of white-space: nowrap or a large min-width.

Do not judge compatibility from a wide desktop window alone. Resize the Posts screen through the WordPress admin breakpoint, expand and collapse row details, and test both long and short titles.

When a custom width is only useful on desktop, say so in the stylesheet:

@media screen and (min-width: 783px) {
    .wp-list-table .column-my_plugin_score {
        width: 8rem;
    }
}

At narrow widths, let Core’s responsive rules do their job unless the plugin has a clear reason to override them. If an override is required, test it against long translated strings, right-to-left administration, browser zoom, and a user who has hidden or reordered optional columns.

A deeper note for custom WP_List_Table implementations

The public-facing developer note is about post list tables, but the committed implementation also updates the shared WP_List_Table row-generation logic. That matters to plugins that build their own administration tables by subclassing Core’s list table class.

In WordPress 7.1, the base renderer outputs the checkbox column as a <td> and the primary column as <th scope="row">. It also introduces a protected method named get_primary_column_aria_label() so a subclass can return a concise human-readable identifier for the row. The current implementation can be reviewed in the WordPress WP_List_Table source.

A custom table can provide its row label like this:

/**
 * Return a concise accessible name for the current row.
 *
 * @param array|object $item Current table item.
 * @return string
 */
protected function get_primary_column_aria_label( $item ) {
    if ( is_array( $item ) ) {
        return isset( $item['name'] ) ? (string) $item['name'] : '';
    }

    return isset( $item->name ) ? (string) $item->name : '';
}

Return plain text. The parent renderer escapes the value for the HTML attribute.

Adding this method does not prevent the subclass from loading on older WordPress versions; older parents simply do not call it. It therefore works as a progressive compatibility improvement when the plugin supports both pre-7.1 and 7.1 installations.

There are two important qualifications.

First, WP_List_Table is marked private. The WordPress developer reference explicitly warns plugin authors that the class is subject to change and recommends testing against beta and release-candidate builds. A custom subclass should always be treated as a maintenance commitment.

Second, some subclasses use _column_* methods that output complete cell markup themselves. If your code prints its own opening and closing <td>, the base renderer cannot automatically correct that markup. Audit those methods manually, particularly the method responsible for the primary column.

If your table changes its primary column through list_table_primary_column, that choice now has semantic consequences as well as responsive-layout consequences. The chosen column should genuinely identify the row, and its accessible label should be concise and unique enough for users to understand.

A fast source-code audit before WordPress 7.1

Start with a targeted search rather than reading every asset by hand. From the plugin root, Ripgrep can expose the most likely assumptions:

rg -n --glob '!node_modules/**' --glob '!vendor/**' \
  'th\.check-column|td\.(title|column-title|page-title|column-primary)' .

rg -n --glob '!node_modules/**' --glob '!vendor/**' \
  'td\s+\.(row-title|post-state|row-actions)|th\s+input' .

rg -n --glob '!node_modules/**' --glob '!vendor/**' \
  'nth-child|querySelector(All)?|closest\(|parents?\(' assets src includes tests

Adjust the source directories to match the project. Also inspect built or minified assets that are actually shipped. Fixing src/admin.js is not enough if the release ZIP still contains an old build/admin.js.

Search PHP too. Many plugins build CSS selectors inside localized configuration, inline scripts, test fixtures, or PHP-rendered JavaScript.

For each match, ask three questions:

  1. Is the tag name essential, or is the class sufficient?
  2. Does the selector need to support both WordPress 7.0 and 7.1?
  3. Does the code still behave at narrow viewport widths?

Most repairs are one-line selector changes. The value comes from finding all of them, including the ones hidden in test helpers and production bundles.

A practical WordPress 7.1 plugin test matrix

Use a local environment or staging copy. WordPress’s own 7.1 testing guide explains how to install a pre-release with WordPress Playground, a local site, the WordPress Beta Tester plugin, or WP-CLI. Do not install a beta or release candidate directly on a production site.

Test more than one clean Posts screen. Permissions, post states, responsive behavior, and plugin combinations can change the rendered row.

Test areaScenariosWhat to verify
Content typesPosts, Pages, every plugin-registered custom post typeAll custom columns, badges, actions, and links appear
Post statesPublished, draft, scheduled, private, password-protected, trash, untitledTitles and status labels remain readable and aligned
Editing stateUnlocked and locked by another userRow identity remains meaningful; plugin controls respect permissions
User rolesAdministrator, editor, author, contributor, custom rolesCheckboxes and actions appear only when permitted; JS does not assume they exist
Core interactionsSelect all, bulk actions, Quick Edit, row action linksControls still initialize and complete successfully
View modesCompact and extendedExcerpts, states, and custom content do not pollute or obscure the primary label
ViewportsWide desktop, around the admin breakpoint, phone widthFlex layout wraps correctly; detail toggles remain usable
AccessibilityKeyboard, zoom, high contrast, available screen readerFocus is visible, row headers announce useful names, controls have labels
InternationalizationLong translated strings and RTL if supportedNo clipping, overlap, or reversed spacing assumptions
DiagnosticsBrowser console, PHP debug log, network panelNo selector-related null errors, notices, or failed requests

Keep WordPress 7.0 in the test matrix if the plugin still supports it. A repair that only targets the new <th> can create the opposite regression for users on older versions.

A safe compatibility pattern for supporting old and new WordPress versions

For most plugins, version detection is unnecessary. The DOM already exposes stable classes that work across both structures.

Prefer this:

.wp-list-table .check-column { /* both versions */ }
.wp-list-table .column-title { /* both versions */ }
.wp-list-table .column-primary { /* both versions */ }

over branching CSS or adding a version class to the body.

If element type genuinely matters, list both possibilities:

.wp-list-table td.column-primary,
.wp-list-table th.column-primary {
    /* Shared compatibility rule. */
}

JavaScript can use the same approach:

const primaryCells = document.querySelectorAll(
    '.wp-list-table .column-primary'
);

Version checks such as version_compare( $GLOBALS['wp_version'], '7.1', '>=' ) should be a last resort. They duplicate information the markup already provides and can behave poorly with development suffixes, backports, or custom Core builds.

What plugin developers should ship before August 19

A focused compatibility release does not need to become a redesign. It should make the smallest reliable changes and document them clearly.

The release should:

  • remove tag-dependent selectors around post-list checkbox and primary columns;
  • retain compatibility with supported older WordPress versions;
  • verify responsive rows and custom columns in WordPress 7.1 RC;
  • rebuild production assets and update browser tests;
  • add a changelog entry that names WordPress 7.1 admin list-table compatibility;
  • update the Tested up to value only after the plugin has actually been tested;
  • tell support staff what a failure looks like and which screen to inspect.

A concise changelog entry could read:

Fixed WordPress 7.1 compatibility for post-list row headers and responsive admin table layouts.

Avoid claiming full WordPress 7.1 compatibility based only on a static code search. The responsive flex change, permission-dependent rows, and dynamically attached events require a real browser test.

What site owners and agencies should do

Site owners do not need to disable automatic updates in panic. They need a normal, disciplined major-release process.

Before updating Core:

  1. Update plugins and theme companion plugins to their latest compatible versions.
  2. Create a current backup.
  3. Clone the site to staging.
  4. Update staging to the current WordPress 7.1 release candidate.
  5. Open Posts, Pages, and every important custom post type.
  6. Test custom columns, bulk actions, Quick Edit, editorial controls, SEO indicators, and mobile-width layouts.
  7. Review the browser console for errors while using those controls.

Pay special attention to agency-built must-use plugins and snippets. A commercial plugin may have a published compatibility release while a five-year-old admin customization still contains td.column-title in an inline stylesheet.

If something fails, deactivate only the suspected admin customization on staging and retest. That isolates the cause far faster than downgrading every plugin. Report the exact screen, WordPress version, browser width, console error, and reproduction steps to the developer.

Even when the public site looks normal, do not ignore a broken editorial screen. Missing bulk controls, inaccessible row names, or detached workflow actions can create operational errors for the people maintaining the site.

What about themes?

A theme that only renders public templates is not affected. However, the practical boundary between a theme and plugin is often blurred in commercial products.

Audit a theme package if it:

  • enqueues styles or scripts with admin_enqueue_scripts;
  • adds columns to Posts, Pages, products, portfolios, or other custom post types;
  • bundles a companion plugin that changes editorial workflows;
  • includes white-label admin styling;
  • replaces or decorates post row actions.

Load admin assets only on the screens that need them. Besides improving performance, proper screen scoping reduces the chance that a broad table rule interferes with future WordPress markup changes.

A useful audit prompt for AI-assisted or “vibe-coded” plugins

AI can help locate fragile selectors, but it should not be asked to “make the plugin compatible with WordPress 7.1” without the exact acceptance criteria. A narrow prompt produces a safer review:

Audit this WordPress plugin for the WordPress 7.1 post list table row-header change. Find CSS, JavaScript, PHP-generated selectors, and browser tests that require th.check-column, td.column-title, td.page-title, td.column-primary, or a td ancestor for .row-title, .post-state, and .row-actions. Replace tag-dependent selectors with screen-scoped class selectors that support both WordPress 7.0 and 7.1. Also identify responsive rules that may conflict with flex-based collapsed list rows. Do not alter public-facing tables or unrelated admin screens. List every modified selector and the test that proves it works.

Then review the diff manually. Check that the model did not remove necessary screen scoping, broaden a rule to every admin table, edit third-party vendor code, or forget to rebuild the distributed asset.

AI assistance does not replace testing with the actual WordPress 7.1 release candidate. It makes the audit faster; it does not make DOM assumptions true.

Frequently asked questions

Will WordPress 7.1 break all plugins that add admin columns?

No. The normal PHP hooks for adding and rendering post-list columns are unchanged. Risk is highest when a plugin’s CSS or JavaScript requires the checkbox to be a <th> or the title to be a <td>.

Does the WordPress 7.1 accessibility change affect the front end?

Not directly. It changes list-table markup in wp-admin. A plugin could still create an indirect workflow problem if editors can no longer use its admin controls, but public theme markup is outside this change.

Which WordPress screens should I test?

Test Posts, Pages, and every custom post type used by the site. If a plugin owns a custom WP_List_Table, test that screen too because the shared base renderer changed.

Do I need separate CSS for WordPress 7.0 and 7.1?

Usually not. Class selectors such as .check-column, .column-title, and .column-primary work across both. If a rule needs the element type, target both td and th.

Why is the title cell a <th> instead of a <td>?

Because the post title identifies the object represented by the row. scope="row" gives assistive technology the correct relationship between that title and the other cells in the same row.

Why does the title row header also need aria-label?

The cell can contain row actions, states, lock information, excerpts, and plugin controls. The label provides a concise row name based on the post title instead of forcing assistive technology to derive a name from all nested content.

Can I wait until WordPress 7.1 is released to test?

You can, but plugin developers and agencies should not. Release candidates exist specifically to find compatibility issues before users receive the final update. WordPress recommends testing pre-releases on local or staging environments, never directly on production.

Is WP_List_Table a stable public plugin API?

No. WordPress marks it private and subject to change. Plugins that subclass it should test every beta and release candidate and avoid assuming its internal HTML will remain fixed.

To wrap up…

WordPress 7.1 is correcting a semantic mistake that made an important administration table harder to understand with a screen reader. The improvement is worth shipping. It also demonstrates why extension code should bind to meaning instead of incidental markup.

.column-title describes purpose. td.column-title describes one historical implementation of that purpose.

Plugins built around the first idea are likely to pass through this update without drama. Plugins built around the second need a small compatibility release. In most cases, the fix is straightforward: remove unnecessary tag assumptions, keep selectors properly scoped, test the responsive flex layout, and verify behavior against both the previous WordPress branch and the 7.1 release candidate.

That is the sensible response to this WordPress 7.1 accessibility change—not panic, and not complacency. A short audit now protects the editorial interface, preserves backward compatibility, and lets Core improve accessibility without leaving plugin users to discover brittle selectors in production.

Leave A Comment