API Reference@liveblocks/react-lexical

@liveblocks/react-lexical provides you with a React plugin that adds collaboration to any Lexical text editor. It also adds realtime cursors, document persistence on the cloud, comments, and mentions. Read our get started guide to learn more.

Setup

To set up your collaborative Lexical editor, you must use LiveblocksPlugin and liveblocksConfig.

LiveblocksPlugin

Liveblocks plugin for Lexical that adds collaboration to your editor.

<LexicalComposer initialConfig={initialConfig}>  <LiveblocksPlugin /></LexicalComposer>

LiveblocksPlugin should always be nested inside LexicalComposer, and each Lexical default component you’re using should be placed inside the plugin.

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 ( <LexicalComposer initialConfig={initialConfig}> <LiveblocksPlugin> <FloatingThreads /> <FloatingComposer /> <AnchoredThreads /> </LiveblocksPlugin> <RichTextPlugin contentEditable={<ContentEditable />} placeholder={<div>Enter some text...</div>} ErrorBoundary={LexicalErrorBoundary} /> </LexicalComposer> );}

Learn more in our get started guide.

liveblocksConfig

Function that takes a Lexical editor config and modifies it to add the necessary nodes and theme to make LiveblocksPlugin works correctly.

import { liveblocksConfig } from "@liveblocks/react-lexical";
const initialConfig = liveblocksConfig({ namespace: "MyEditor", theme: {}, nodes: [], onError: (err) => console.error(err),});

The config created by liveblocksConfig should be passed to initialConfig in LexicalComposer.

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 ( <LexicalComposer initialConfig={initialConfig}> <LiveblocksPlugin> <FloatingThreads /> <FloatingComposer /> <AnchoredThreads /> </LiveblocksPlugin> <RichTextPlugin contentEditable={<ContentEditable />} placeholder={<div>Enter some text...</div>} ErrorBoundary={LexicalErrorBoundary} /> </LexicalComposer> );}

Note that liveblocksConfig sets editorState to null because LiveblocksPlugin is responsible for initializing it on the server.

Default components

FloatingComposer

Displays a Composer near the current lexical selection.

<LexicalComposer initialConfig={initialConfig}>  <LiveblocksPlugin>    <FloatingComposer />  </LiveblocksPlugin></LexicalComposer>
FloatingComposer

Submitting a comment will attach an annotation thread at the current selection. Should be nested inside LiveblocksPlugin.

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,  FloatingComposer,} from "@liveblocks/react-lexical";
const initialConfig = liveblocksConfig({ namespace: "MyEditor", theme: {}, nodes: [], onError: (err) => console.error(err),});
function Editor() { return ( <LexicalComposer initialConfig={initialConfig}> <LiveblocksPlugin> <FloatingComposer style={{ width: "350px" }} /> </LiveblocksPlugin> <RichTextPlugin contentEditable={<ContentEditable />} placeholder={<div>Enter some text...</div>} ErrorBoundary={LexicalErrorBoundary} /> </LexicalComposer> );}

Opening the composer

To open the FloatingComposer, you need to dispatch the OPEN_FLOATING_COMPOSER_COMMAND Lexical command.

import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";import { OPEN_FLOATING_COMPOSER_COMMAND } from "@liveblocks/react-lexical";
function Toolbar() { const [editor] = useLexicalComposerContext();
return ( <button onClick={() => { editor.dispatchCommand(OPEN_FLOATING_COMPOSER_COMMAND, undefined); }} > 💬 New comment </button> );}

Props

  • metadataThreadMetadata

    The metadata of the thread to create.

  • onComposerSubmitfunction

    The event handler called when the composer is submitted.

  • defaultValueCommentBody

    The composer’s initial value.

  • collapsedboolean

    Whether the composer is collapsed. Setting a value will make the composer controlled.

  • onCollapsedChangefunction

    The event handler called when the collapsed state of the composer changes.

  • defaultCollapsedboolean

    Whether the composer is initially collapsed. Setting a value will make the composer uncontrolled.

  • disabledboolean

    Whether the composer is disabled.

  • autoFocusboolean

    Whether to focus the composer on mount.

  • overridesPartial<GlobalOverrides & ComposerOverrides>

    Override the component’s strings.

FloatingThreads

Displays floating Thread components below text highlights in the editor.

<LexicalComposer initialConfig={initialConfig}>  <LiveblocksPlugin>    <FloatingThreads threads={threads} />  </LiveblocksPlugin></LexicalComposer>
FloatingThreads

