blokhaus

Tables

Create and edit tables with cell merging, resize, and contextual menus.

Blokhaus includes a full-featured table system built on Lexical's @lexical/table module. It provides cell merging, column resize, contextual right-click menus, and hover actions for adding rows and columns.

Basic setup

Three components work together to provide the complete table experience:

app/editor/page.tsx
"use client";

import {
  EditorRoot,
  TablePlugin,
  TableActionMenu,
  TableHoverActions,
  InputRulePlugin,
  SlashMenu,
} from "@blokhaus/core";

export default function EditorPage() {
  return (
    <EditorRoot namespace="my-editor">
      <InputRulePlugin />
      <SlashMenu />
      <TablePlugin />
      <TableActionMenu />
      <TableHoverActions />
    </EditorRoot>
  );
}
ComponentPurpose
TablePluginRegisters the table node, handles insertion commands, and manages selection/tab behavior
TableActionMenuRight-click context menu for table operations (insert/delete rows/columns, merge cells, etc.)
TableHoverActionsVisual handles on hover: row/column handles, add row/column buttons, column resize, table grip

TablePlugin props

PropTypeDefaultDescription
hasCellMergebooleantrueEnable cell merging/unmerging
hasTabHandlerbooleantrueEnable Tab key to move between cells
<TablePlugin hasCellMerge={true} hasTabHandler={true} />

When hasCellMerge is false, a transform is registered that automatically unmerges any merged cells, effectively disabling the feature.

When hasTabHandler is true, pressing Tab moves focus to the next cell and Shift+Tab moves to the previous cell.

Inserting a table

Via the slash menu

Type /table in the editor. The slash menu shows a "Table" item. Selecting it inserts a default 3x3 table at the cursor position.

Via a command

Use the INSERT_TABLE_COMMAND_BLOKHAUS command to insert a table programmatically:

import { INSERT_TABLE_COMMAND_BLOKHAUS } from "@blokhaus/core";

editor.dispatchCommand(INSERT_TABLE_COMMAND_BLOKHAUS, {
  rows: 4,
  columns: 3,
});

This inserts a table with the specified dimensions after the current block. If the current block is an empty paragraph, it is replaced by the table. The cursor is moved to the first cell of the new table.

TableActionMenu -- right-click context menu

Right-clicking inside a table cell opens a context menu with the following actions:

ActionDescription
Insert row aboveAdds a new row above the current row
Insert row belowAdds a new row below the current row
Insert column leftAdds a new column to the left of the current column
Insert column rightAdds a new column to the right of the current column
Delete rowRemoves the current row
Delete columnRemoves the current column
Merge cellsMerges selected cells (when multiple cells are selected)
Unmerge cellsSplits a previously merged cell back into individual cells
Cell backgroundOpens a color picker to set the background color of selected cells
Delete tableRemoves the entire table

Cell background colors

The cell background picker uses tokens from tokens.css:

ColorToken
Default (clear)--
Gray--blokhaus-cell-bg-gray
Brown--blokhaus-cell-bg-brown
Orange--blokhaus-cell-bg-orange
Yellow--blokhaus-cell-bg-yellow
Green--blokhaus-cell-bg-green
Blue--blokhaus-cell-bg-blue
Purple--blokhaus-cell-bg-purple
Pink--blokhaus-cell-bg-pink
Red--blokhaus-cell-bg-red

TableHoverActions -- visual handles

When the user hovers over a table, several interactive handles appear:

Row handles

Small handles appear to the left of each row. Hovering over a row handle highlights the row and provides a click target for row-level operations.

Column handles

Handles appear above each column, providing click targets for column-level operations.

Add row/column buttons

A "+" button appears below the last row and to the right of the last column. Clicking these buttons appends a new row or column to the table.

Column resize

Dragging the border between column handles resizes columns. A visual guide line follows the cursor during the drag, and the column width is updated on drop.

Table grip

A small grip icon appears at the top-left corner of the table. Clicking it selects the entire table (as a NodeSelection), which allows the table to be deleted with Backspace/Delete.

Table selection

Lexical's table module provides a built-in cell selection system. Click and drag across cells to select a range. The selected cells are highlighted with the --blokhaus-table-selection overlay color.

When multiple cells are selected:

  • The "Merge cells" action becomes available in the context menu.
  • The "Cell background" action applies to all selected cells.

Keyboard navigation

KeyBehavior
TabMove to the next cell (left to right, top to bottom)
Shift + TabMove to the previous cell
Arrow keysNavigate between cells
Backspace / Delete (when table is selected)Delete the entire table

Table tokens

The table appearance is controlled by CSS custom properties:

:root {
  --blokhaus-table-border: hsl(214.3 31.8% 91.4%);
  --blokhaus-table-header-bg: hsl(210 40% 96.1%);
  --blokhaus-table-selection: hsla(221.2 83.2% 53.3% / 0.1);
  --blokhaus-table-resize-handle: hsl(221.2 83.2% 53.3%);
}

Override these in your theme to customize the table appearance. See the Theming guide for details.

Example: Read-only table with custom data

import { $createTableNodeWithDimensions } from "@lexical/table";

editor.update(() => {
  // Create a 3-column, 2-row table
  const table = $createTableNodeWithDimensions(2, 3, false);

  const root = $getRoot();
  root.append(table);
});

Deleting a table

Tables can be deleted in several ways:

  • Keyboard: Select the table (click the table grip or use NodeSelection), then press Backspace or Delete.
  • Context menu: Right-click inside the table and select "Delete table".
  • Programmatically: Remove the table node via editor.update().

When a table is deleted, a new empty paragraph is inserted at its position to prevent the editor from becoming empty.