Problem/Motivation

CKEditor mangles the following tables in various ways, causing data loss and severe accessibility issues.

Table 1

This table has a rowgroup header with a colspan

<table>
    <thead>
    <tr>
        <th scope="col">Col Header 1</th>
        <th scope="col">Col Header 2</th>
        <th scope="col">Col Header 3</th>
    </tr>
    </thead>
    <tbody>
    <tr>
        <th scope="rowgroup" colspan="3">Rowgroup Header 1</th>
    </tr>
    <tr>
        <th scope="row">Row Header 1</th>
        <td>Data 1,1</td>
        <td>Data 2,1</td>
    </tr>
    </tbody>
</table>

Table 2

This table has two rowgroup headers that have colspans.

<table>
    <thead>
    <tr>
        <th scope="col">Col Header 1</th>
        <th scope="col">Col Header 2</th>
        <th scope="col">Col Header 3</th>
    </tr>
    </thead>
    <tbody>
    <tr>
        <th scope="rowgroup" colspan="3">Rowgroup Header 1</th>
    </tr>
    <tr>
        <th scope="row">Row Header 1</th>
        <td>Data 1,1</td>
        <td>Data 2,1</td>
    </tr>
    </tbody>
    <tbody>
    <tr>
        <th scope="rowgroup" colspan="3">Rowgroup Header 2</th>
    </tr>
    <tr>
        <th scope="row">Row Header 2</th>
        <td>Data 1,2</td>
        <td>Data 2,2</td>
    </tr>
    <tr>
        <th scope="row">Row Header 3</th>
        <td>Data 1,3</td>
        <td>Data 2,3</td>
    </tr>
    </tbody>
</table>

Table 3

Similar to previous examples, but we've removed the colspan on the rowgroup and replaced with empty td elements. We've also added some row headers to the second column and a tbody block that doesn't have any th with a scope set to rowgroup.

<table>
    <thead>
    <tr>
        <th scope="col">Col Header 1</th>
        <th scope="col">Col Header 2</th>
        <th scope="col">Col Header 3</th>
    </tr>
    </thead>
    <tbody>
    <tr>
        <th scope="rowgroup">Rowgroup Header 1</th>
        <td></td>
        <td></td>
    </tr>
    <tr>
        <th scope="row">Row Header 1</th>
        <th scope="row">Row Subheader 1</th>
        <td>Data 1,1</td>
    </tr>
    </tbody>
    <tbody>
    <tr>
        <th scope="row">Row Header 2</th>
        <th scope="row">Row Subheader 2</th>
        <td>Data 1,2</td>
    </tr>
    <tr>
        <th scope="row">Row Header 3</th>
        <th scope="row">Row Subheader 3</th>
        <td>Data 1,3</td>
    </tr>
    </tbody>
    <tbody>
    <tr>
        <th scope="rowgroup">Rowgroup Header 2</th>
        <td></td>
        <td></td>
    </tr>
    <tr>
        <th scope="row">Row Header 4</th>
        <th scope="row">Row Subheader 4</th>
        <td>Data 1,4</td>
    </tr>
    </tbody>
</table>

Table 4

Just for fun, let's try an example table from the HTML5 spec.

<table>
    <colgroup> <col>
    <colgroup> <col> <col> <col>
    <thead>
    <tr> <th> <th>2008 <th>2007 <th>2006
    <tbody>
    <tr> <th scope=rowgroup> Research and development
        <td> $ 1,109 <td> $ 782 <td> $ 712
    <tr> <th scope=row> Percentage of net sales
        <td> 3.4% <td> 3.3% <td> 3.7%
    <tbody>
    <tr> <th scope=rowgroup> Selling, general, and administrative
        <td> $ 3,761 <td> $ 2,963 <td> $ 2,433
    <tr> <th scope=row> Percentage of net sales
        <td> 11.6% <td> 12.3% <td> 12.6%
</table>

Steps to reproduce

Paste the above code into the source view of the CKEditor reference implementation. Click off source view and then back into it. CKEditor produces the following...

Table 1

CKEditor has moved the thead element to below the parent row for "Rowgroup Header 1", which makes the table inaccessible to screen readers.

<table>
    <thead>
    <tr>
        <th scope="col">
            Col Header 1
        </th>
        <th scope="col">
            Col Header 2
        </th>
        <th scope="col">
            Col Header 3
        </th>
    </tr>
    <tr>
        <th colspan="3" scope="rowgroup">
            Rowgroup Header 1
        </th>
    </tr>
    </thead>
    <tbody>
    <tr>
        <th scope="row">
            Row Header 1
        </th>
        <td>
            Data 1,1
        </td>
        <td>
            Data 2,1
        </td>
    </tr>
    </tbody>
</table>
Table 2

CKEditor has moved all the parent rows for rowgroup headers into the thead element. It has also put all the rows after the thead block into one tbody block. This makes the table inaccessible to screen readers and is a loss of data, since we can no longer reconstruct the original table due to loss of the tbody blocks.

