Skip to main content

Overview

The @lexical/table package provides comprehensive table functionality for Lexical, including table creation, manipulation, cell merging, and table-specific selection.

Installation

npm install @lexical/table

Nodes

TableNode

Represents a table element. Methods:
getColWidths()
number[] | null
Returns the column widths array
setColWidths()
this
Sets the column widths

TableRowNode

Represents a table row (<tr>).
getHeight()
number | null
Returns the row height
setHeight()
this
Sets the row height

TableCellNode

Represents a table cell (<td> or <th>).
headerState
TableCellHeaderStates
Cell header state: NO_STATUS, ROW, COLUMN, or BOTH
colSpan
number
Number of columns the cell spans (default: 1)
rowSpan
number
Number of rows the cell spans (default: 1)
width
number
Cell width in pixels
backgroundColor
string
Cell background color

Factory Functions

$createTableNode

Creates a new TableNode.
function $createTableNode(): TableNode

$createTableRowNode

Creates a new TableRowNode.
function $createTableRowNode(height?: number): TableRowNode

$createTableCellNode

Creates a new TableCellNode.
function $createTableCellNode(
  headerState: TableCellHeaderStates,
  colSpan?: number,
  width?: number
): TableCellNode
Example:
import {
  $createTableNode,
  $createTableRowNode,
  $createTableCellNode,
  TableCellHeaderStates
} from '@lexical/table';

editor.update(() => {
  const table = $createTableNode();
  
  // Create header row
  const headerRow = $createTableRowNode();
  const headerCell1 = $createTableCellNode(TableCellHeaderStates.ROW);
  headerCell1.append($createParagraphNode().append($createTextNode('Header 1')));
  const headerCell2 = $createTableCellNode(TableCellHeaderStates.ROW);
  headerCell2.append($createParagraphNode().append($createTextNode('Header 2')));
  headerRow.append(headerCell1, headerCell2);
  
  // Create data row
  const dataRow = $createTableRowNode();
  const dataCell1 = $createTableCellNode(TableCellHeaderStates.NO_STATUS);
  dataCell1.append($createParagraphNode().append($createTextNode('Data 1')));
  const dataCell2 = $createTableCellNode(TableCellHeaderStates.NO_STATUS);
  dataCell2.append($createParagraphNode().append($createTextNode('Data 2')));
  dataRow.append(dataCell1, dataCell2);
  
  table.append(headerRow, dataRow);
  $getRoot().append(table);
});

$createTableNodeWithDimensions

Creates a table with specified dimensions.
function $createTableNodeWithDimensions(
  rowCount: number,
  columnCount: number,
  includeHeaders?: InsertTableCommandPayloadHeaders
): TableNode
rowCount
number
required
Number of rows to create
columnCount
number
required
Number of columns to create
includeHeaders
InsertTableCommandPayloadHeaders
Whether to include header rows/columns: true, false, 'rows', or 'columns'

Type Guards

$isTableNode

function $isTableNode(node: LexicalNode | null | undefined): node is TableNode

$isTableRowNode

function $isTableRowNode(node: LexicalNode | null | undefined): node is TableRowNode

$isTableCellNode

function $isTableCellNode(node: LexicalNode | null | undefined): node is TableCellNode

$isTableSelection

function $isTableSelection(selection: BaseSelection | null): selection is TableSelection

Commands

INSERT_TABLE_COMMAND

Inserts a new table at the current selection.
const INSERT_TABLE_COMMAND: LexicalCommand<InsertTableCommandPayload>
Payload:
rows
number
required
Number of rows
columns
number
required
Number of columns
includeHeaders
InsertTableCommandPayloadHeaders
Header configuration
Example:
editor.dispatchCommand(INSERT_TABLE_COMMAND, {
  rows: 3,
  columns: 3,
  includeHeaders: 'rows'
});

Table Manipulation Utilities

$insertTableRow

Inserts a row in a table.
function $insertTableRow(
  tableNode: TableNode,
  targetIndex: number,
  shouldInsertAfter?: boolean,
  rowCount?: number,
  grid?: TableMapType
): void

$insertTableColumn

