Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/content/docs/react/styling-theming/overriding-css.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ BlockNote uses classes with the `bn-` prefix to style editor elements. Here are
- `.bn-drag-handle-menu`: Drag handle menu.
- `.bn-suggestion-menu`: Suggestion menu.

#### Table Row & Column Dragging

- `.bn-table-handle`: Row & column drag handles.
- `.bn-table-drag-source-row` / `.bn-table-drag-source-col`: Every cell of the row/column being dragged.
- `.bn-table-drop-cursor`: Bar marking the edge the row/column would be dropped at.
- `.bn-table-drag-preview`: Snapshot of the row/column shown next to the cursor. Rendered outside the editor, so selectors scoped to `.bn-editor` won't match it.

### BlockNote CSS Attributes

BlockNote uses data attributes to target specific block types and properties:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"playground": true,
"docs": false,
"author": "must",
"tags": [
"Intermediate",
"UI Components",
"Tables",
"Drag & Drop",
"Appearance & Styling"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Table Reordering Visualization

BlockNote gives table row/column dragging visual feedback out of the box: a
snapshot of the row/column follows the cursor, the row/column being dragged is
tinted and outlined, and a drop indicator marks where it would land.

This example shows how to restyle a table - and those built-in drag
affordances - to match your own product, using a Microsoft Loop-inspired look:

- **Restyled tables**: rounded card look, muted header row, hairline
borders, and a row-hover highlight instead of a harsh black grid.
- **Retuned drag affordances**: the built-in drag source highlight, drop
indicator and drag snapshot recolored to the same palette.
- **Header row by default**: the `/table` command starts new tables with
a header row already enabled, so the header styling is visible right away.

## How It Works

Everything here is CSS plus one slash-menu tweak - no extensions, no event
handling. BlockNote's `TableHandlesExtension` owns the whole drag lifecycle and
exposes it through classes you can target:
Comment on lines +19 to +21

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the extension wording.

TableHandlesExtension provides the built-in behavior. “No extensions” is incorrect. State that the example requires no additional extensions or event handling.

  • examples/03-ui-components/21-table-reordering-visualization/README.md#L19-L21: replace “no extensions” with “no additional extensions”.
  • playground/src/examples.gen.tsx#L918-L918: regenerate this generated file after correcting the source README.
📍 Affects 2 files
  • examples/03-ui-components/21-table-reordering-visualization/README.md#L19-L21 (this comment)
  • playground/src/examples.gen.tsx#L918-L918
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/03-ui-components/21-table-reordering-visualization/README.md` around
lines 19 - 21, Update the README wording around TableHandlesExtension to say the
example requires no additional extensions or event handling. Regenerate
playground/src/examples.gen.tsx so its generated example text reflects the
corrected README wording at the cited range.


| Class | What it's on |
| -------------------------- | ---------------------------------------------- |
| `bn-table-drag-source-row` | every cell of the row being dragged |
| `bn-table-drag-source-col` | every cell of the column being dragged |
| `bn-table-drop-cursor` | a bar on the edge the row/column would drop at |
| `bn-table-drag-preview` | the snapshot shown next to the cursor |

The first three are ProseMirror decorations inside the editor, so they're
scoped under `.bn-editor [data-content-type="table"]` like any other table
style. `bn-table-drag-preview` is different: it's appended outside the editor
(the browser can only use an attached element as a drag image), so it has to be
styled through its own class rather than through the table selectors.

`tableStyles.css` does the restyling; `App.tsx` overrides the default `/table`
slash-menu item so new tables start with `headerRows: 1`.

## Known Limitations

- **Keyboard and touch**: BlockNote's table drag handles are `draggable` +
`onDragStart` only today (see `TableHandle.tsx`) - there's no
keyboard-operable reorder path, and native HTML5 drag-and-drop isn't
supported on touch browsers at all. Both are gaps in BlockNote's table-drag
feature as a whole, not something this example introduces or fixes.
- **Accessibility**: for the same reason, there's no keyboard focus
restoration to verify after a reorder - the interaction can't be reached by
keyboard in the first place yet.

**Relevant Docs:**

- [Tables](/docs/features/blocks/tables)
- [Overriding CSS](/docs/react/styling-theming/overriding-css)
- [Editor Setup](/docs/getting-started/editor-setup)
- [Slash Menu](/docs/react/components/suggestion-menus)
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<html lang="en">

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add the HTML doctype.

The missing doctype can enable quirks mode. Add <!doctype html> before the <html> element.

Proposed fix
+<!doctype html>
 <html lang="en">
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<html lang="en">
<!doctype html>
<html lang="en">
🧰 Tools
🪛 HTMLHint (1.9.2)

[error] 1-1: Doctype must be declared before any non-comment content.

(doctype-first)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/03-ui-components/21-table-reordering-visualization/index.html` at
line 1, Update the document header before the html element to include the
standard HTML5 doctype declaration, ensuring the page does not enter quirks
mode.

Source: Linters/SAST tools

<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Table Reordering Visualization</title>
<script>
<!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -->
</script>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY
import React from "react";
import { createRoot } from "react-dom/client";
import App from "./src/App.jsx";

const root = createRoot(document.getElementById("root")!);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>,
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "@blocknote/example-ui-components-table-reordering-visualization",
"description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY",
"type": "module",
"private": true,
"version": "0.12.4",
"scripts": {
"start": "vp dev",
"dev": "vp dev",
"build:prod": "tsc && vp build",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Replace tsc with the supported type-check command.

Line 10 invokes tsc directly. Use vp run lint before vp build.

Proposed fix
-    "build:prod": "tsc && vp build",
+    "build:prod": "vp run lint && vp build",

As per coding guidelines, use only vp or pnpm commands and use vp run lint for linting and type-checking; do not use tsc or prettier.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"build:prod": "tsc && vp build",
"build:prod": "vp run lint && vp build",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/03-ui-components/21-table-reordering-visualization/package.json` at
line 10, Update the build:prod script to replace the direct tsc invocation with
vp run lint, preserving the subsequent vp build command and using only supported
vp commands for linting and type-checking.

Source: Coding guidelines

"preview": "vp preview"
},
"dependencies": {
"@blocknote/ariakit": "latest",
"@blocknote/core": "latest",
"@blocknote/mantine": "latest",
"@blocknote/react": "latest",
"@blocknote/shadcn": "latest",
"@mantine/core": "^9.0.2",
"@mantine/hooks": "^9.0.2",
"react": "^19.2.3",
"react-dom": "^19.2.3"
},
"devDependencies": {
"@types/react": "^19.2.3",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1",
"vite-plus": "catalog:"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { BlockNoteEditor } from "@blocknote/core";
import {
filterSuggestionItems,
insertOrUpdateBlockForSlashMenu,
} from "@blocknote/core/extensions";
import "@blocknote/core/fonts/inter.css";
import { BlockNoteView } from "@blocknote/mantine";
import "@blocknote/mantine/style.css";
import {
DefaultReactSuggestionItem,
getDefaultReactSlashMenuItems,
SuggestionMenuController,
useCreateBlockNote,
} from "@blocknote/react";

import "./tableStyles.css";

// BlockNote's stock "/table" item inserts a table with no header row, so it
// never picks up the header styling until someone manually toggles it on.
// This swaps in a version that starts with `headerRows: 1` instead.
const getCustomSlashMenuItems = (
editor: BlockNoteEditor<any, any, any>,
): DefaultReactSuggestionItem[] =>
getDefaultReactSlashMenuItems(editor).map((item) => {
// `key` is typed away on the React item (it's reserved for JSX), but the
// underlying object - built from the same items core uses - still has it.
const key = (item as unknown as { key?: string }).key;
if (key !== "table") {
return item;
}
return {
...item,
onItemClick: () =>
insertOrUpdateBlockForSlashMenu(editor, {
type: "table",
content: {
type: "tableContent",
headerRows: 1,
rows: [{ cells: ["", "", ""] }, { cells: ["", "", ""] }],
} as any,
}),
};
});

export default function App() {
const editor = useCreateBlockNote({
tables: {
splitCells: true,
cellBackgroundColor: true,
cellTextColor: true,
headers: true,
},
initialContent: [
{
type: "heading",
props: { level: 2 },
content: "Restyling BlockNote.js Table Reordering",
},
{
type: "table",
content: {
type: "tableContent",
columnWidths: [180, 140, 140, 220],
headerRows: 1,
rows: [
{ cells: ["Column A", "Column B", "Column C", "Column D"] },
{ cells: ["1a", "1b", "1c", "1d"] },
{ cells: ["2a", "2b", "2c", "2d"] },
{ cells: ["3a", "3b", "3c", "3d"] },
],
},
},
],
});

return (
<BlockNoteView editor={editor} slashMenu={false}>
<SuggestionMenuController
triggerCharacter="/"
getItems={async (query) =>
filterSuggestionItems(getCustomSlashMenuItems(editor), query)
}
/>
</BlockNoteView>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/**
* Palette
*
* Declared on `.bn-root` rather than on the table, for two reasons: the drag
* handles render in a portal outside the editor, and so does the drag
* snapshot - BlockNote puts `bn-root` and the active color scheme on both, so
* anything defined here reaches them.
*/
.bn-root {
--table-border: #e2e2ea;
--table-shadow: rgb(0 0 0 / 6%);
--table-header-bg: #f0f0f3;
--table-header-text: #5d5d70;
--table-row-hover: #d3d4e0;
--table-accent: #4a48b8;
--table-handle-hover-bg: #eef1fa;
--table-handle-hover-text: #5e5cd0;
/* Kept translucent so they read as a wash over the cell's own background
rather than replacing it - a flat fill light enough for this palette would
blot out the text in dark mode. */
--table-drag-tint: rgb(94 92 208 / 14%);
--table-drag-outline: rgb(94 92 208 / 45%);
--table-selected: rgb(94 92 208 / 18%);
}

.bn-root[data-color-scheme="dark"] {
--table-border: #3b3b46;
--table-shadow: rgb(0 0 0 / 40%);
--table-header-bg: #2c2c35;
--table-header-text: #a6a6bd;
--table-row-hover: #35353f;
/* The light-mode accent is a dark purple, which all but disappears against
the dark editor background. */
--table-accent: #9391ff;
--table-handle-hover-bg: #35354a;
--table-handle-hover-text: #b3b1ff;
--table-drag-tint: rgb(147 145 255 / 22%);
--table-drag-outline: rgb(147 145 255 / 55%);
--table-selected: rgb(147 145 255 / 25%);
}

/**
* Tables
* Loop/Notion-style card look: rounded outer border, muted header row,
* hairline internal grid and a hover highlight instead of the default
* harsh black grid lines.
*/
.bn-editor [data-content-type="table"] table {
border-collapse: separate;
border-spacing: 0;
border: 1px solid var(--table-border);
border-radius: 8px;
box-shadow: 0 1px 3px var(--table-shadow);
overflow: hidden;
}
.bn-editor [data-content-type="table"] th,
.bn-editor [data-content-type="table"] td {
border: none;
border-right: 1px solid var(--table-border);
border-bottom: 1px solid var(--table-border);
padding: 10px 16px;
transition: background-color 0.15s ease;
}
.bn-editor [data-content-type="table"] th:last-child,
.bn-editor [data-content-type="table"] td:last-child {
border-right: none;
}
.bn-editor [data-content-type="table"] tr:last-child th,
.bn-editor [data-content-type="table"] tr:last-child td {
border-bottom: none;
}
.bn-editor [data-content-type="table"] th {
background-color: var(--table-header-bg);
color: var(--table-header-text);
font-weight: 600;
font-size: 0.8125em;
letter-spacing: 0.01em;
}
.bn-editor [data-content-type="table"] tr:hover > td {
background-color: var(--table-row-hover);
}
.bn-editor [data-content-type="table"] .selectedCell:after {
background: var(--table-selected);
opacity: 1;
}

/**
* Row/column reordering: BlockNote already highlights the row/column being
* dragged and marks the drop position; these just retune the built-in
* affordances to the palette above, so the drag state stays distinguishable
* from this table's own hover and selection colors.
*/
.bn-editor [data-content-type="table"] td.bn-table-drag-source-row,
.bn-editor [data-content-type="table"] th.bn-table-drag-source-row,
.bn-editor [data-content-type="table"] td.bn-table-drag-source-col,
.bn-editor [data-content-type="table"] th.bn-table-drag-source-col {
background-color: var(--table-drag-tint);
outline-color: var(--table-drag-outline);
}
.bn-editor [data-content-type="table"] .bn-table-drop-cursor {
background-color: var(--table-accent);
}

/* The drag snapshot is rendered outside `.bn-editor`, so it's styled through
its own class rather than the table selectors above. */
.bn-table-drag-preview th,
.bn-table-drag-preview td {
border-color: var(--table-accent);
padding: 10px 16px;
}

/* Row/column drag handles and add-row/add-column buttons. */
.bn-mantine .bn-table-handle,
.bn-mantine .bn-table-cell-handle {
border-radius: 2px;
}
.bn-mantine .bn-table-handle:hover,
.bn-mantine .bn-table-handle-dragging,
.bn-mantine .bn-table-cell-handle:hover,
.bn-mantine .bn-extend-button:hover,
.bn-mantine .bn-extend-button-editing {
background-color: var(--table-handle-hover-bg);
color: var(--table-handle-hover-text);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="vite-plus/client" />
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY",
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"composite": true
},
"include": ["."],
"__ADD_FOR_LOCAL_DEV_references": [
{
"path": "../../../packages/core/"
},
{
"path": "../../../packages/react/"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="vite-plus/client" />
Loading
Loading