Tables and cells
Insert tables, type in cells, add or delete rows and columns, merge and split cells, and format tables.
A PPTX table is a graphic frame that holds an a:tbl. You edit it with editor commands: run editor.exec(command), or check first with editor.can(command). Each command acts on the table of the current selection: a caret in a cell, a range of cells, or the selected table frame. Every edit is one undo step, and every edit keeps the table valid under ECMA-376: one cell for each grid column in every row, and merge markers (gridSpan, rowSpan, hMerge, vMerge) on the covered cells.
The toolbar shows the table group while the selection is in a table. Customize controls through slots such as table.mergeCells, table.splitCell, table.cellFill, and table.borderTarget.
Insert a table
insertTable adds a table to the current slide with the deck's default table style, Header Row, and Banded Rows. The caret goes into the first cell. Where the table goes depends on the slide:
- When the slide has an empty content or table placeholder, the table takes its place. It fills the placeholder that is selected or holds the caret, or else the first one on the slide. The table starts at the placeholder's top-left corner and is as wide as the placeholder. It keeps the placeholder's index, so a layout change moves it with the placeholder. One undo brings the empty placeholder back.
- A placeholder with its own fill, line, or effect is passed over. The table fills the next empty placeholder, or goes to the middle of the slide when none is left.
- Otherwise, the table is two thirds of the slide wide and centered.
import { MenuTableGrid, useEditorCommand } from '@pptx-editor.dev/react';
// The 10 × 8 size grid inside a host's Insert > Table submenu.
function InsertTableMenu({ close }: { close: () => void }) {
return <MenuTableGrid onInserted={close} />;
}
// The same insert from your own button.
function InsertThreeByTwo() {
const insert = useEditorCommand('table.insert');
return (
<button disabled={!insert.isEnabled} onClick={() => insert.execute({ rows: 2, cols: 3 })}>
Insert a 3 × 2 table
</button>
);
}Counts must be integers from 1 to 75. MenuTableGrid is a grid with one tab stop: the arrow keys, Home, and End move in it, and Enter inserts the table.
Read the table of the selection
editor.snapshot().table is a TableContext while the selection is in or on a table, and null otherwise. tableChromeVisible(snapshot.table) tells you whether to show the table controls.
const table = editor.snapshot().table;
if (table) {
console.log(table.rows, table.columns); // the grid size
console.log(table.rowIndex, table.columnIndex); // the caret's cell, or the range's focus cell
console.log(table.selectedRows, table.selectedColumns); // { from, to } each
console.log(table.styleId, table.options); // the style and its style options
}A caret in a cell is a text selection with a cell address. A range of cells is a cells selection:
editor.setSelection({
kind: 'cells',
nodeId: table.nodeId,
anchor: { row: 0, column: 0 },
focus: { row: 1, column: 1 },
});An address inside a merged cell moves to the merged cell's top-left grid position.
Edit cell text
Every text command works in a cell, in each cell of a range, and in each cell of a selected table: font, size, bold, alignment, bullets, and the others. Type with editor.input('text', value) while the caret is in a cell. Tab selects the text of the next cell, and Shift+Tab the text of the cell before. Tab in the last cell adds a row. An arrow key at the edge of a cell's text moves to the next cell, and Escape selects the table. Typing with a range of cells selected is refused with disabled.typeInOneCell.
Shift+Arrow extends the text selection in the cell. At the edge of the cell's text, it selects a range of cells, and later Shift+Arrow presses grow the range one cell at a time.
Copy and paste cells
With a range of cells selected, Copy and Cut put the cells on the clipboard as tab-separated text, one line for each row. Cut also clears the cells. Paste into a range of cells, or paste text that holds tabs at a caret in a cell, to fill cells from the top-left cell of the range or the caret's cell. Text pasted at a caret without tabs, and one copied cell, go into the cell's text at the caret, as usual.
The paste runs pasteCells, so a host can run it too:
editor.exec({
type: 'pasteCells',
texts: [
['Q1', 'Q2'],
['12', '15'],
],
});A paste larger than the table adds rows at the bottom and columns at the end of the grid (the right, or the left in a right-to-left table), in the same undo step. New columns share the table's width. A covered position of a merged cell takes no text. A line break in a value makes a new paragraph in its cell. A paste reads at most 75 rows, 75 columns, and 32,768 characters in each cell. After the paste, the filled cells are selected.
Edit on the canvas
The canvas shows table chrome while the pointer is over a table or the selection is in it:
- Select handle. A small box with a move symbol sits out from the table's top-left corner. Click it to select the table. Drag it to move the table.
- Grid lines. The grid line under the pointer is marked across the table. Drag it to resize the columns or rows beside it; the right and bottom edges resize the last column or row, and stop at the slide's edge so you can grab them again. A selected table has no side handles on these two edges, so each edge does one thing. The frame's other resize handles stop at the slide's edge too, also with Shift held on a corner. Just outside the top and left edges, as on any shape's border, a drag moves the table. A press inside a cell places the caret, except on a grid line, where the resize cursor shows. The drag selects the table and is one undo step. A click on a line places no caret. A table in a group shows its chrome after you enter the group.
- Insert buttons. Point at the left edge of the table to show a "+" button just outside it, at the nearest row line. Click it to insert a row below that line, with the caret in the new row. The top edge shows the same button for a column to the right of a column line. A right-to-left table inserts columns from the menus only.
- Context menu. Right-click a cell for Cut, Copy, and Paste, the Insert, Delete, and Select submenus, Merge cells, Split cells, and the Font and Paragraph dialogs. Right-click the select handle, or a table that is selected as an object, for the object menu with the Insert submenu.
- Format panel. The Format panel shows the size and position of a selected table, its row height and column width, and the table style options. A table has no rotation. Enter commits a field and keeps the focus there, and Ctrl+Z (Cmd+Z) in the panel undoes the change.
The chrome does not show in view mode. The toolbar shows a short table group while the selection is in a table: the style, cell fill, borders, pen color, pen weight, delete, insert, and merge. The cell context menu has Select and Split cells. The style options, pen style, distribute, and cell margins are on the ribbon's Table Design and Table Layout tabs, and a host places any table slot with Toolbar.Slot.
Edit structure
| Command | Effect |
|---|---|
{ type: 'insertRow', where: 'above' | 'below' } | One row for a caret or a selected table, one row for each selected row of a range. After an insert from a range, the new rows are selected; a caret stays in its cell |
{ type: 'insertColumn', where: 'left' | 'right' } | The same for columns. The table keeps its width: the columns narrow in proportion |
{ type: 'deleteRow' }, { type: 'deleteColumn' } | Deletes the selected rows or columns; the table goes when none would stay |
{ type: 'deleteTable' } | Deletes the table |
{ type: 'selectTableRegion', region: 'table' | 'row' | 'column' } | Selects the table, or the rows or columns of the selection. A merged cell does not widen a whole row or column: the highlight, insert, delete, formatting, and copy all act on its own grid lines, so Delete columns under a merged header deletes one column. Merge cells still grows over the merged cells it crosses |
{ type: 'setRowHeight', height }, { type: 'setColumnWidth', width } | Sizes in EMU; a row height is a minimum, so rows still grow to fit their text |
{ type: 'distributeRows' }, { type: 'distributeColumns' } | Gives the selected rows or columns the same size |
{ type: 'setTableGrid', columns?, rows? } | Sets every grid column width or row height at once, in EMU, as a grid line drag does. Run the steps of one drag with one historyGroup to undo them as one step (see Undo and history groups) |
A merged cell that a new row or column crosses grows over it. A merged cell that loses some of its rows or columns keeps its text.
Merge and split cells
mergeCells merges a range of cells into one cell. The text of every merged cell moves into it, in reading order. A range of one cell is refused with disabled.mergeOneCell.
splitCell splits one cell into rows × cols cells. A merged cell splits along its grid lines when its width or height divides evenly. A one-column cell gets new grid columns that share its width, and the cells above and below it widen over them.
editor.exec({ type: 'mergeCells' });
editor.exec({ type: 'splitCell', rows: 2, cols: 1 });Format tables and cells
| Command | Effect |
|---|---|
{ type: 'setTableStyle', styleId } | A style from editor.tableStyles(); the id is an upper-case GUID |
{ type: 'toggleTableStyleOption', option } | firstRow, lastRow, bandRow, firstCol, lastCol, or bandCol |
{ type: 'setCellFill', color } | A color, or null for No Fill |
{ type: 'setTableBorders', scope, spec } | all, outside, inside, top, bottom, left, or right, with { color, width, dash? } in points; { scope: 'none' } removes every border of the selected cells |
{ type: 'setCellMargins', margins } | left, right, top, bottom in EMU |
{ type: 'pasteCells', texts } | Fills cells from the top-left cell of the selection with rows of text (see Copy and paste cells) |
{ type: 'setVerticalAlign', anchor } | t, ctr, or b for the selected cells |
Draw borders with the pen
The Borders menu (table.borderTarget) draws with the border pen: Pen color (table.borderColor), Pen style (table.borderStyle, a LineDash), and Pen weight (table.borderWidth, in points). The pen starts as 1 pt solid in the theme's Text 1.
- A pen color, style, or weight pick sets the pen and changes nothing in the document.
- A border target pick (All, Outside, Inside, or one side) draws the pen there.
- After a border target pick, a pen pick redraws that target with the new pen while the same cells stay selected. In other cells, a pen pick only arms the pen. No Border clears the borders and stops the redraw.
The pen is a setting of each editor, not part of the document. tableBorderPen(editor) returns it, and runToolbarCommand keeps it current:
runToolbarCommand(editor, 'table.borderColor', '#1F4E79'); // sets the pen only
runToolbarCommand(editor, 'table.borderTarget', 'outside'); // draws the outside borders
runToolbarCommand(editor, 'table.borderWidth', '3'); // redraws them 3 pt wide
tableBorderPen(editor); // { target: 'outside', drawn: true, spec: { color: '#1F4E79', width: 3 } }Customize the table chrome
The table controls follow the customization ladder of the toolbar. Hide or restyle them by slot id, or hide the whole group:
<PptxEditor.Toolbar>
<PptxEditor.Toolbar.Group id="table" hidden />
</PptxEditor.Toolbar>
<PptxEditor.Toolbar>
<PptxEditor.Toolbar.Slot slotId="table.mergeCells" hidden />
<PptxEditor.Toolbar.Slot slotId="table.insertRowBelow" className="acme-insert-below" />
</PptxEditor.Toolbar>Give a button your own icon, or render your own element with asChild. The named parts are the same controls:
<PptxEditor.Toolbar>
<PptxEditor.Toolbar.TableInsert icon={<AcmeTableIcon />} />
<PptxEditor.Toolbar.TableInsertRowBelow icon={<AcmeRowIcon />} />
<PptxEditor.Toolbar.TableMergeCells asChild>
<AcmeButton />
</PptxEditor.Toolbar.TableMergeCells>
</PptxEditor.Toolbar>table.insert (the Insert Table grid button) takes icon and asChild too, and table.cellFill and table.borderColor take icon for their color splits. The dropdown pickers (table.style, table.borderTarget, table.borderStyle, table.borderWidth, table.select, table.delete, table.splitCell, and table.cellMargins) take className and hidden, and warn in development on icon or asChild. PptxEditor.Menu's Insert menu already holds the Table grid; PptxEditor.Menu.TableGrid places it in your own menu, and it closes that menu after an insert.
In Vue, the same parts are PptxEditorToolbar, ToolbarGroup, and ToolbarSlot:
<PptxEditorToolbar>
<ToolbarGroup id="table" hidden />
</PptxEditorToolbar>
<PptxEditorToolbar>
<ToolbarSlot slot-id="table.mergeCells" hidden />
<ToolbarSlot slot-id="table.insertRowBelow" className="acme-insert-below" />
</PptxEditorToolbar>TABLE_CHROME_SLOT_IDS lists the slots that a table selection shows, and isTableChromeSlot(slot) tests one. Hiding a control changes the interface only: the command still runs. The Vue toolbar draws the same table controls as the React toolbar, pickers included.
Handle unsupported structures
A command that cannot run returns { ok: false, reason } and changes nothing. The reasons:
| Reason | When |
|---|---|
disabled.noSelection | Nothing is selected |
disabled.noTable | The selection is not in a table |
disabled.mergeOneCell | Merge with one cell selected |
disabled.splitOneCell | Split with more than one cell selected |
disabled.splitUneven | A split that does not divide a merged cell evenly |
disabled.tableTooLarge | The table would grow past 75 columns or 1,000 rows |
disabled.typeInOneCell | Typing with a range of cells selected |
errors.unsupported | A value no table can take, with a detail: a size, color, or style id a host passes; distribute on a table without sizes; or a table from a file that the editor cannot read (rows that do not match its grid in a table larger than the editor makes) |
A table whose rows do not hold one cell for each grid column is repaired by the next table command when it is no larger than 75 columns and 1,000 rows, and a save writes whole rows. In a collaboration session, two people can change the rows, columns, or merged cells of one table at the same time and leave merge markers that do not match. The editor then shows and edits that table with the markers that the next table command would write, and a save writes those markers. Every other table shows and saves with the merge markup that it has in the editor.
Next steps
See Toolbar customization for the slot ladder, and PPTX fidelity for what the editor supports.