<table>
    <thead>
    <tr>
        <th scope="col">
            Col Header 1
        </th>
        <th scope="col">
            Col Header 2
        </th>
        <th scope="col">
            Col Header 3
        </th>
    </tr>
    <tr>
        <th colspan="3" scope="rowgroup">
            Rowgroup Header 1
        </th>
    </tr>
    <tr>
        <th colspan="3" scope="rowgroup">
            Rowgroup Header 2
        </th>
    </tr>
    </thead>
    <tbody>
    <tr>
        <th scope="row">
            Row Header 1
        </th>
        <td>
            Data 1,1
        </td>
        <td>
            Data 2,1
        </td>
    </tr>
    <tr>
        <th scope="row">
            Row Header 2
        </th>
        <td>
            Data 1,2
        </td>
        <td>
            Data 2,2
        </td>
    </tr>
    <tr>
        <th scope="row">
            Row Header 3
        </th>
        <td>
            Data 1,3
        </td>
        <td>
            Data 2,3
        </td>
    </tr>
    </tbody>
</table>
Table 3

CKEditor has not moved the all the parent rows for rowgroup headers into the thead element, but has put all the rows after the thead block into one tbody block. This causes data loss, since we can no longer reconstruct the original table due to loss of the tbody blocks. And this makes the table inaccessible to screen readers. CKEditor has also turned row headers in the second column to td elements with a scope attribute, which is invalid, and causes further accessibility issues.

<table>
    <thead>
    <tr>
        <th scope="col">
            Col Header 1
        </th>
        <th scope="col">
            Col Header 2
        </th>
        <th scope="col">
            Col Header 3
        </th>
    </tr>
    </thead>
    <tbody>
    <tr>
        <th scope="rowgroup">
            Rowgroup Header 1
        </th>
        <td>
            &nbsp;
        </td>
        <td>
            &nbsp;
        </td>
    </tr>
    <tr>
        <th scope="row">
            Row Header 1
        </th>
        <td scope="row">
            Row Subheader 1
        </td>
        <td>
            Data 1,1
        </td>
    </tr>
    <tr>
        <th scope="row">
            Row Header 2
        </th>
        <td scope="row">
            Row Subheader 2
        </td>
        <td>
            Data 1,2
        </td>
    </tr>
    <tr>
        <th scope="row">
            Row Header 3
        </th>
        <td scope="row">
            Row Subheader 3
        </td>
        <td>
            Data 1,3
        </td>
    </tr>
    <tr>
        <th scope="rowgroup">
            Rowgroup Header 2
        </th>
        <td>
            &nbsp;
        </td>
        <td>
            &nbsp;
        </td>
    </tr>
    <tr>
        <th scope="row">
            Row Header 4
        </th>
        <td scope="row">
            Row Subheader 4
        </td>
        <td>
            Data 1,4
        </td>
    </tr>
    </tbody>
</table>
Table 4

CKEditor has added inline styles to col elements (what?), put all the rows after the thead block into one tbody block (again, potential data loss and makes the table inaccessible to screen readers). I'm not sure what the practical implications of this next quirk are, but I want to note it. CKEditor has added a non-breaking space character to an empty th tag. I don't think non-breaking spaces are ASCII whitespace. This means that the header cell is non-empty and user agents (including screen readers) should add it to the header list for the cells beneath it, according to the spec.

<table class="ck-table-resized">
    <colgroup><col style="width:25%;"><col style="width:25%;"><col style="width:25%;"><col style="width:25%;"></colgroup><colgroup><col><col><col></colgroup>
    <thead>
    <tr>
        <th>
            &nbsp;
        </th>
        <th>
            2008
        </th>
        <th>
            2007
        </th>
        <th>
            2006
        </th>
    </tr>
    </thead>
    <tbody>
    <tr>
        <th scope="rowgroup">
            Research and development
        </th>
        <td>
            $ 1,109
        </td>
        <td>
            $ 782
        </td>
        <td>
            $ 712
        </td>
    </tr>
    <tr>
        <th scope="row">
            Percentage of net sales
        </th>
        <td>
            3.4%
        </td>
        <td>
            3.3%
        </td>
        <td>
            3.7%
        </td>
    </tr>
    <tr>
        <th scope="rowgroup">
            Selling, general, and administrative
        </th>
        <td>
            $ 3,761
        </td>
        <td>
            $ 2,963
        </td>
        <td>
            $ 2,433
        </td>
    </tr>
    <tr>
        <th scope="row">
            Percentage of net sales
        </th>
        <td>
            11.6%
        </td>
        <td>
            12.3%
        </td>
        <td>
            12.6%
        </td>
    </tr>
    </tbody>
</table>

Comments

jameslp created an issue. See original summary.

jameslp’s picture

Issue summary: View changes
jameslp’s picture

wim leers’s picture

Title: CKEditor 5 Mangles Table Structure, August 2023 Edition » [Table] CKEditor 5 changes <thead> and keeps only a single <tbody>, which impedes screen reader accessibility of HTML tables
Version: 10.1.x-dev » 11.x-dev
Priority: Critical » Major
Status: Active » Postponed
Issue tags: +Needs upstream bugfix, +Accessibility, +Needs tests

Thank you! 🙏 We'll need test coverage on the Drupal side to ensure CKEditor 5 never regresses against this without us knowing. Would you be interested in contributing that, @jameslp? 😊 This would be a great addition to \Drupal\Tests\ckeditor5\FunctionalJavascript\TableTest.

There's metadata loss here, not "hard" data loss. So reducing priority to Major.

Please vote with a 👍 there if you want to see it solved sooner.

wim leers’s picture

Title: [Table] CKEditor 5 changes <thead> and keeps only a single <tbody>, which impedes screen reader accessibility of HTML tables » [upstream] [Table] CKEditor 5 changes <thead> and keeps only a single <tbody>, which impedes screen reader accessibility of HTML tables
jweowu’s picture

There's metadata loss here, not "hard" data loss.

It's certainly data corruption and a hard loss of semantic meaning.

I'd agree that some HTML markup could be classed as metadata for most intents and purposes, but table markup is so integral to the semantic meaning of that content that I don't think you can consider it as anything other than "data".

This issue might be treated as less important on the basis of it being likely to affect a relatively small set of users, but for those affected users I think "data loss" is an appropriate description.

Wim Leers credited lauriii.

wim leers’s picture

Forgot to update this issue … but I agree with you, @jweowu :)

See #3384400-19 #3340578-19: [meta] [upstream] Prioritized CKEditor 5 upstream blockers and the current issue summary: I've categorized this (with @lauriii's +1) as the current highest priority for the CKEditor team to fix as far as the Drupal project is concerned :)

ericgsmith’s picture

Cross posting for visibility - but I have added comment and a draft MR with a PoC to resolve the tbody part of problem.

Would love for anybody impacted by this issue to head over to the CKEditor issue to take a look and provide any relevant feedback.

wim leers’s picture

@ericgsmith 👏

bkosborne’s picture

Wim, in #8 I think you meant to tag a separate issue but you tagged this one

jwilson3’s picture

There's metadata loss here, not "hard" data loss. So reducing priority to Major.

There's an argument here that this should be left as critical

I’m responding to an RFP for a city government website proposal, and one question completely disqualifies Drupal 10 with CKE5 as inconformant:

To comply with the U.S. Access Board Web-based Intranet and Internet Information and Applications (1194.22) provisions, the solution provides the following:

  • The solution allows markup to be used to associate data cells and header cells for data tables that have two or more logical levels of row or column headers.

The CKEditor 5 demo breaks the table example from Curriculum for Web Content Accessibility Guidelines 1.0: 5.2 - For data tables that have two or more logical levels of row or column headers, use markup to associate data cells and header cells. (created in year 2000) (Paste the table in via the Source mode button).

wim leers’s picture

Priority: Major » Critical

This already is the most important remaining prioritized upstream blocker (see #3340578: [meta] [upstream] Prioritized CKEditor 5 upstream blockers). The CKEditor 5 team knows.

Can you share at https://github.com/ckeditor/ckeditor5/issues/14911 that this is critical for any U.S. government website? 🙏 You already did: https://github.com/ckeditor/ckeditor5/issues/14911#issuecomment-1942852909 👏🙏 Thank you!

Bumping this to Critical.

Thank you very much, @jwilson3! Crediting you 😊

jwilson3’s picture

No problem at all. Thanks to you, Wim, for your continued and undying efforts here in general.

metsfan804’s picture

Has there been any update regarding this issue. We've newly upgraded to CKEDITOR 5 and we're seriously considering rolling back due to how it mangles tables.

smulvih2’s picture

I was having similar issues, in my case CKE5 was stripping tfoot. This would even happen after diabling the table plugin on my text format. I have created a module to fix this - https://www.drupal.org/project/ckeditor5_table_fix

It provides a faux-plugin that replaces the table plugin, and provides as much flexibility in table markup as possible. No UI components, just the ability to copy/past semantically correct tables into a text format and prevent stripping.

joseph.olstad’s picture

FYI, we've published newer versions of ckeditor5_table_fix that expand upon the support of advanced markup accepted by the wysiwyg editor when this plugin is installed and configured as per instructions.

ericgsmith’s picture

For visibility - there has been a recent update to the CKEditor issue by @Witoso which sounds promising - https://github.com/ckeditor/ckeditor5/issues/14911#issuecomment-3751133289

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.

ericgsmith’s picture

The issue mentioned in #3 has now been closed. The comment from @andrzejkala provides a good summary of what is enabled in CKEditor 48.

Not all problems in the issue summary are resolved by that issue - the issue for preserving multiple tbody elements has been split out into https://github.com/ckeditor/ckeditor5/issues/20074.

dalemoore’s picture

Is it documented somewhere when CKEditor versions are incorporated into Core? Just curious when we would expect v48 to be included. The improvements they're making to tables are definitely appreciated.

joseph.olstad’s picture

Status: Postponed » Needs work