---
title: "Table Node"
description: "Add a table node UI component to your Tiptap editor. More in the documentation!"
canonical_url: "https://tiptap.dev/docs/ui-components/node-components/table-node"
---

# Table Node

Add a table node UI component to your Tiptap editor. More in the documentation!

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.

> **Interactive demo:** [table node](https://template.tiptap.dev/preview/tiptap-node/table-node)

## Installation

Add the component via the Tiptap CLI:

```bash
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:**

```tsx
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:**

```tsx
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:**

```tsx
<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](#lesstablenode-greater) 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

```tsx
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:

```tsx
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

```tsx
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:**

```tsx
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:**

```tsx
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:**

```tsx
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:**

```tsx
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:**

```tsx
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:**

```tsx
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:**

```tsx
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:**

```tsx
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:**

```tsx
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:**

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

function MyToolbar() {
  return <TableFitToWidthButton />
}
```

### Menu Components

#### `<TableAlignmentMenu />`

Menu component providing alignment options for table cells.

**Props:**

- `index?: number`
- `orientation?: Orientation`

**Usage:**

```tsx
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:

```tsx
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:

```tsx
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:

```css
: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

## Related Extensions

- **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
