### Create Advanced Collaborative Spreadsheet example
Source: https://liveblocks.io/docs/tools/devtools
Run this command to download and create a local project from the Advanced Collaborative Spreadsheet example. Follow the guided prompts to complete the setup.
```bash
npx create-liveblocks-app@latest --example nextjs-spreadsheet-advanced
```
--------------------------------
### Multiple editors setup example
Source: https://liveblocks.io/docs/api-reference/liveblocks-react-blocknote
Example of setting up multiple editors on one page, each with a separate field value.
```tsx
import { useCreateBlockNoteWithLiveblocks } from "@liveblocks/react-blocknote";
import { BlockNoteView } from "@blocknote/mantine";
function TextEditors() {
return (
);
}
```
--------------------------------
### Create Liveblocks Starter Kit project
Source: https://liveblocks.io/docs/tools/devtools
Run this command to create a new Next.js project using the Liveblocks Starter Kit. Follow the guided prompts in the terminal to complete the setup.
```bash
npx create-liveblocks-app@latest --next
```
--------------------------------
### Full editor setup with LiveblocksPlugin and liveblocksConfig
Source: https://liveblocks.io/docs/api-reference/liveblocks-react-lexical
Complete example of setting up a collaborative Lexical editor. It imports the necessary Lexical components and the Liveblocks plugin, creates an initialConfig with liveblocksConfig, and wraps the editor with LiveblocksPlugin containing FloatingThreads, FloatingComposer, and AnchoredThreads.
```tsx
import { LexicalComposer } from "@lexical/react/LexicalComposer";
import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin";
import { ContentEditable } from "@lexical/react/LexicalContentEditable";
import { LexicalErrorBoundary } from "@lexical/react/LexicalErrorBoundary";
// +++
import { liveblocksConfig, LiveblocksPlugin } from "@liveblocks/react-lexical";
// +++
const initialConfig = liveblocksConfig({
namespace: "MyEditor",
theme: {},
nodes: [],
onError: (err) => console.error(err),
});
function Editor() {
return (
// +++
// +++
}
placeholder={
Enter some text...
}
ErrorBoundary={LexicalErrorBoundary}
/>
);
}
```
--------------------------------
### Start of app/page.tsx
Source: https://liveblocks.io/docs/get-started/nextjs-ai-notifications
Start of the app/page.tsx file. The full example is not shown in the source.
```tsx
"use client";
```
--------------------------------
### Provider setting example
Source: https://liveblocks.io/docs/products/ai-copilots/copilots
Example value for the provider setting, showing 'OpenAI'.
```text
OpenAI
```
--------------------------------
### Initialize RoomProvider with initial storage
Source: https://liveblocks.io/docs/api-reference/upgrading/0.18
Set up a RoomProvider with initial presence and storage containing a LiveObject with nested LiveList. This is the setup used in the following examples.
```tsx
```
--------------------------------
### Install @liveblocks/chat-sdk-adapter and chat
Source: https://liveblocks.io/docs/api-reference/liveblocks-chat-sdk-adapter
Install the adapter and the Chat SDK package using npm.
```bash
npm install @liveblocks/chat-sdk-adapter chat
```
--------------------------------
### Implement multiple editors setup
Source: https://liveblocks.io/docs/api-reference/liveblocks-react-tiptap
Example showing how to render multiple TextEditor components, each with a unique field identifier.
```tsx
import { useLiveblocksExtension } from "@liveblocks/react-tiptap";
import { useEditor, EditorContent } from "@tiptap/react";
function TextEditors() {
return (
);
}
```
--------------------------------
### Install Supabase library
Source: https://liveblocks.io/docs/guides/how-to-synchronize-your-liveblocks-yjs-document-data-to-a-supabase-postgres-database
Install the Supabase JavaScript client using npm.
```bash
npm i @supabase/supabase-js
```
--------------------------------
### Example prompt: Add a realtime text editor with Liveblocks and Tiptap
Source: https://liveblocks.io/docs/integrations/lovable
Use this prompt in the Lovable chat to request adding a realtime text editor with Liveblocks and Tiptap. This is the first step in the setup process.
```text
Add a realtime text editor with Liveblocks and Tiptap.
```
--------------------------------
### Multiple subscriptions: old approach with manual setup
Source: https://liveblocks.io/docs/api-reference/upgrading/0.18
Previously, deriving a computed value from multiple storage values required manual subscriptions and cleanup. This example shows the old, verbose method.
```tsx
function Component() {
const objA = useObject("a");
const objB = useObject("b");
const room = useRoom();
const [sum, setSum] = useState();
// ^^^ We’re trying to compute the result of a.x + b.x in here
useEffect(() => {
function onChange() {
setSum(objA.get("x") + objB.get("x"));
}
const unsubA = room.subscribe(objA, onChange);
const unsubB = room.subscribe(objB, onChange);
return () => {
unsubA();
unsubB();
};
}, [room, objA, objB]);
}
```
--------------------------------
### Install Liveblocks packages
Source: https://liveblocks.io/docs/get-started/nextjs-canvas-custom
Install the required Liveblocks client and React packages.
```bash
npm install @liveblocks/client @liveblocks/react
```
--------------------------------
### Simple system prompt example
Source: https://liveblocks.io/docs/products/ai-copilots/copilots
Example of a simple system prompt.
```text
You're a helpful assistant that can answer questions and help with tasks.
```
--------------------------------
### Install Bun
Source: https://liveblocks.io/docs/tools/dev-server
Install the Bun runtime required to run the Liveblocks development server.
```bash
npm install -g bun
```
--------------------------------
### Model setting example
Source: https://liveblocks.io/docs/products/ai-copilots/copilots
Example value for the model setting, showing 'GPT-4.1'.
```text
GPT-4.1
```
--------------------------------
### Install Liveblocks and Handsontable dependencies
Source: https://liveblocks.io/docs/get-started/nextjs-comments-handsontable
Install the necessary Liveblocks and Handsontable packages for your project.
```bash
npm install @liveblocks/client @liveblocks/react @liveblocks/react-ui @handsontable/react-wrapper handsontable
```
--------------------------------
### Start development server
Source: https://liveblocks.io/docs/tools/nextjs-starter-kit
Run the development server to apply changes and view authentication in action.
```bash
npm run dev
```
--------------------------------
### Initialize React project
Source: https://liveblocks.io/docs/guides/how-to-create-a-collaborative-online-whiteboard-with-react-redux-and-liveblocks
Create a new React application using create-react-app.
```bash
npx create-react-app redux-whiteboard
```
--------------------------------
### Get Comment Success Response Example
Source: https://liveblocks.io/docs/api-reference/rest-api-endpoints
Example of a successful response when retrieving a comment by its ID.
```json
{
"type": "comment",
"threadId": "thread-id",
"roomId": "room-id",
"id": "comment-id",
"userId": "alice",
"createdAt": "2019-08-24T14:15:22Z",
"editedAt": "2019-08-24T14:15:22Z",
"deletedAt": "2019-08-24T14:15:22Z",
"body": {},
"metadata": {},
"reactions": [],
"attachments": []
}
```
--------------------------------
### Get Thread Success Response Example
Source: https://liveblocks.io/docs/api-reference/rest-api-endpoints
Example JSON response returned when successfully retrieving a thread by its ID.
```json
{
"type": "thread",
"id": "thread-id",
"roomId": "room-id",
"comments": [
{
"type": "comment",
"threadId": "thread-id",
"roomId": "room-id",
"id": "comment-id",
"userId": "string",
"createdAt": "2019-08-24T14:15:22Z",
"editedAt": "2019-08-24T14:15:22Z",
"deletedAt": "2019-08-24T14:15:22Z",
"body": {},
"metadata": {},
"reactions": [],
"attachments": []
}
]
}
```
--------------------------------
### Run the dev server
Source: https://liveblocks.io/docs/tools/dev-server
Start the development server from your project directory.
```bash
npx liveblocks dev
```
--------------------------------
### Full AnchoredThreads implementation example
Source: https://liveblocks.io/docs/api-reference/liveblocks-react-lexical
This example shows how to retrieve threads using useThreads and render them with AnchoredThreads within a Lexical editor setup.
```tsx
import { LexicalComposer } from "@lexical/react/LexicalComposer";
import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin";
import { ContentEditable } from "@lexical/react/LexicalContentEditable";
import { LexicalErrorBoundary } from "@lexical/react/LexicalErrorBoundary";
import { useThreads } from "@liveblocks/react/suspense";
import {
liveblocksConfig,
LiveblocksPlugin,
FloatingComposer,
// +++
AnchoredThreads,
// +++
} from "@liveblocks/react-lexical";
const initialConfig = liveblocksConfig({
namespace: "MyEditor",
theme: {},
nodes: [],
onError: (err) => console.error(err),
});
function Editor() {
// +++
const { threads } = useThreads();
// +++
return (
// +++
// +++
}
placeholder={
Enter some text...
}
ErrorBoundary={LexicalErrorBoundary}
/>
);
}
```
--------------------------------
### Initialize room storage with enterRoom
Source: https://liveblocks.io/docs/guides/how-to-use-liveblocks-storage-with-redux
Example of initializing the room's storage when entering a room for the first time.
```javascript
enterRoom("room-id", { firstName: "Lise", lastName: "Meitner", discoveries: ["Nuclear fission", "Protactinium"],});
```
--------------------------------
### Get Storage document response example
Source: https://liveblocks.io/docs/api-reference/rest-api-endpoints
Example response showing the structure of a room's storage document in the default plain LSON format.
```json
{
"liveblocksType": "LiveObject",
"data": {
"aLiveObject": {
"liveblocksType": "LiveObject",
"data": {
"a": 1
}
},
"aLiveList": {
"liveblocksType": "LiveList",
"data": [
"a",
"b"
]
},
"aLiveMap": {
"liveblocksType": "LiveMap",
"data": {
"a": 1,
"b": 2
}
},
"aLiveFile": {
"liveblocksType": "LiveFile",
"data": {
"id": "fl_abc123456789012345678",
"name": "photo.png",
"size": 12345,
"mimeType": "image/png"
}
}
}
}
```
--------------------------------
### Install Liveblocks agent skills with npx
Source: https://liveblocks.io/docs/integrations/claude-desktop
Installs Liveblocks agent skills for Claude Desktop. Select Claude Desktop in the CLI and use global installation for easiest setup. Re-run this command if skills do not load.
```bash
npx skills add liveblocks/skills
```
--------------------------------
### Complete Chat SDK Adapter Integration Example
Source: https://liveblocks.io/docs/api-reference/liveblocks-chat-sdk-adapter
Full example of setting up a bot with the Chat SDK and Liveblocks adapter, including configuration, state, and event handlers for mentions and reactions. Uses a placeholder API key and webhook secret.
```TypeScript
import { Chat } from "chat";
import {
createLiveblocksAdapter,
LiveblocksAdapter,
} from "@liveblocks/chat-sdk-adapter";
import { createMemoryState } from "@chat-adapter/state-memory";
const bot = new Chat<{ liveblocks: LiveblocksAdapter }>({
userName: "MyBot",
adapters: {
liveblocks: createLiveblocksAdapter({
apiKey: "{{SECRET_KEY}}",
webhookSecret: "whsec_...",
botUserId: "my-bot-user",
botUserName: "MyBot",
resolveUsers: async ({ userIds }) => {
const users = await getUsersFromDatabase(userIds);
return users.map((user) => ({ name: user.fullName }));
},
}),
},
state: createMemoryState(),
});
bot.onNewMention(async (thread, message) => {
await thread.adapter.addReaction(thread.id, message.id, "👀");
await thread.post(`Hello, ${message.author.userName}!`);
});
bot.onReaction(async (event) => {
if (!event.added) return;
await event.adapter.postMessage(
event.threadId,
`${event.user.userName} reacted with "${event.emoji.name}"`
);
});
```
--------------------------------
### Active Users Response Example
Source: https://liveblocks.io/docs/api-reference/rest-api-endpoints
Example JSON response for the GET /rooms/{roomId}/active-users endpoint, showing a list of active users with their connection IDs and IDs.
```json
{
"data": [
{
"type": "user",
"connectionId": 16,
"id": "alice",
"info": {}
},
{
"type": "user",
"connectionId": 20,
"id": "bob",
"info": {}
}
]
}
```
--------------------------------
### Initialize React project
Source: https://liveblocks.io/docs/guides/how-to-create-a-collaborative-to-do-list-with-react-zustand-and-liveblocks
Create a new React application with TypeScript support using create-react-app.
```bash
npx create-react-app zustand-todo-app --template typescript
```
--------------------------------
### Example query string for REST API
Source: https://liveblocks.io/docs/guides/how-to-filter-rooms-using-query-language
A query string example that filters rooms by ID prefix and metadata. This query is passed to the query parameter of the Get Rooms REST API.
```js
roomId^'liveblocks:' AND metadata['roomType']:'whiteboard'
```
--------------------------------
### Get Yjs subdocument as binary update
Source: https://liveblocks.io/docs/api-reference/liveblocks-node
Retrieves a specific subdocument as a binary update by providing its guid.
```ts
const binaryYjsUpdate = await liveblocks.getYjsDocumentAsBinaryUpdate(
"my-room-id",
{
// Optional, return a subdocument instead. guid is its unique identifier
guid: "c4a755...",
}
);
```
--------------------------------
### Install dependencies
Source: https://liveblocks.io/docs/get-started/nextjs-ai-react-flow
Install the required Liveblocks, Vercel AI SDK, OpenAI, and Zod packages.
```bash
npm install @liveblocks/node @liveblocks/react-flow ai @ai-sdk/openai zod
```
--------------------------------
### Get Storage File Parameters
Source: https://liveblocks.io/docs/api-reference/rest-api-endpoints
Example values for the roomId and fileId path parameters used when retrieving a Storage file.
```json
{
"schema": {
"type": "string",
"description": "ID of the room",
"example": "my-room-id"
},
"name": "roomId",
"in": "path",
"required": true
},
{
"schema": {
"type": "string",
"pattern": "^fl_[A-Za-z0-9_-]{21}$",
"description": "ID of the Storage file",
"example": "fl_abc123456789012345678"
},
"name": "fileId",
"in": "path",
"required": true
}
```
--------------------------------
### Install Liveblocks and React Flow packages
Source: https://liveblocks.io/docs/get-started/nextjs-react-flow
Install all required Liveblocks and React Flow packages with npm, then initialize the Liveblocks configuration file using the CLI. Both commands are run in the terminal.
```bash
npm install @liveblocks/client @liveblocks/react @liveblocks/react-ui @liveblocks/react-flow @xyflow/react
```
```bash
npx create-liveblocks-app@latest --init --framework react
```
--------------------------------
### Notification event object structure
Source: https://liveblocks.io/docs/products/notifications/email-notifications
An example of the event object received when a user gets a new or updated inbox notification on the email channel.
```ts
const event = {
type: "notification",
data: {
channel: "email",
kind: "$myCustomNotification",
projectId: "my-project-id",
roomId: "my-room-id",
userId: "my-user-id",
inboxNotificationId: "in_xt3p7ak...",
createdAt: "2021-10-06T01:45:56.558Z",
},
};
```
--------------------------------
### Initialize Storage Document Request Body Example
Source: https://liveblocks.io/docs/api-reference/rest-api-endpoints
Example structure for the request body when initializing a room's storage.
```json
{
"liveblocksType": "LiveObject"
```
--------------------------------
### Passing liveblocksConfig to LexicalComposer
Source: https://liveblocks.io/docs/api-reference/liveblocks-react-lexical
Complete example of passing the config created by liveblocksConfig to LexicalComposer's initialConfig. Includes the full editor setup with LiveblocksPlugin and RichTextPlugin.
```tsx
import { LexicalComposer } from "@lexical/react/LexicalComposer";
import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin";
import { ContentEditable } from "@lexical/react/LexicalContentEditable";
import { LexicalErrorBoundary } from "@lexical/react/LexicalErrorBoundary";
import { liveblocksConfig, LiveblocksPlugin } from "@liveblocks/react-lexical";
const initialConfig = liveblocksConfig({
namespace: "MyEditor",
theme: {},
nodes: [],
onError: (err) => console.error(err),
});
function Editor() {
return (
}
placeholder={
Enter some text...
}
ErrorBoundary={LexicalErrorBoundary}
/>
);
}
```
--------------------------------
### Initialize Liveblocks client and storage
Source: https://liveblocks.io/docs/guides/how-to-create-a-collaborative-to-do-list-with-javascript-and-liveblocks
This snippet demonstrates how to initialize the Liveblocks client, enter a room, and set up an initial storage state using a LiveList to hold to-do items.
```javascript
import { createClient, LiveList } from "@liveblocks/client";
const client = createClient({ publicApiKey: "pk_prod_xxxxxxxxxx…xxxxxxxxpk_prod_xxxxxxxxxxxxxxxxxxxxxxxx",});
async function run() { const { room, leave } = client.enterRoom("javascript-todo-app", { initialPresence: { isTyping: false }, initialStorage: { todos: new LiveList() }, });
const whoIsHere = document.getElementById("who_is_here"); const todoInput = document.getElementById("todo_input"); const someoneIsTyping = document.getElementById("someone_is_typing");
const { root } = await room.getStorage();
let todos = root.get("todos");
room.subscribe("others", (others) => { /* ... */ });
todoInput.addEventListener("keydown", (e) => { if (e.key === "Enter") { room.updatePresence({ isTyping: false }); todos.push({ text: todoInput.value }); todoInput.value = ""; } else { room.updatePresence({ isTyping: true }); } });
todoInput.addEventListener("blur", () => { room.updatePresence({ isTyping: false }); });}
run();
```
--------------------------------
### Project structure overview
Source: https://liveblocks.io/docs/tools/nextjs-starter-kit
The directory layout of the Next.js Starter Kit project.
```text
app
components
data
icons
layouts
lib
└─ actions
└─ database
└─ hooks
└─ utils
primitives
styles
types
utils
auth.ts
auth.config.ts
constants.ts
liveblocks.config.ts
liveblocks.server.config.ts
package.json
```
--------------------------------
### Upgrade Liveblocks packages to 1.5
Source: https://liveblocks.io/docs/api-reference/upgrading/1.5
Install the latest versions of the Liveblocks packages you use, for example in a React app. Update every Liveblocks package you're using.
```bash
npm install @liveblocks/client@latest @liveblocks/node@latest @liveblocks/react@latest
```
--------------------------------
### Set up RoomProvider with initialStorage
Source: https://liveblocks.io/docs/get-started/nextjs
Configures the Liveblocks provider and room with an initial storage object containing an input LiveObject. This is the first step in the quickstart, setting up the collaborative environment.
```tsx
export function Room({ children }: { children: ReactNode }) {
return (
Loading…}>
{children}
);
}
```
--------------------------------
### Track cursors with useOthers and useUpdateMyPresence
Source: https://liveblocks.io/docs/use-cases/canvas
Use useOthers and useUpdateMyPresence from @liveblocks/react/suspense to get and set user cursor positions on the canvas. The example uses screen coordinates and renders a Cursor component for each other user.
```tsx
import { useOthers, useUpdateMyPresence } from "@liveblocks/react/suspense";
import { Cursor } from "@liveblocks/react-ui";
function CanvasPresence() {
// +++
const others = useOthers();
const updateMyPresence = useUpdateMyPresence();
// +++
return (
);
}
```
--------------------------------
### Install @liveblocks/node, AI SDK, and Anthropic provider
Source: https://liveblocks.io/docs/get-started/nextjs-comments-ai
Run this command to install the packages needed for webhook verification, AI response generation, and Anthropic provider support.
```bash
npm install @liveblocks/node ai @ai-sdk/anthropic
```
--------------------------------
### Get ProseMirror document state with withProsemirrorDocument and api.getText
Source: https://liveblocks.io/docs/guides/getting-prosemirror-state-on-the-server
Use withProsemirrorDocument and api.getText to retrieve the text content of a ProseMirror document on the server. The example logs the text content, which is "My content".
```TypeScript
import { Liveblocks } from "@liveblocks/node";
import { withProsemirrorDocument } from "@liveblocks/node-prosemirror";
const liveblocks = new Liveblocks({
secret: "{{SECRET_KEY}}",
});
// +++
const textContent = await withProsemirrorDocument(
{ roomId: "your-room-id", client: liveblocks, field: "prosemirror" },
async (api) => {
return api.getText();
}
);
// +++
// "My content"
console.log(textContent);
```
--------------------------------
### Install Liveblocks and Tiptap packages
Source: https://liveblocks.io/docs/get-started/nextjs-tiptap-storage
Install all required Liveblocks and Tiptap packages. Every Liveblocks package should use the same version.
```bash
npm install @liveblocks/client @liveblocks/react @liveblocks/react-ui @liveblocks/react-tiptap @tiptap/react @tiptap/starter-kit
```
--------------------------------
### Set up Liveblocks client with LiveblocksProvider and RoomProvider
Source: https://liveblocks.io/docs/get-started/react-tiptap-storage
Set up a Liveblocks client with LiveblocksProvider and join a room with RoomProvider. In Storage mode, documents are created under root._tiptap_docs when the editor first connects, so initialStorage can be left empty.
```tsx
"use client";
import {
LiveblocksProvider,
RoomProvider,
} from "@liveblocks/react/suspense";
import { Editor } from "./Editor";
export default function App() {
return (
{/* ... */}
);
}
```
--------------------------------
### Display unread count and mark all as read or delete all with hooks
Source: https://liveblocks.io/docs/use-cases/inbox
Use useUnreadInboxNotificationsCount to get the unread count, and useMarkAllInboxNotificationsAsRead and useDeleteAllInboxNotifications to perform bulk actions. The example shows a header with the count and buttons to trigger these actions.
```tsx
import {
useMarkAllInboxNotificationsAsRead,
useUnreadInboxNotificationsCount,
useDeleteAllInboxNotifications,
} from "@liveblocks/react/suspense";
function InboxHeader() {
// +++
const { count } = useUnreadInboxNotificationsCount();
const markAllAsRead = useMarkAllInboxNotificationsAsRead();
const deleteAll = useDeleteAllInboxNotifications();
// +++
return (
// +++
{count} unread
// +++
);
}
```
--------------------------------
### Install Slate, Yjs, and Liveblocks packages
Source: https://liveblocks.io/docs/guides/how-to-create-a-collaborative-text-editor-with-slate-yjs-nextjs-and-liveblocks
Run this npm command to install all required packages for the collaborative editor: @liveblocks/client, @liveblocks/react, @liveblocks/yjs, @liveblocks/node, yjs, slate, slate-react, @slate-yjs/core, and @slate-yjs/react.
```bash
npm install @liveblocks/client @liveblocks/react @liveblocks/yjs @liveblocks/node yjs slate slate-react @slate-yjs/core @slate-yjs/react
```
--------------------------------
### GET /rooms/{roomId}/ydoc-binary
Source: https://liveblocks.io/docs/api-reference/rest-api-endpoints
This endpoint retrieves the room's Yjs document as a binary update. It can be used to obtain a copy of the document in your back end, and supports subdocuments via the `guid` query parameter.
```APIDOC
## [GET] /rooms/{roomId}/ydoc-binary
### Description
This endpoint returns the room's Yjs document encoded as a single binary update. This can be used by `Y.applyUpdate(responseBody)` to get a copy of the document in your back end. To return a subdocument instead of the main document, pass its `guid`. Corresponds to [`liveblocks.getYjsDocumentAsBinaryUpdate`](https://liveblocks.io/docs/api-reference/liveblocks-node#get-rooms-roomId-ydoc-binary).
### Method
GET
### Endpoint
/rooms/{roomId}/ydoc-binary
### Parameters
#### Path Parameters
- **roomId** (string) - Required - ID of the room
#### Query Parameters
- **guid** (string) - Optional - ID of the subdocument
### Request Example
(No request body)
### Response
#### Success Response (200)
- **binary update** (binary) - The room's Yjs document encoded as a binary Yjs update.
#### Response Example
(Response body is binary data)
```
--------------------------------
### useUndo and useRedo hooks in React
Source: https://liveblocks.io/docs/guides/how-to-use-liveblocks-multiplayer-undo-redo-with-react
Use the useUndo and useRedo hooks from @liveblocks/react/suspense to get undo and redo functions. The example renders two buttons that call undo() and redo() to revert or reapply the last storage change.
```tsx
import { useUndo, useRedo } from "@liveblocks/react/suspense";
function App() {
const undo = useUndo();
const redo = useRedo();
return (
<>
>
);
}
```
--------------------------------
### Install Liveblocks and BlockNote packages
Source: https://liveblocks.io/docs/get-started/nextjs-blocknote-storage
Install all required Liveblocks and BlockNote packages. Every Liveblocks package should use the same version.
```bash
npm install @liveblocks/client @liveblocks/react @liveblocks/react-ui @liveblocks/react-blocknote @blocknote/core @blocknote/react @blocknote/mantine
```