Takes a list of threads retrieved from useThreads and renders them to the page. Each thread is opened by clicking on its corresponding text highlight.

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,  FloatingThreads,} from "@liveblocks/react-lexical";
const initialConfig = liveblocksConfig({ namespace: "MyEditor", theme: {}, nodes: [], onError: (err) => console.error(err),});
function Editor() { const { threads } = useThreads();
return ( <LexicalComposer initialConfig={initialConfig}> <LiveblocksPlugin> <FloatingComposer /> <FloatingThreads threads={threads} style={{ width: "350px" }} /> </LiveblocksPlugin> <RichTextPlugin contentEditable={<ContentEditable />} placeholder={<div>Enter some text...</div>} ErrorBoundary={LexicalErrorBoundary} /> </LexicalComposer> );}

Should be nested inside LiveblocksPlugin.

Recommended usage

FloatingThreads and AnchoredThreads have been designed to work together to provide the optimal experience on mobile and desktop. We generally recommend using both components, hiding one on smaller screens, as we are below with Tailwind classes. Most apps also don’t need to display resolved threads, so we can filter those out with a useThreads option.

import { useThreads } from "@liveblocks/react/suspense";import { AnchoredThreads, FloatingThreads } from "@liveblocks/react-lexical";
function ThreadOverlay() { const { threads } = useThreads({ query: { resolved: false } });
return ( <> <FloatingThreads threads={threads} className="w-[350px] block md:hidden" /> <AnchoredThreads threads={threads} className="w-[350px] hidden sm:block" /> </> );}

We can place this component inside ClientSideSuspense to prevent it rendering until threads have loaded.

<LexicalComposer initialConfig={initialConfig}>  <LiveblocksPlugin>    <FloatingComposer />    <ClientSideSuspense fallback={null}>      <ThreadOverlay />    </ClientSideSuspense>  </LiveblocksPlugin></LexicalComposer>

Customization

The FloatingThreads component acts as a wrapper around each individual Thread. You can treat the component like you would a div, using classes, listeners, and more.

<FloatingThreads threads={threads} className="my-floating-thread" />

To apply styling to each Thread, you can pass a custom Thread property to components and modify this in any way. This is the best way to modify a thread’s width.

import { Thread } from "@liveblocks/react-ui";
<FloatingThreads threads={threads} className="my-floating-thread" components={{ Thread: (props) => ( <Thread {...props} className="border shadow" style={{ width: "300px" }} /> ), }}/>;

You can return any custom ReactNode here, including anything from a simple wrapper around Thread, up to a full custom Thread component built using our Comment primitives.

import { Comment } from "@liveblocks/react-ui/primitives";
<FloatingThreads threads={threads} className="my-floating-thread" components={{ Thread: (props) => ( <div> {props.thread.comments.map((comment) => ( <Comment.Body key={comment.id} body={comment.body} components={/* ... */} /> ))} </div> ), }}/>;

Props

  • threadsThreadData[]Required

    The threads to display.

  • componentsPartial<AnchoredThreadsComponents>

    Override the component’s components.

  • components.Thread(props: ThreadProps) => ReactNode

    Override the Thread component.

AnchoredThreads

Displays a list of Thread components vertically alongside the editor.

<LexicalComposer initialConfig={initialConfig}>  <LiveblocksPlugin>    <AnchoredThreads threads={threads} />  </LiveblocksPlugin></LexicalComposer>
AnchoredThreads

Takes a list of threads retrieved from useThreads and renders them to the page. Each thread is displayed at the same vertical coordinates as its corresponding text highlight. If multiple highlights are in the same location, each thread is placed in order below the previous thread.

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 ( <LexicalComposer initialConfig={initialConfig}> <LiveblocksPlugin> <FloatingComposer /> <AnchoredThreads threads={threads} style={{ width: "350px" }} /> </LiveblocksPlugin> <RichTextPlugin contentEditable={<ContentEditable />} placeholder={<div>Enter some text...</div>} ErrorBoundary={LexicalErrorBoundary} /> </LexicalComposer> );}

Should be nested inside LiveblocksPlugin.

Recommended usage

FloatingThreads and AnchoredThreads have been designed to work together to provide the optimal experience on mobile and desktop. We generally recommend using both components, hiding one on smaller screens, as we are below with Tailwind classes. Most apps also don’t need to display resolved threads, so we can filter those out with a useThreads option.