Inserts a column in a table.
function $insertTableColumn(
  tableNode: TableNode,
  targetIndex: number,
  shouldInsertAfter?: boolean,
  columnCount?: number,
  grid?: TableMapType
): void

$deleteTableRow

Deletes a table row.
function $deleteTableRowAtSelection(): void

$deleteTableColumn

Deletes a table column.
function $deleteTableColumnAtSelection(): void

$mergeCells

Merges selected table cells.
function $mergeCells(): void

$unmergeCell

Unmerges a merged table cell.
function $unmergeCell(): void

$computeTableMap

Computes the table map for a given table.
function $computeTableMap(
  table: TableNode,
  cellA: TableCellNode,
  cellB: TableCellNode
): [TableMapType, TableMapValueType, TableMapValueType]

Selection Utilities

$createTableSelection

Creates a new table selection.
function $createTableSelection(): TableSelection

$findCellNode

Finds the table cell node from any node.
function $findCellNode(node: LexicalNode): TableCellNode | null

$findTableNode

Finds the table node from any node.
function $findTableNode(node: LexicalNode): TableNode | null

$getTableCellNodeRect

Gets the row and column indices of a cell.
function $getTableCellNodeRect(cell: TableCellNode): {
  rowIndex: number;
  columnIndex: number;
  rowSpan: number;
  colSpan: number;
} | null

Registration

registerTablePlugin

Registers core table functionality.
function registerTablePlugin(
  editor: LexicalEditor,
  config: TableConfig
): () => void

registerTableSelectionObserver

Registers table selection observer.
function registerTableSelectionObserver(
  editor: LexicalEditor,
  hasTabHandler: boolean
): () => void

registerTableCellUnmergeTransform

Registers transform to handle cell unmerging.
function registerTableCellUnmergeTransform(editor: LexicalEditor): () => void

Extension

TableExtension

Bundles all table functionality.
import { createEditor } from 'lexical';
import { TableExtension } from '@lexical/table';

const editor = createEditor({
  extensions: [
    TableExtension.configure({
      hasTabHandler: true,
      hasHorizontalScroll: false
    })
  ]
});
hasTabHandler
boolean
default:"true"
Enable Tab key navigation between cells
hasHorizontalScroll
boolean
default:"false"
Enable horizontal scrolling for wide tables

Table Observer

TableObserver

Observes and manages table DOM state.
class TableObserver {
  constructor(editor: LexicalEditor, tableNodeKey: NodeKey);
  removeListeners(): void;
}

$getTableAndElementByKey

Gets table node and element by key.
function $getTableAndElementByKey(
  nodeKey: NodeKey
): [TableNode, HTMLTableElement] | [null, null]

Types

TableCellHeaderStates

enum TableCellHeaderStates {
  NO_STATUS = 0,
  ROW = 1,
  COLUMN = 2,
  BOTH = 3
}

TableSelection

Custom selection type for table cells.
tableKey
NodeKey
Key of the table node
anchor
PointType
Selection anchor point
focus
PointType
Selection focus point

InsertTableCommandPayload

type InsertTableCommandPayload = {
  columns: number;
  rows: number;
  includeHeaders?: InsertTableCommandPayloadHeaders;
}

type InsertTableCommandPayloadHeaders = true | false | 'rows' | 'columns'

Complete Example

import { createEditor } from 'lexical';
import {
  TableExtension,
  $createTableNodeWithDimensions,
  $insertTableRow,
  $insertTableColumn,
  INSERT_TABLE_COMMAND
} from '@lexical/table';

const editor = createEditor({
  extensions: [
    TableExtension.configure({
      hasTabHandler: true
    })
  ]
});

editor.setRootElement(document.getElementById('editor'));

// Insert table via command
editor.dispatchCommand(INSERT_TABLE_COMMAND, {
  rows: 4,
  columns: 3,
  includeHeaders: 'rows'
});

// Or create programmatically
editor.update(() => {
  const table = $createTableNodeWithDimensions(3, 3, true);
  $getRoot().append(table);
  
  // Add a row
  $insertTableRow(table, 1, true);
  
  // Add a column
  $insertTableColumn(table, 2, true);
});

Build docs developers (and LLMs) love