Skip to content

Preserve multiple <tbody> sections instead of collapsing them into one #20074

Description

@andrzejkala

Follow up to #14911

When a table with multiple <tbody> elements is loaded into the editor, all body rows are merged into a single <tbody> on output. The current table model stores body rows as a flat sequence of elements with no representation of <tbody> boundaries, so the conversion pipeline normalizes every table body to one <tbody> regardless of the input structure.

Multiple <tbody> sections are valid HTML spec and are the standard mechanism for grouping rows into logical sections — for instance, organizing data under scope="rowgroup" headers as shown in the W3C WAI Tables Tutorial. Screen readers rely on these boundaries to announce rowgroup context, and losing them degrades table navigation for assistive technology users. It also constitutes data loss, since the original grouping cannot be reconstructed from the collapsed output.

It is also worth noting that while the spec's content model places <thead> before any <tbody> elements, the HTML parser's error recovery handles <thead> tokens encountered inside a table body by closing the current <tbody> and processing the <thead> normally. This means real-world HTML can contain a <thead> appearing between <tbody> sections, and browsers will produce a valid DOM tree from it. The model should account for this when processing tables from external or legacy sources.

📝 Provide detailed reproduction steps (if any)

  1. Load the following HTML into the editor (via source editing or editor.setData()):
<table>
    <thead>
    <tr>
        <th></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>
    </tbody>
    <tbody>
    <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>
  1. Call editor.getData() or switch to source view.

✔️ Expected result

Two <tbody> sections in the output, matching the input.

❌ Actual result

All body rows are wrapped in a single <tbody>.


If you'd like to see this fixed sooner, add a 👍 reaction to this post.

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:accessibilityThis issue reports an accessibility problem.domain:v4-compatibilityThis issue reports a CKEditor 4 feature/option that's missing in CKEditor 5.package:tablesupport:2An issue reported by a commercially licensed client.type:bugThis issue reports a buggy (incorrect) behavior.

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions