Table Node

Available in Start plan

An enhanced table node component for Tiptap editors with table handles for row and column manipulation, as well as cell alignment controls. It includes add, delete, move, and duplicate functionality, along with advanced table management capabilities, responsive styling, and accessibility features.

Installation

Add the component via the Tiptap CLI:

npx @tiptap/cli@latest add table-node

Usage

Basic Integration

To add table functionality to your Tiptap editor, follow these steps:

1. Import the required extensions and components:

import { Highlight } from '@tiptap/extension-highlight'
import { TextStyle } from '@tiptap/extension-text-style'

import { NodeBackground } from '@/components/tiptap-extension/node-background-extension'
import { NodeAlignment } from '@/components/tiptap-extension/node-alignment-extension'

import { TableKit } from '@/components/tiptap-node/table-node/extensions/table-node-extension'
import { TableHandleExtension } from '@/components/tiptap-node/table-node/extensions/table-handle'

import { TableTriggerButton } from '@/components/tiptap-node/table-node/ui/table-trigger-button'
import { TableHandle } from '@/components/tiptap-node/table-node/ui/table-handle/table-handle'
import { TableSelectionOverlay } from '@/components/tiptap-node/table-node/ui/table-selection-overlay'
import { TableCellHandleMenu } from '@/components/tiptap-node/table-node/ui/table-cell-handle-menu'
import { TableExtendRowColumnButtons } from '@/components/tiptap-node/table-node/ui/table-extend-row-column-button'

// Import required styles
import '@/components/tiptap-node/table-node/styles/prosemirror-table.scss'
import '@/components/tiptap-node/table-node/styles/table-node.scss'

2. Add extensions to your editor configuration:

const editor = useEditor({
  extensions: [
    StarterKit,
    TableKit.configure({
      table: {
        resizable: true, // Enable column resizing
      },
    }),
    TableHandleExtension, // Required for row/column manipulation
    NodeAlignment, // For cell alignment
    NodeBackground, // For cell background colors
    TextStyle, // For text styling
    Highlight.configure({ multicolor: true }), // For highlighting
  ],
  content: '<p>Your content here</p>',
})

3. Add the table components to your editor:

<EditorContext.Provider value={{ editor }}>
  {/* Add table button to your toolbar */}
  <div className="toolbar">
    <TableTriggerButton />
  </div>

  <EditorContent editor={editor} />

  {/* Add these components outside EditorContent for proper positioning */}
  <TableHandle />
  <TableSelectionOverlay
    showResizeHandles={true}
    cellMenu={(props) => (
      <TableCellHandleMenu
        editor={props.editor}
        onMouseDown={(e) => props.onResizeStart?.('br')(e)}
      />
    )}
  />
  <TableExtendRowColumnButtons />
</EditorContext.Provider>

That's it! Your editor now has full table functionality including:

  • Visual table insertion
  • Row/column manipulation via handles
  • Cell selection and resizing
  • Context menus for table operations

For a complete example with all features, see the TableNode component example below.

Creating Tables

<TableTriggerButton />

The primary component for inserting tables into your editor. This button opens an interactive grid selector that allows users to visually choose the dimensions of their table before inserting it.

Key Features

  • Visual Grid Selector: Interactive grid interface for selecting table dimensions
  • Quick Insertion: Click to instantly create tables up to 8×8 (customizable)
  • Accessible: Full keyboard navigation and ARIA labels

Usage

import { TableTriggerButton } from '@/components/tiptap-node/table-node/ui/table-trigger-button'

function MyToolbar() {
  return (
    <TableTriggerButton
      editor={editor}
      maxRows={8}
      maxCols={8}
      hideWhenUnavailable={true}
      text="Insert Table"
      onInserted={(rows, cols) => console.log(`Inserted ${rows}×${cols} table`)}
    />
  )
}

Props

  • editor?: Editor | null - Editor instance
  • hideWhenUnavailable?: boolean - Hide when table insertion is not available (default: false)
  • maxRows?: number - Maximum number of rows in the grid selector (default: 8)
  • maxCols?: number - Maximum number of columns in the grid selector (default: 8)
  • onInserted?: (rows: number, cols: number) => void - Callback after successful insertion
  • text?: string - Optional text to display alongside the icon
  • All standard button props (className, disabled, etc.)

Custom Implementation with Hook

For custom button implementations, use the useTableTriggerButton hook:

import { useTableTriggerButton } from '@/components/tiptap-node/table-node/ui/table-trigger-button'

function CustomTableButton() {
  const {
    isVisible,
    canInsert,
    isOpen,
    setIsOpen,
    hoveredCell,
    handleCellHover,
    handleCellClick,
    resetHoveredCell,
    label,
    Icon,
  } = useTableTriggerButton({
    editor,
    hideWhenUnavailable: true,
    maxRows: 10,
    maxCols: 10,
    onInserted: (rows, cols) => {
      console.log(`Created ${rows}×${cols} table`)
    },
  })

  if (!isVisible) return null

  return (
    <button onClick={() => setIsOpen(true)} disabled={!canInsert}>
      <Icon /> {label}
    </button>
  )
}

Components

<TableNode />

An enhanced table node component with table handles, row/column manipulation, and advanced management features.

Usage

import { useEditor, EditorContent, EditorContext } from '@tiptap/react'
import { StarterKit } from '@tiptap/starter-kit'
import { TextStyle } from '@tiptap/extension-text-style'
import { Highlight } from '@tiptap/extension-highlight'

import { NodeBackground } from '@/components/tiptap-extension/node-background-extension'
import { NodeAlignment } from '@/components/tiptap-extension/node-alignment-extension'

import { ButtonGroup } from '@/components/tiptap-ui-primitive/button'

import { TableTriggerButton } from '@/components/tiptap-node/table-node/ui/table-trigger-button'
import { TableKit } from '@/components/tiptap-node/table-node/extensions/table-node-extension'
import { TableHandleExtension } from '@/components/tiptap-node/table-node/extensions/table-handle'
import { TableHandle } from '@/components/tiptap-node/table-node/ui/table-handle/table-handle'
import { TableSelectionOverlay } from '@/components/tiptap-node/table-node/ui/table-selection-overlay'
import { TableCellHandleMenu } from '@/components/tiptap-node/table-node/ui/table-cell-handle-menu'
import { TableExtendRowColumnButtons } from '@/components/tiptap-node/table-node/ui/table-extend-row-column-button'
import '@/components/tiptap-node/table-node/styles/prosemirror-table.scss'
import '@/components/tiptap-node/table-node/styles/table-node.scss'

// --- Tiptap Node Styles ---
import '@/components/tiptap-node/heading-node/heading-node.scss'
import '@/components/tiptap-node/paragraph-node/paragraph-node.scss'

export default function BasicEditor() {
  const editor = useEditor({
    immediatelyRender: false,
    extensions: [
      StarterKit,
      TableKit.configure({
        table: {
          resizable: true,
        },
      }),
      TableHandleExtension,
      NodeBackground,
      NodeAlignment,
      TextStyle,
      Highlight.configure({ multicolor: true }),
    ],
    content: `
      <h2>Table Node Demo</h2>
      <p>
        This demo shows the table functionality with all features enabled.
      </p>

      <table>
        <tbody>
          <tr>
            <th>
              <p><strong>Name</strong></p>
            </th>
            <th>
              <p><strong>Role</strong></p>
            </th>
            <th>
              <p><strong>Department</strong></p>
            </th>
          </tr>
          <tr>
            <td>
              <p>Alice Johnson</p>
            </td>
            <td>
              <p>Senior Developer</p>
            </td>
            <td>
              <p>Engineering</p>
            </td>
          </tr>
          <tr>
            <td>
              <p>Bob Smith</p>
            </td>
            <td>
              <p>Product Manager</p>
            </td>
            <td>
              <p>Product</p>
            </td>
          </tr>
          <tr>
            <td>
              <p>Carol White</p>
            </td>
            <td>
              <p>UX Designer</p>
            </td>
            <td>
              <p>Design</p>
            </td>
          </tr>
        </tbody>
      </table>

      <p>
        You can click inside the table to see the selection overlay and resize handles. Use the extend buttons to add more rows or columns.
      </p>
    `,
  })

  return (
    <EditorContext.Provider value={{ editor }}>
      <div className="controls-bar">
        <div className="control-item">
          <ButtonGroup orientation="horizontal">
            <TableTriggerButton />
          </ButtonGroup>
        </div>
      </div>

      <EditorContent editor={editor} role="presentation" className="control-showcase" />

      <TableHandle />
      <TableSelectionOverlay
        showResizeHandles={true}
        cellMenu={(props) => (
          <TableCellHandleMenu
            editor={props.editor}
            onMouseDown={(e) => props.onResizeStart?.('br')(e)}
          />
        )}
      />
      <TableExtendRowColumnButtons />
    </EditorContext.Provider>
  )
}

Extensions

TableKit

A comprehensive table extension kit that bundles multiple table-related extensions together.

Table Manipulation Components

Each component is available as both a React component and a composable hook:

Row/Column Manipulation Components

<TableAddRowColumnButton /> / useTableAddRowColumn()

Add rows or columns to the table at specific positions.

Props:

  • editor?: Editor | null - Editor instance
  • index?: number - Row/column index
  • orientation?: Orientation - "row" or "column"
  • direction?: "before" | "after" - Add before or after the index
  • tablePos?: number - Position of the table in the document
  • hideWhenUnavailable?: boolean - Hide when action unavailable
  • onAdded?: () => void - Callback after successful addition

Usage:

import { TableAddRowColumnButton } from '@/components/tiptap-node/table-node/ui/table-add-row-column-button'

function MyToolbar() {
  return <TableAddRowColumnButton orientation="row" direction="after" />
}

<TableDeleteRowColumnButton /> / useTableDeleteRowColumn()

Delete rows or columns from the table.

Props:

  • editor?: Editor | null
  • index?: number
  • orientation?: Orientation
  • tablePos?: number
  • hideWhenUnavailable?: boolean
  • onDeleted?: () => void

Usage:

import { TableDeleteRowColumnButton } from '@/components/tiptap-node/table-node/ui/table-delete-row-column-button'

function MyToolbar() {
  return <TableDeleteRowColumnButton orientation="column" />
}

<TableMoveRowColumnButton /> / useTableMoveRowColumn()

Move rows or columns up/down or left/right within the table.

Props:

  • editor?: Editor | null
  • index?: number
  • orientation?: Orientation
  • direction?: "up" | "down" | "left" | "right"
  • tablePos?: number
  • hideWhenUnavailable?: boolean
  • onMoved?: () => void

Usage:

import { TableMoveRowColumnButton } from '@/components/tiptap-node/table-node/ui/table-move-row-column-button'

function MyToolbar() {
  return <TableMoveRowColumnButton orientation="row" direction="up" />
}

<TableDuplicateRowColumnButton /> / useTableDuplicateRowColumn()

Duplicate rows or columns, preserving content and formatting.

Props:

  • editor?: Editor | null
  • index?: number
  • orientation?: Orientation
  • tablePos?: number
  • hideWhenUnavailable?: boolean
  • onDuplicated?: () => void

Usage:

import { TableDuplicateRowColumnButton } from '@/components/tiptap-node/table-node/ui/table-duplicate-row-column-button'

function MyToolbar() {
  return <TableDuplicateRowColumnButton orientation="row" />
}

<TableSortRowColumnButton /> / useTableSortRowColumn()

Sort rows or columns alphabetically (A-Z or Z-A). Headers are automatically excluded from sorting and remain in their original positions. Empty cells are always sorted to the end.

Props:

  • editor?: Editor | null
  • index?: number
  • orientation?: Orientation
  • direction: "asc" | "desc" - Sort direction
  • tablePos?: number
  • hideWhenUnavailable?: boolean
  • onSorted?: () => void

Usage:

import { TableSortRowColumnButton } from '@/components/tiptap-node/table-node/ui/table-sort-row-column-button'

function MyToolbar() {
  return <TableSortRowColumnButton orientation="column" direction="asc" />
}

Cell Manipulation Components

<TableMergeSplitCellButton /> / useTableMergeSplitCell()

Merge multiple selected cells or split a merged cell.

Props:

  • editor?: Editor | null
  • action: "merge" | "split" - Action to perform
  • hideWhenUnavailable?: boolean
  • onExecuted?: (action: "merge" | "split") => void

Usage:

import { TableMergeSplitCellButton } from '@/components/tiptap-node/table-node/ui/table-merge-split-cell-button'

function MyToolbar() {
  return (
    <>
      <TableMergeSplitCellButton action="merge" />
      <TableMergeSplitCellButton action="split" />
    </>
  )
}

<TableAlignCellButton /> / useTableAlignCell()

Align cell content (text alignment and vertical alignment).

Props:

  • editor?: Editor | null
  • alignment: string - Alignment value (e.g., "left", "center", "right", "top", "middle", "bottom")
  • type: "textAlign" | "verticalAlign" - Alignment type
  • hideWhenUnavailable?: boolean
  • onAligned?: () => void

Usage:

import { TableAlignCellButton } from '@/components/tiptap-node/table-node/ui/table-align-cell-button'

function MyToolbar() {
  return (
    <>
      <TableAlignCellButton type="textAlign" alignment="center" />
      <TableAlignCellButton type="verticalAlign" alignment="middle" />
    </>
  )
}

<TableClearRowColumnContentButton /> / useTableClearRowColumnContent()

Clear content from cells in a row, column, or selection.

Props:

  • editor?: Editor | null
  • index?: number
  • orientation?: Orientation
  • tablePos?: number
  • resetAttrs?: boolean - Also reset cell attributes (colors, alignment, etc.)
  • hideWhenUnavailable?: boolean
  • onCleared?: () => void

Usage:

import { TableClearRowColumnContentButton } from '@/components/tiptap-node/table-node/ui/table-clear-row-column-content-button'

function MyToolbar() {
  return <TableClearRowColumnContentButton orientation="row" resetAttrs={true} />
}

Header and Layout Components

<TableHeaderRowColumnButton /> / useTableHeaderRowColumn()

Toggle the first row or column as a header.

