Skip to content

POC: Table block with inner blocks - #81593

Closed
talldan wants to merge 75 commits into
trunkfrom
update/table-block-v2
Closed

talldan wants to merge 75 commits into
trunkfrom
update/table-block-v2

Conversation

@talldan

@talldan talldan commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

What?

A proof of concept of a table block that uses inner blocks. This isn't intended to be shippable, but the idea is to demonstrate the possibility, discuss whether it's worth it. If we think is worth it, we can discuss exactly what needs to be done to support such a thing, and potentially move forwards with a better implementation than in this PR 😄

The PR is heavily AI generated with human guidance, but not much code review (only high level discussion), so I don't expect others to review the code!

Features in this PR:

  • Multi-cell selection (row, columns, rectangular selections of cells)
  • Cell merging (colspan, rowspan)
  • Cell (or multi-cell) styling (border, typography, colors, padding)
  • An 'outside border' tool (not supposed to be final, this was to demo possibilities, the existing border tools will likely need some extra features like this to support tables)

A couple of future features that this would become easier to implement:

  • A more flexible way to define header cells (e.g. th cells with scope=row)
  • Cell splitting

Why?

In the past there have been discussions about the possibilities for the table block (#18768). The block editor doesn't have built-in support for everything the table block needs to be able to use inner blocks, so those discussions were never explored. I think AI now allows us to explore a proof of concept with lower effort than previously, so it seems a great time to explore improving the table block.

How?

New blocks

Adds four new blocks:

  • Table V2
  • Table Cell (which also has a Table Header variation for when used as a <th>)
  • Table Section
  • Table Row

Table Section and Row blocks exist purely as a way to output thead, tbody, tfoot, tr and th elements. The blocks have blockEditingMode: disabled set permanently, so they're not visible in the editor. I personally found making these blocks selectable in the editor makes the table block overly complicated. I'm not sure there's a huge amount of value in allowing these blocks to be selected. For sections and rows (and columns), we could possibly support styling via elements instead of via a block. Another point is that it wouldn't be possible to support a column block, so only having row blocks feels unusual.

Block selection

The block editor store has added support for non-contiguous set selections. This is introduced to support selection of columns and rectangular ranges within a table. Broader support for non-contiguous block selections within the editor UI isn't added in this PR, but it could form the basis.

There are also writing flow changes to support table selection natively. Whenever table cells are detected as blocks, mouse and keyboard multi-block selection uses a rectangular selection (creating a 'set' selection of clientIds) , and accounts for situations where cells are merged. This could probably do with some iteration, it's here as a proof of concept currently.

Block supports

editingMode: disabled

Mentioned above, this makes the row and section blocks not selectable in the editor.

listView: false

Prevents the table cell block from displaying in List View. Even a relatively modest 10x10 table has 100 cells, so I'm not sure there's value in displaying every cell, it quickly becomes overwhelming and not particularly useful. It's possible that there might be a better way to support the table block in List View, but it might be best as a separate exploration.

removable: false

Prevents table cells from being individually removable. They're only removable via delete column(s) / delete row(s)

movable: false

Prevents table cells from being individually movable.

duplicate: false

Prevents block duplication for table cells.

copy: false

Prevents block copy/paste for table cells.

Multi selection attribute update fix

The fix from #80070 is also in this PR, this should be shipped separately.

Testing Instructions

  1. Enable in Gutenberg > Experiments the experiment for new blocks.
  2. Insert the Table V2 block
  3. From the placeholder choose columns and row count and create a table
  4. Click into a cell to add some text, observe the cells are individual blocks
  5. Select a range of cells by clicking a cell and dragging a range
  6. From the toolbar dropdown observe you can change text alignment for those cells, merge cells, apply border around the cells, and from the 'Edit table' dropdown expand the selection to a row or table
  7. Also observe that in the block inspector it's possible to apply other styles to an individual or range of cells

Screenshots or screencast

Kapture.2026-08-13.at.17.46.46.mp4

Use of AI Tools

OpenCode / Codex, Kimi K3, GLM-5.2

@talldan talldan self-assigned this Aug 13, 2026
@talldan talldan added the [Block] Table Affects the Table Block label Aug 13, 2026
@talldan talldan added the [Type] Experimental Experimental feature or API. label Aug 13, 2026
@github-actions github-actions Bot added [Package] Core data /packages/core-data [Package] Blocks /packages/blocks [Package] Block library /packages/block-library [Package] Block editor /packages/block-editor [Package] Icons /packages/icons labels Aug 13, 2026
@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: talldan <talldanwp@git.wordpress.org>
Co-authored-by: Intenzi <intenzi@git.wordpress.org>
Co-authored-by: t-hamano <wildworks@git.wordpress.org>
Co-authored-by: Mamaduka <mamaduka@git.wordpress.org>
Co-authored-by: ellatrix <ellatrix@git.wordpress.org>
Co-authored-by: annezazu <annezazu@git.wordpress.org>
Co-authored-by: andrewserong <andrewserong@git.wordpress.org>
Co-authored-by: tellthemachines <isabel_brison@git.wordpress.org>
Co-authored-by: jasmussen <joen@git.wordpress.org>
Co-authored-by: youknowriad <youknowriad@git.wordpress.org>
Co-authored-by: ramonjd <ramonopoly@git.wordpress.org>
Co-authored-by: aduth <aduth@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown

Size Change: +13 kB (+0.16%)

Total Size: 7.93 MB

📦 View Changed
Filename Size Change
build/scripts/block-editor/index.min.js 477 kB +1.71 kB (+0.36%)
build/scripts/block-library/index.min.js 374 kB +5.5 kB (+1.49%)
build/styles/block-editor/content-rtl.css 5.7 kB +36 B (+0.64%)
build/styles/block-editor/content-rtl.min.css 4.17 kB +27 B (+0.65%)
build/styles/block-editor/content.css 5.7 kB +37 B (+0.65%)
build/styles/block-editor/content.min.css 4.16 kB +27 B (+0.65%)
build/styles/block-library/editor-rtl.css 12.7 kB +24 B (+0.19%)
build/styles/block-library/editor-rtl.min.css 10.4 kB +13 B (+0.13%)
build/styles/block-library/editor.css 12.8 kB +24 B (+0.19%)
build/styles/block-library/editor.min.css 10.4 kB +13 B (+0.13%)
build/styles/block-library/style-rtl.css 22.5 kB +52 B (+0.23%)
build/styles/block-library/style-rtl.min.css 19 kB +62 B (+0.33%)
build/styles/block-library/style.css 22.6 kB +55 B (+0.24%)
build/styles/block-library/style.min.css 19 kB +57 B (+0.3%)
build/styles/block-library/table-v2-cell/editor-rtl.css 60 B +60 B (new file) 🆕
build/styles/block-library/table-v2-cell/editor-rtl.min.css 53 B +53 B (new file) 🆕
build/styles/block-library/table-v2-cell/editor.css 60 B +60 B (new file) 🆕
build/styles/block-library/table-v2-cell/editor.min.css 53 B +53 B (new file) 🆕
build/styles/block-library/table-v2-cell/style-rtl.css 533 B +533 B (new file) 🆕
build/styles/block-library/table-v2-cell/style-rtl.min.css 146 B +146 B (new file) 🆕
build/styles/block-library/table-v2-cell/style.css 532 B +532 B (new file) 🆕
build/styles/block-library/table-v2-cell/style.min.css 144 B +144 B (new file) 🆕
build/styles/block-library/table-v2/editor-rtl.css 1.08 kB +1.08 kB (new file) 🆕
build/styles/block-library/table-v2/editor-rtl.min.css 158 B +158 B (new file) 🆕
build/styles/block-library/table-v2/editor.css 1.08 kB +1.08 kB (new file) 🆕
build/styles/block-library/table-v2/editor.min.css 158 B +158 B (new file) 🆕
build/styles/block-library/table-v2/style-rtl.css 531 B +531 B (new file) 🆕
build/styles/block-library/table-v2/style-rtl.min.css 144 B +144 B (new file) 🆕
build/styles/block-library/table-v2/style.css 531 B +531 B (new file) 🆕
build/styles/block-library/table-v2/style.min.css 144 B +144 B (new file) 🆕

compressed-size-action

@ellatrix

Copy link
Copy Markdown
Member

Yes please ❤️

@annezazu

Copy link
Copy Markdown
Contributor

Love the concept and the attempt! Some quick bugs/notes to address:

Hard to select items in a row from right to left

selecting.all.mov

Merge cell icon unclear

That looks like a copy icon to me and I totally missed it when testing without watching your video.

Merging cells loses text

Not sure what should happen here but I'd expect both items of text to remain rather than losing it:

merging.mov

@andrewserong

Copy link
Copy Markdown
Contributor

Yes please ❤️

💯

Haven't looked at the code, but this is amazing. Love being able to select and style by column or row!

image

@ellatrix

Copy link
Copy Markdown
Member

So when will we have sheets/excel functionality? 😜

@t-hamano t-hamano left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR! I've only tested it briefly, but I have a few observations to share.

1. Merging and unmerging cells. If a cell that is already merged is included in the selected cells, merging or unmerging cannot be performed. I believe that if the set of selected cells forms a visual rectangle, it should always be possible to merge them.

merge-unmerge.mp4

2. Add/Delete Row/Cell. Inserting or deleting cells in a row or column that contains merged cells can break the layout. In such cases, we need to add or subtract rowspan or colspan.

insert-delete-row.mp4

Accessibility. Ideally, cell merging should be achievable using only keyboard operations, but this is not currently possible.

keyboard-cell.mp4

Regarding item 1.2, I have previously addressed this with my own plugin. All the logic is consolidated here, so I hope it will be helpful.

https://github.com/t-hamano/flexible-table-block/blob/main/src/utils/table-state.ts

@tellthemachines

Copy link
Copy Markdown
Contributor

This is a pretty nice POC!

To be clear, we would implement this as an improvement to Table v1, with migration etc? Otherwise it'd be a bit awkward to have two Table blocks.

Would it make sense to be able to add child blocks inside cells? I think that's been requested before

Comment on lines +270 to +287
if ( multiSelectedBlockClientIds.length > 0 ) {
const updates = getPerBlockAttributeUpdates(
attributes,
newAttributes,
getBlocksByClientId( multiSelectedBlockClientIds )
);
if ( updates ) {
updateBlockAttributes(
multiSelectedBlockClientIds,
updates,
{
uniqueByBlock: true,
}
);
}
} else {
updateBlockAttributes( clientId, newAttributes );
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. Maybe something like this could be helpful with #41260. cc @tellthemachines

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we should definitely extract the fix for that. Most of it is based on the existing PR a contributor did, there was just an extra bug in that PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of the fix here is based on #80070, I've asked the contributor if I can rebase and add a couple of commits to that PR.

Delete row/column operated only on the anchor cell's own range, so a
multi-cell selection deleted just one row or column despite the plural
menu labels. The range now comes from the selection rectangle when a
cell set is selected, matching the flat-structure behavior.

Merged cells starting in the deleted range whose span extends beyond it
are split first, so their surviving slots become real cells and the
table stays rectangular, instead of vanishing with the deleted cell.
The deletion geometry moves to tested pure functions,
getColumnDeletionActions and getRowDeletionActions, computed on fresh
placements after the splits.
Merge was blocked whenever the selection contained an already-merged
cell. Now any rectangular same-section selection can merge: merged
cells in the selection are split first, then the bounding rectangle is
merged, matching how other table editors behave.

Because the selection rectangle's bounds already account for spans, it
always covers the whole of any selected merge, and the splits leave
only single cells inside it. Cell removal is computed from fresh
placements after the splits, since splitting adds cells the selection
didn't know about.
Select row/column only selected the anchor cell's row or column despite
the plural menu labels. When a cell set is selected, they now select
every cell intersecting the selection rectangle's row or column range,
restoring the flat-structure behavior.
Clicking into a cell selects it with a null initial position, and
MULTI_SELECT_SET never updated that state, so useMultiSelection bailed
and the native drag selection was never cleared for table cell
selections. When a later operation (like merging cells) removed the
selected cells from the DOM, the browser repaired the lingering range
into a non-collapsed selection anchored in a surviving cell, and the
selection observer re-interpreted it as a new cell multi-selection
covering extra cells.

Handle MULTI_SELECT_SET in the initialPosition reducer and pass an
initial position for canvas drag selections in the selection observer,
so the native selection is cleared when the block selection is
established — the same behavior as a regular block multi-selection.
Toolbar-triggered sets (Select row/column) omit the position, so focus
still stays on the toolbar.
Merging discarded the contents of all but the top-left cell. The merged
cell now keeps the contents of every cell in the rectangle, in document
order, joined with line breaks. Cells without visible text are skipped
so they don't add stray blank lines.
Move getTableCell and getTableCellRectangleClientIds out of the
selection observer into a shared module built on a common layout
builder, and add getTableCellNeighbor: the block-level cell adjacent to
a cell in a given direction, span-aware via the occupancy grid. To be
used for grid-aware keyboard selection.
multiSelectSet recorded the set's document-order first and last blocks
as the selection start and end, losing the direction of a drag or
keyboard extension. A rectangle's document-order ends also cannot
represent off-diagonal ends (anchor top-right, focus bottom-left).

Accept optional anchorClientId/focusClientId and record them instead.
The selection observer passes the drag's start and end cells, so a
later keyboard extension grows the end the user was actually dragging.
The shift+arrow paths in use-arrow-nav extended selections in block
document order or left the browser to extend the native selection,
which both move horizontally across a row. For a caret in a cell or a
cell multi-selection, shift+arrow now moves the focus end to the
adjacent cell on the table grid (span-aware via getTableCellNeighbor)
and selects the rectangle from the anchor cell.

At the table's edge, the caret path falls through to native handling so
the selection can still extend out of the table.
@talldan
talldan force-pushed the update/table-block-v2 branch from 16772dd to 9fb2566 Compare August 26, 2026 06:52
The table-v2 block renders its nested structure with the standard
useInnerBlocksProps flow, which already forwards options to
UncontrolledInnerBlocks and registers the inner block settings there,
so the hook and the restructured settings registration are no longer
needed.
Its only consumer was useInnerBlockItems.save, removed with the hook.
The table-v2 block serializes its nested structure in save.js.
The guard existed only because getRichTextValues polluted the
innerBlocksPropsProvider global that useInnerBlockItems.save() read.
Both are gone, and getRichTextValues recurses into inner blocks, so
footnotes inside table cells are numbered normally.
Remove the comment-only stylesheets for the row and section blocks
along with the block.json style fields referencing them, and drop the
unused __ import from the table block.
TextControl is flagged by the use-recommended-components lint rule in
favor of InputControl from @wordpress/ui. The new component passes
native input props through and reports changes via onValueChange, which
hands the same string value to the existing state setters.
@github-actions github-actions Bot removed [Package] Core data /packages/core-data [Package] Blocks /packages/blocks labels Aug 26, 2026
@ramonjd

ramonjd commented Aug 26, 2026

Copy link
Copy Markdown
Member

Gave this another test drive. Great stuff.

Just mentioning the following for future iterations: I was clicking around testing the "Edit table" dropdown, and tried to delete a "row" when a col was selected and nuked the table!

Kapture.2026-08-26.at.18.22.29.mp4

@talldan

talldan commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

Just mentioning the following for future iterations: I was clicking around testing the "Edit table" dropdown, and tried to delete a "row" when a col was selected and nuked the table!

Yep, it's a feature to try to mimic what excel/sheets does. The button text said "Delete rows", it deletes every row that the selection covers. Maybe it could say 'Delete rows 2-5' or something, or have a confirm step. It should probably go back to the placeholder when the entire table is deleted too!

@talldan

talldan commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

I've pushed some updates to the PR and reduced the number of new APIs that are being introduced by changing the technical approach.

Also fixed some of the bugs mentioned.

As requested by @youknowriad, the PR description has a summary of the API changes.

@talldan

talldan commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

In terms of how to move this forward. We can iterate on this PR as a proof of concept till we're happy with the general approach, but I wouldn't try to merge. After that I'd suggest smaller PRs to actually ship code, so that the API changes are easier to review.

  • PR 1 - an initial version of the block (not with all the features in this PR), behind an experiment/feature flag. Perhaps use a similar approach to gallery, add separate versions of the block files that are used when the flag is active.
  • PR 2 - Selection API changes
  • PR 3-N - Add any other desired features to the block (border improvements, element support, block supports)
  • Last PRs - Migration code, user testing, stabilization

@aduth

This comment was marked as off-topic.

@ramonjd

ramonjd commented Aug 26, 2026

Copy link
Copy Markdown
Member

Yep, it's a feature to try to mimic what excel/sheets does. The

Ah today I learned, thanks. If that's what users expect to happen, then 👍🏻 I was very click happy during testing

@youknowriad

Copy link
Copy Markdown
Contributor

Thanks for the replies and update to the description. It's a lot clearer and feels doable.

The block selection changes seems like the biggest frameworky part. I know that non contiguous selection is also something that @ellatrix looked at in the past and it's a feature that can be shipped regardless of the table block. ctrl + click (then delete or else) could be a good feature on its own. So yeah, this feels like something that can land in parallel.

Other than that, from my reading, there's no other API changes, maybe the "editingMode: disabled" is also something that could benefit other blocks (list item, column?) and I wonder if we need to think about this more. Like should it be a runtime or more declarative block API..

Glad to see this moving forward.

@talldan

talldan commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

Closing this now. I've made an iteration issue (Table block with inner blocks - iteration issue for WordPress 7.3-7.4) that we can use to discuss the implementation, at the moment it tracks what I mentioned in the PR description, I think WordPress 7.3 or 7.4 could be a good target.

@talldan talldan closed this Sep 8, 2026
@talldan
talldan deleted the update/table-block-v2 branch September 8, 2026 06:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Block] Table Affects the Table Block [Package] Block editor /packages/block-editor [Package] Block library /packages/block-library [Package] Icons /packages/icons [Type] Experimental Experimental feature or API.

Projects

None yet

Development

Successfully merging this pull request may close these issues.