Table Node
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-nodeUsage
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 instancehideWhenUnavailable?: 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 insertiontext?: 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 instanceindex?: number- Row/column indexorientation?: Orientation- "row" or "column"direction?: "before" | "after"- Add before or after the indextablePos?: number- Position of the table in the documenthideWhenUnavailable?: boolean- Hide when action unavailableonAdded?: () => 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 | nullindex?: numberorientation?: OrientationtablePos?: numberhideWhenUnavailable?: booleanonDeleted?: () => 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 | nullindex?: numberorientation?: Orientationdirection?: "up" | "down" | "left" | "right"tablePos?: numberhideWhenUnavailable?: booleanonMoved?: () => 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 | nullindex?: numberorientation?: OrientationtablePos?: numberhideWhenUnavailable?: booleanonDuplicated?: () => 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 | nullindex?: numberorientation?: Orientationdirection: "asc" | "desc"- Sort directiontablePos?: numberhideWhenUnavailable?: booleanonSorted?: () => 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 | nullaction: "merge" | "split"- Action to performhideWhenUnavailable?: booleanonExecuted?: (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 | nullalignment: string- Alignment value (e.g., "left", "center", "right", "top", "middle", "bottom")type: "textAlign" | "verticalAlign"- Alignment typehideWhenUnavailable?: booleanonAligned?: () => 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 | nullindex?: numberorientation?: OrientationtablePos?: numberresetAttrs?: boolean- Also reset cell attributes (colors, alignment, etc.)hideWhenUnavailable?: booleanonCleared?: () => 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 | nullindex?: number- Should be 0 for first row/columnorientation?: OrientationtablePos?: numberhideWhenUnavailable?: booleanonToggled?: () => 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 | nullhideWhenUnavailable?: booleanonWidthAdjusted?: () => void
Usage:
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?: numberorientation?: 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 showncan[Action]: boolean- Whether the action can be performedhandle[Action]: () => boolean- Execute the actionlabel: string- UI label for the actionIcon: 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 positioningsass/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