Props:

  • editor?: Editor | null
  • index?: number - Should be 0 for first row/column
  • orientation?: Orientation
  • tablePos?: number
  • hideWhenUnavailable?: boolean
  • onToggled?: () => void

Usage:

import { TableHeaderRowColumnButton } from '@/components/tiptap-node/table-node/ui/table-header-row-column-button'

function MyToolbar() {
  return <TableHeaderRowColumnButton index={0} orientation="row" />
}

<TableFitToWidthButton /> / useTableFitToWidth()

Automatically adjust table column widths to fit the editor's container width.

Props:

  • editor?: Editor | null
  • hideWhenUnavailable?: boolean
  • onWidthAdjusted?: () => void

Usage:

import { TableFitToWidthButton } from '@/components/tiptap-node/table-node/ui/table-fit-to-width-button'

function MyToolbar() {
  return <TableFitToWidthButton />
}

<TableAlignmentMenu />

Menu component providing alignment options for table cells.

Props:

  • index?: number
  • orientation?: Orientation

Usage:

import { TableAlignmentMenu } from '@/components/tiptap-node/table-node/ui/table-alignment-menu'

function MyToolbar() {
  return <TableAlignmentMenu orientation="row" index={0} />
}

Using Hooks Directly

All button components have corresponding hooks that can be used to build custom UI:

import { useTableDeleteRowColumn } from '@/components/tiptap-node/table-node/ui/table-delete-row-column-button'

function CustomDeleteButton() {
  const { isVisible, canDeleteRowColumn, handleDelete, label, Icon } = useTableDeleteRowColumn({
    orientation: 'row',
    hideWhenUnavailable: true,
  })

  if (!isVisible) return null

  return (
    <button onClick={handleDelete} disabled={!canDeleteRowColumn}>
      <Icon /> {label}
    </button>
  )
}

Each hook returns:

  • isVisible: boolean - Whether the action should be shown
  • can[Action]: boolean - Whether the action can be performed
  • handle[Action]: () => boolean - Execute the action
  • label: string - UI label for the action
  • Icon: ComponentType - Icon component for the action

Styling

The table node requires two stylesheets:

import '@/components/tiptap-node/table-node/styles/prosemirror-table.scss'
import '@/components/tiptap-node/table-node/styles/table-node.scss'

CSS Variables

The table node uses CSS variables for theming:

:root {
  --tt-table-border-color: var(--tt-gray-light-a-300);
  --tt-table-selected-bg: rgba(195, 189, 255, 0.4);
  --tt-table-selected-stroke: var(--tt-brand-color-400);
  --tt-table-column-resize-handle-bg: var(--tt-brand-color-400);
  --tt-table-cell-padding: 0.5rem;
  --tt-table-margin-block: 1.25rem;
  --tt-table-pad-block-start: 1rem;
  --tt-table-pad-block-end: 1.5rem;
  --tt-table-pad-inline-start: 1rem;
  --tt-table-pad-inline-end: 1.5rem;
  --tt-table-handle-bg-color: var(--tt-gray-light-a-100);
  --tt-table-extend-icon-color: var(--tt-gray-light-a-400);
}

Dark mode variants are automatically applied when using the .dark class.

Dependencies

Required Packages

  • @tiptap/extension-table - Core table extensions (Table, TableRow, TableCell, TableHeader)
  • @tiptap/pm/tables - ProseMirror table utilities
  • @tiptap/pm/state - ProseMirror state management
  • @tiptap/pm/view - ProseMirror view utilities
  • @tiptap/react - React integration
  • @floating-ui/react - Floating UI for positioning
  • sass / sass-embedded - For SCSS compilation

Required Extensions

  • table-handle-extension - Table handle extension for row/column manipulation context

Referenced Components

The table-node component internally uses:

  • use-tiptap-editor (hook)
  • tiptap-utils (lib)
  • tiptap-table-utils (lib)
  • Various table manipulation components and utilities

Features

  • Table Creation: Visual grid selector for quick table insertion
  • Row/Column Manipulation: Add, delete, move, and duplicate rows/columns
  • Drag-and-Drop: Reorder rows and columns by dragging handles
  • Cell Operations: Merge, split, and clear cell content
  • Alignment Controls: Text and vertical alignment for cells
  • Column Resizing: Interactive column width adjustment
  • Selection Overlay: Visual feedback for selected cells
  • Keyboard Shortcuts: Efficient keyboard navigation
  • Responsive Design: Works on desktop and mobile devices
  • Dark Mode Support: Built-in dark mode theming
  • Accessibility: ARIA labels and keyboard navigation
  • Node Alignment Extension - Required for cell alignment support
  • UniqueID Extension - Optional, used in the example template for giving tables unique IDs (also used for other block-level nodes)
  • Color Extension - For cell background colors
  • TextStyle Extension - For text styling within cells