import { useThreads } from "@liveblocks/react/suspense";import { AnchoredThreads, FloatingThreads } from "@liveblocks/react-lexical";
function ThreadOverlay() { const { threads } = useThreads({ query: { resolved: false } });
return ( <> <FloatingThreads threads={threads} className="w-[350px] block md:hidden" /> <AnchoredThreads threads={threads} className="w-[350px] hidden sm:block" /> </> );}

We can place this component inside ClientSideSuspense to prevent it rendering until threads have loaded.

<LexicalComposer initialConfig={initialConfig}>  <LiveblocksPlugin>    <FloatingComposer />    <ClientSideSuspense fallback={null}>      <ThreadOverlay />    </ClientSideSuspense>  </LiveblocksPlugin></LexicalComposer>

Customization

The AnchoredThreads component acts as a wrapper around each Thread. It has no width, so setting this is required, and each thread will take on the width of the wrapper. You can treat the component like you would a div, using classes, listeners, and more.

<AnchoredThreads  threads={threads}  style={{ width: "350px" }}  className="my-anchored-thread"/>

To apply styling to each Thread, you can pass a custom Thread property to components and modify this in any way.

import { Thread } from "@liveblocks/react-ui";
<AnchoredThreads threads={threads} style={{ width: "350px" }} className="my-anchored-thread" components={{ Thread: (props) => ( <Thread {...props} className="border shadow" style={{ background: "white" }} /> ), }}/>;

You can return any custom ReactNode here, including anything from a simple wrapper around Thread, up to a full custom Thread component built using our Comment primitives.

import { Comment } from "@liveblocks/react-ui/primitives";
<AnchoredThreads threads={threads} style={{ width: "350px" }} className="my-anchored-thread" components={{ Thread: (props) => ( <div> {props.thread.comments.map((comment) => ( <Comment.Body key={comment.id} body={comment.body} components={/* ... */} /> ))} </div> ), }}/>;
Modifying thread floating positions

Using CSS variables you can modify the gap between threads, and the horizontal offset that’s added when a thread is selected.

.lb-lexical-anchored-threads {  /* Minimum gap between threads */  --lb-lexical-anchored-threads-gap: 8px;
/* How far the active thread is offset to the left */ --lb-lexical-anchored-threads-active-thread-offset: 12px;}

Props

  • threadsThreadData[]Required

    The threads to display.

  • componentsPartial<AnchoredThreadsComponents>

    Override the component’s components.

  • components.Thread(props: ThreadProps) => ReactNode

    Override the Thread component.

HistoryVersionPreview

The HistoryVersionPreview component allows you to display a preview of a specific version of your Lexical editor's content. It also contains a button and logic for restoring. It must be used inside the <LiveblocksPlugin> context. To render a list of versions, see VersionHistory.

Usage

import { HistoryVersionPreview } from "@liveblocks/react-lexical";
function VersionPreview({ selectedVersion, onVersionRestore }) { return ( <HistoryVersionPreview version={selectedVersion} onVersionRestore={onVersionRestore} /> );}

Props

  • versionHistoryVersionRequired

    The version of the editor content to preview.

  • onVersionRestore(version: HistoryVersion) => void

    Callback function called when the user chooses to restore this version.

The HistoryVersionPreview component renders a read-only view of the specified version of the editor content. It also provides a button for users to restore the displayed version.

Hooks

useEditorStatus

Returns the current editor status.

import { useEditorStatus } from "@liveblocks/react-lexical";
const status = useEditorStatus();

The possible value are:

  • not-loaded: Initial editor state when entering the room.
  • loading: Once the editor state has been requested by LiveblocksPlugin.
  • synchronizing: Not working yet at runtime! Will be available in a future release.
  • synchronized: The editor state is sync with Liveblocks servers.

useIsThreadActive

Accepts a thread id and returns whether the thread annotation for this thread is selected or not in the Lexical editor. This hook must be used in a component nested inside LiveblocksPlugin.

import { useIsThreadActive } from "@liveblocks/react-lexical";
const isActive = useIsThreadActive(thread.id);

This hook can be useful to style threads based on whether their associated thread annotations are selected or not in the editor.

Stylesheets

React Lexical comes with default styles, and these can be imported into the root of your app or directly into a CSS file with @import. Note that you must also install and import a stylesheet from @liveblocks/react-ui to use these styles.

import "@liveblocks/react-ui/styles.css";import "@liveblocks/react-lexical/styles.css";

Customizing your styles

Adding dark mode and customizing your styles is part of @liveblocks/react-ui, learn how to do this under styling and customization.

We use cookies to collect data to improve your experience on our site. Read our Privacy Policy to learn more.