Sign in

@liveblocks/react-ui

@liveblocks/react-ui provides you with React components to build collaborative experiences. Read our Comments and Notifications get started guides to learn more.

AI Copilots

Default components

AiChat

Displays an interactive AI chat. AI can use knowledge and run actions or display content via tools.

<AiChat chatId="my-chat-id" />
AI Chat

Each chat is stored permanently, and is identified by its unique chatId. Chats are only visible to the authenticated user who created the chat.

import { AiChat } from "@liveblocks/react-ui";
function Chat() { return <AiChat chatId="my-chat-id" />;}
Assigning a copilot

Use a custom copilot in your chat. You can define copilots with custom prompts & settings in the Liveblocks dashboard, passing your API key from OpenAI, Anthropic, or Google. Copy the copilot's ID and pass it to the copilotId prop.

import { AiChat } from "@liveblocks/react-ui";
function Chat() { return ( <AiChat chatId="my-chat-id" copilotId="co_a7Gd3x..." /> );}

Dynamically switching copilots is possible, and messages will use whichever copilotId is set when a message is sent.

Show placeholder content in new chats

In chats without messages, you can display placeholder content to welcome and guide the user. To set this content, use the Empty property under components.

import { AiChat } from "@liveblocks/react-ui";
function Chat() { return ( <AiChat chatId="my-chat-id" components={{ Empty: <div>I'm an empty chat!</div> }} /> );}

Additionally, you can add suggestion buttons which will automatically submit new messages to the chat when clicked. Create them with useSendAiMessage.

import { useSendAiMessage } from "@liveblocks/react";import { AiChat } from "@liveblocks/react-ui";
function Chat({ chatId }: { chatId: string }) { const sendAiMessage = useSendAiMessage(chatId);
return ( <AiChat chatId="my-chat-id" components={{ Empty: ( <div> <div>Suggestions</div> <button onClick={() => sendAiMessage("What's new?")}> Update me </button> <button onClick={() => sendAiMessage("Create a new document")}> Draft a document </button> </div> ), }} /> );}
List the user’s chats and switch between them

You can display a list of all chats created by the current user with useAiChats. For example, you can render a list of buttons that allow you to switch between chats. In each button, you can display the chat’s automatically generated title, as seen below. Chats can be deleted with useDeleteAiChat.

import { useState } from "react";import { AiChat } from "@liveblocks/react-ui";import { useAiChats } from "@liveblocks/react";import { Timestamp } from "@liveblocks/react-ui/primitives";
function Chats() { const { chats, error, isLoading } = useAiChats(); const [chatId, setChatId] = useState(); const deleteChat = useDeleteAiChat();
if (isLoading) { return <div>Loading...</div>; }
if (error) { return <div>Error: {error.message}</div>; }
return ( <div style={{ display: "flex" }}> <ul> {chats.map((chat) => ( <li key={chat.id}> <button onClick={() => setChatId(chat.id)}> {chat.title || "Untitled"} </button> <Timestamp date={chat.lastMessageAt || chat.createdAt} /> <button onClick={() => deleteChat(chat.id)}></button> </li> ))} </ul> <AiChat chatId={chatId} /> </div> );}
Display the chat’s title

Each chat has a title property, automatically generated by AI. The title of a new chat starts empty, and is updated after AI receives the first message and writes a response. You can render this alongside your chat with useAiChat.

import { AiChat } from "@liveblocks/react-ui";import { useAiChat } from "@liveblocks/react";
function ChatWithTitle({ chatId }: { chatId: string }) { const { chat, error, isLoading } = useAiChat(chatId);
if (isLoading) { return <div>Loading...</div>; }
if (error) { return <div>Error: {error.message}</div>; }
return ( <div> <h1>{chat.title || "Untitled chat"}</h1> <AiChat chatId={chatId} /> </div> );}
Add front-end knowledge

You can add front-end knowledge to chats, meaning the AI will understand the information you pass, and will answer questions or call tools based on it. This is particularly helpful for passing user info, app state, and small contextual knowledge.

It’s generally recommended to use RegisterAiKnowledge for adding knowledge, as this will add knowledge to all AI features on the page. However, if you’d like knowledge that is specific to one chat, you can add it with the knowledge prop on AiChat. No other chats will have access to this knowledge.

import { AiChat } from "@liveblocks/react-ui";
function Chat() { return ( <AiChat chatId="my-chat-id" knowledge={[ { description: "The current user's payment plan", value: "Enterprise" }, { description: "The current user's info", value: { name: "Jody Hekla", email: "jody@liveblocks.io", teams: ["Engineering", "Product"], }, }, ]} /> );}
Add back-end knowledge

You can add back-end knowledge to chats, meaning the AI will understand the information you pass, and can answer questions or call tools based on it. This is a way to pass large amounts of project-wide information, for example complex documentation.

When creating or editing a copilot in the Liveblocks dashboard navigate to the Knowledge tab. Within here you can upload any relevant files, or submit websites for indexing. Your copilot will internalize this knowledge using retrieval-augmented generation (RAG).

Adjusting the chat’s width

When using the default inset layout, it’s possible to adjust the chat’s width by setting the --lb-ai-chat-container-width CSS variable. This allows the chat’s scroll window to stay full width, whilst keeping the composer and messages centered in the middle.

.lb-ai-chat {  --lb-ai-chat-container-width: 600px;}
Compact layout mode

An alternate compact layout mode is available, ideal for smaller UI components such as pop-up windows. Compact layout mode removes the shadow and padding on the composer, makes it full-width, and displays a border above it.

import { AiChat } from "@liveblocks/react-ui";
function Chat() { return ( <AiChat chatId="my-chat-id" layout="compact" /> );}
Change background color

You can change the background color of the chat by setting the --lb-background CSS variable on .lb-ai-chat.

.lb-ai-chat {  --lb-background: #eeeeee;}
Customize CSS variables and classes

You can customize the default styles of the chat by modifying CSS variables and classes prefixed with lb. Here are some examples.

/* Lowers spacing and shrinks font size */.lb-ai-chat {  --lb-spacing: 0.6em;  font-size: 14px;}
/* Removes composer shadow and adds border */.lb-ai-chat-composer { box-shadow: 0; border: 1px solid #f0f0f0;}
/* Removes padding below the composer */.lb-ai-chat-footer { padding-bottom: 0;}
Customize how Markdown is rendered

You can customize how Markdown is rendered in messages by passing components to the components prop. A full list is available here.

<AiChat  chatId="my-chat-id"  components={{    markdown: {      // Example: Use custom paragraph styles      Paragraph: ({ children }) => <p className="my-3">{children}</p>,
// Example: Use an existing component for quotes Blockquote: ({ children }) => <MyQuote>{children}</MyQuote>,
// Example: Use `next/link` instead of default `<a>` tag Link: ({ children, href }) => <Link href={href || ""}>{children}</Link>,
// Example: Use an external library to add syntax highlighting to code blocks CodeBlock: ({ language, code }) => ( <SyntaxHighlighter language={language}>{code}</SyntaxHighlighter> ),
// `Heading`, `Inline`, `List`, `Table`, `Image`, `Separator`, etc. // ... }, }}/>
Props
  • chatIdstringRequired

    The unique identifier for the chat. Each chat is stored permanently and is only visible to the authenticated user who created it.

  • autoFocusboolean

    Whether to automatically focus the composer input when the chat loads. Defaults to false.

  • copilotIdstring

    The ID of the custom copilot to use for this chat. Copy this from your copilot configuration in the Liveblocks dashboard.

  • knowledgeAiKnowledgeSource[]

    Array of knowledge sources specific to this chat. This knowledge will only be available to this chat instance and not to other AI features on the page.

  • toolsRecord<string, AiToolDefinition>

    Object mapping tool names to tool definitions that should be available in this chat.

  • onComposerSubmitfunction

    The event handler called when the composer is submitted.

  • layout'inset' | 'compact'

    The layout mode for the chat. Use 'inset' (default) for standalone chats, or 'compact' for embedded scenarios like pop-up windows.

  • overridesAiChatOverrides

    Advanced customization options for overriding default chat behavior and styling.

  • componentsAiChatComponents

    Custom components to override specific parts of the chat UI, such as the Empty placeholder component or Markdown components.

  • responseTimeoutnumber

    The time, in milliseconds, before an AI response will timeout. Defaults to 30_000.

  • showReasoningboolean | 'during'

    Whether to show reasoning. Defaults to true. If set to 'during', reasoning will only be shown during reasoning.

  • showRetrievalsboolean | 'during' | { ... } | undefined

    Whether to show retrievals. Defaults to true. If set to 'during', retrievals will only be shown during retrieval.

  • showSourcesboolean

    Whether to show sources. Defaults to true.

  • classNamestring

    CSS class name to apply to the chat container.

  • styleCSSProperties

    Inline styles to apply to the chat container. Useful for setting CSS custom properties.

components

Override specific parts of AiChat with custom components.

  • Empty({ chatId: string, copilotId?: string }) => ReactNode

    The component used to render the empty state of the chat. Defaults to nothing.

  • Loading() => ReactNode

    The component used to render the loading state of the chat. Defaults to a loading spinner.

  • markdownPartial<MarkdownComponents>

    The components used to render Markdown content.

  • markdown.Paragraph({ children: ReactNode }) => ReactNode

    The component used to render paragraphs.

  • markdown.Inline

    The component used to render inline elements (bold, italic, strikethrough, and inline code).

  • markdown.Link({ href: string, title?: string, children: ReactNode }) => ReactNode

    The component used to render links.

  • markdown.Heading({ level: 1 | 2 | 3 | 4 | 5 | 6, children: ReactNode }) => ReactNode

    The component used to render headings.

  • markdown.Blockquote({ children: ReactNode }) => ReactNode

    The component used to render blockquotes.

  • markdown.CodeBlock({ code: string, language?: string }) => ReactNode

    The component used to render code blocks.

  • markdown.Image({ src: string, alt: string, title?: string }) => ReactNode

    The component used to render images.

  • markdown.List

    The component used to render lists.

  • markdown.Table

    The component used to render tables.

  • markdown.Separator() => ReactNode

    The component used to render separators.

AiTool

Displays AI tool calls and their progress. Can be customized for many different UIs.

<AiTool />

By default, AiTool will display the name of the current tool, and a loading spinner as it runs.

import { defineAiTool } from "@liveblocks/client";import { RegisterAiTool } from "@liveblocks/react";import { AiTool, AiChat } from "@liveblocks/react-ui";
function App() { return ( <> <RegisterAiTool name="get-weather" tool={defineAiTool()({ description: "Get current weather information", parameters: { type: "object", properties: { location: { type: "string", description: "City name" }, }, required: ["location"], additionalProperties: false, }, execute: async (args) => { const { temperature, condition } = await ( args.location ); return { data: { temperature, condition } }; }, render: ({ result }) => ( <AiTool> {result ? ( <div> {result.temperature}°F - {result.condition} </div> ) : null} </AiTool> ), })} /> <AiChat chatId="my-chat" /> </> );}

Optionally, you can provide a title and icon to render the UI differently.

// Titlerender: () => <AiTool title="Event booked" />
// Title and a collapsible descriptionrender: () => <AiTool title="Event booked">We've booked the event!</AiTool>,
// Title and emoji iconrender: () => <AiTool title="Event booked" icon="📅" />,
// Title and icon componentrender: () => <AiTool title="Event booked" icon={<Icon.Calendar />} />,
// Props are passed to the inner `div`render: () => ( <AiTool title="Event booked" style={{ marginLeft: 10 }} className="event-booked-tool" onMouseOver={() => console.log("Hovered")} />),
Props
  • titleReactNode

    The title to display for the tool. If not provided, the tool name will be formatted as a human-readable string.

  • iconReactNode

    Icon to display alongside the tool title. Can be an emoji string, React component, or any ReactNode.

  • childrenReactNode

    Content to display inside the tool container. Typically used for tool-specific UI or descriptions.

  • variant'block' | 'minimal'

    The visual appearance of the tool. The "block" variant (default) displays the tool as a block with a border.

  • collapsedboolean

    Whether the tool content should be collapsed. When collapsed, only the title and icon are visible.

  • onCollapsedChange(collapsed: boolean) => void

    Callback fired when the collapsed state changes. Use this to control the collapsed state externally.

  • collapsibleboolean

    Whether the tool content can be collapsed. If set to false, clicking on it will have no effect. If there's no content, this prop has no effect.

  • classNamestring

    CSS class name to apply to the tool container.

  • styleCSSProperties

    Inline styles to apply to the tool container.

All other HTML div props are also supported and will be passed through to the underlying container element.

AiTool.Confirmation

Displays an AI tool with a confirmation UI. This allows you to create actions that users must confirm or cancel before they’re run.

<AiTool>  <AiTool.Confirmation confirm={() => /* ... */} cancel={() => /* ... */} /></AiTool>

Use the confirm and cancel props to define which actions should be taken when the users clicks the buttons. You can return information that helps the AI understand what has taken place, and data which you can use in render after the tool is called.

import { defineAiTool } from "@liveblocks/client";import { RegisterAiTool } from "@liveblocks/react";import { AiTool } from "@liveblocks/react-ui";
const deleteFileTool = defineAiTool<{ deletedFileName: string }>()({ description: "Delete a file from the user's workspace", parameters: { type: "object", properties: { fileName: { type: "string", description: "Name of the file to delete" }, }, required: ["fileName"], additionalProperties: false, }, render: ({ stage, args, result, types }) => { if (stage === "receiving") { return "Loading..."; } return ( <AiTool title="Delete File" icon="🗑️"> {!result.data ? ( <AiTool.Confirmation // Make `confirm` and `cancel` type-safe types={types} confirm={async ({ fileName }) => { await deleteFile(fileName); return { data: { deletedFileName: fileName }, }; }} > Are you sure you want to delete {args.fileName}? </AiTool.Confirmation> ) : ( <div>Deleted {result.data.deletedFileName}</div> )} </AiTool> ); },});
function App() { return ( <> <RegisterAiTool name="delete-file" tool={deleteFileTool} /> <AiChat chatId="my-chat" /> </> );}

AiTool.Confirmation will display different content depending on the stage of the tool. For example, the confirm and cancel buttons will disappear when clicked.

Props
  • confirm(args: TArgs) => Promise<ToolResultResponse>Required

    Function called when the user clicks the confirm button. It can return data which will be stored and accessible in render, and optionally also a description for the AI to understand the result: { data: { formId: 123 }, description: "The user accepted and submitted the form" }

  • cancel(args: TArgs) => Promise<ToolResultResponse>

    Function called when the user clicks the cancel button.

  • childrenReactNode

    Content to display in the confirmation UI. Typically a question or description of the action being confirmed.

  • variant'destructive' | 'default'

    The visual appearance of the confirmation UI.

  • overridesPartial<GlobalOverrides & AiToolConfirmationOverrides>

    Override the component’s strings. It can be used the change the "confirm" and "cancel" labels.

All other HTML div props are also supported and will be passed through to the underlying element.

AiTool.Inspector

Displays formatted view of the JSON arguments sent to and results returned by the AI during the current tool invocation. This is useful for debugging or for providing developers with insight into the data exchanged within your app.

<AiTool>  <AiTool.Inspector /></AiTool>

To use, simply include <AiTool.Inspector /> inside an <AiTool /> component to display the tool’s input arguments and resulting output.

import { defineAiTool } from "@liveblocks/client";import { RegisterAiTool } from "@liveblocks/react";import { AiTool, AiChat } from "@liveblocks/react-ui";
function App() { return ( <> <RegisterAiTool name="toggle-todo" tool={defineAiTool()({ description: "Toggle a todo's completion status", parameters: { type: "object", properties: { id: { description: "The id of the todo to toggle", type: "number", }, }, required: ["id"], additionalProperties: false, }, execute: ({ id }) => { toggleTodo(id); }, render: () => ( <AiTool> <AiTool.Inspector /> </AiTool> ), })} /> <AiChat chatId="my-chat" /> </> );}
Props

All HTML div props are supported and will be passed through to the underlying element.

Comments

Default components

Thread

Displays a thread of comments. Each thread has a composer for creating replies.

<Thread thread={thread} />
Thread

Map through threads to render a list of the room’s threads and comments. Threads can be retrieved with useThreads.

import { Thread } from "@liveblocks/react-ui";import { useThreads } from "@liveblocks/react/suspense";
function Component() { const { threads } = useThreads();
return ( <> {threads.map((thread) => ( <Thread key={thread.id} thread={thread} /> ))} </> );}
Resolved and unresolved threads

A thread can be marked as resolved or unresolved via its resolved property. The Thread component automatically handles this through its resolved toggle button displayed by default.

You can additionally use thread.resolved to filter the displayed threads for example. Or if you want to create your own Thread component using the primitives, you can use useMarkThreadAsResolved and useMarkThreadAsUnresolved to update the property.

Collapsed threads

You can collapse threads by setting the maxVisibleComments prop. If a thread contains more comments than the limit set, some of the comments will be hidden and a "Show more replies" button will be displayed instead. Clicking on it will expand the thread to show all comments.

<Thread thread={thread} maxVisibleComments={5} />

The first and last comments are always visible, and by default the oldest comments are more likely to be hidden. You can customize this behavior by setting maxVisibleComments to an object.

// This is the default behavior, the same as `maxVisibleComments={5}`.<Thread thread={thread} maxVisibleComments={{ max: 5, show: "newest" }} />
// Only show the last comment, and all the older ones to fit the limit.<Thread thread={thread} maxVisibleComments={{ max: 5, show: "oldest" }} />
// Show as many old comments as new ones to fit the limit.<Thread thread={thread} maxVisibleComments={{ max: 5, show: "both" }} />
Customize comments

You can provide a custom Comment component via the components prop to fully customize how comments are rendered within a thread. This allows you to render a fully custom React component in the provided Comment slot, however it's often preferable to insert the default Comment component, and use its customization options instead.

The children prop on Comment allows overriding or wrapping the comments’ content, while the additionalContent prop can be useful to render custom content integrated into the comments’ content, just below the comment body. The body prop is the same as the children prop but it only overrides the default rich-text comment body while still keeping attachments, reactions, and additionalContent as is.

Comment also offers avatar, author, and date props to allow overriding or customizing the comment’s displayed avatar, author, and date respectively. Comment.Avatar, Comment.Author, and Comment.Date can be used to retain the default behavior and styles but with more control over them.

import { Comment, Thread } from "@liveblocks/react-ui";
<Thread thread={thread} components={{ Comment: ({ comment, ...props }) => ( <Comment comment={comment} avatar={ <div className="custom-avatar"> <Comment.Avatar userId={props.comment.userId} /> <div className="custom-badge" /> </div> } author={ <span className="custom-author"> <Comment.Author userId={props.comment.userId} /> <span>Custom label</span> </span> } date={ <span className="custom-date"> <Comment.Date date={props.comment.createdAt} /> {props.comment.editedAt && ( <span className="custom-edited-label">Edited</span> )} </span> } additionalContent={ <div className="custom-additional-content"> Content below the comment's body (above reactions and attachments) </div> } {...props} > {({ children }) => ( <div className="custom-content-wrapper"> {children} <div> Content below the comment's content (including reactions and attachments) </div> </div> )} </Comment> ), }}/>;
Rendering custom components

You can render fully custom components in comment threads, instead of showing the default Comment component. This is particularly useful for inserting custom UI into threads, such as data visualizations, tables, custom AI commenting components, and more.

Picture a thread that features a graph visualization instead of a comment. To render it in the thread, you can create a comment and use its comment metadata to define its data. First, set up your metadata typing in your config file. We’ll define two types of comments, a normal comment and a graph comment, and pass both to CommentMetadata. Any data can go in here, but in this example, graphs are defined with type: "graph" and a graphId.

liveblocks.config.ts
type NormalComment = {};
type GraphComment = { type: "graph"; graphId: string;};
declare global { interface Liveblocks { CommentMetadata: NormalComment | GraphComment; }}

Next, create the comment. This comment will most likely by created on the server, using liveblocks.createComment. Set your graph’s metadata in the comment’s metadata option.

const comment = await liveblocks.createComment({  roomId: "my-room-id",  threadId: "th_d75sF3...",  data: {    body: {      version: 1,      content: [        { type: "paragraph", children: [{ text: "Graph placeholder" }] },      ],    },    userId: "bot@example.com",  },  metadata: {    type: "graph",    graphId: "revenue-by-month",  },});

To render this graph inside a comment thread, check for the type: "graph" value you defined in comment.metadata, and return a custom component instead of a Comment. Make sure to return Comment for all regular comments.

import { Comment, Thread } from "@liveblocks/react-ui";
<Thread thread={thread} components={{ Comment: ({ comment, ...props }) => { // Render your custom graph inside a comment UI if (comment.metadata?.type === "graph") { return <Graph graphId={comment.metadata.graphId} />; }
return <Comment comment={comment} {...props} />; }, }}/>;

If you’d like to render a custom component inside a comment UI, for example with avatar, author, date, then customize the comment component instead of returning a fully custom component.

import { Comment, Thread } from "@liveblocks/react-ui";
<Thread thread={thread} components={{ Comment: ({ comment, ...props }) => { // Render your custom graph component if (comment.metadata?.type === "graph") { return ( <Comment comment={comment} {...props}> <Graph graphId={comment.metadata.graphId} /> </Comment> ); }
return <Comment comment={comment} {...props} />; }, }}/>;
Customize dropdown items

Thread shows a dropdown menu for threads and comments which contains actions related to them: “Subscribe to thread”, “Edit comment”, “Delete comment”, etc.

The prop commentDropdownItems allows customizing the dropdown’s items, for example adding new items. Items can be built with the Comment.DropdownItem component which accepts an onSelect prop that is called when the item is selected.

<Thread  commentDropdownItems={    <>      <Comment.DropdownItem        onSelect={() => {          console.log("Open details");        }}      >        Details      </Comment.DropdownItem>      <Comment.DropdownItem        onSelect={() => {          console.log("Move comment");        }}      >        Move      </Comment.DropdownItem>    </>  }/>

These new items will be displayed below the default items, but it’s possible to change that by passing a function. This function receives a children prop which contains the default items, so you can decide to display them above or below your new items, or even not display them at all. This function also receives a comment prop which contains the comment it’s attached to.

<Thread  commentDropdownItems={({ children }) => {    return (      <>        <Comment.DropdownItem          onSelect={() => {            openDetails(comment.id);          }}        >          Details        </Comment.DropdownItem>        {/* The "Details" item will be displayed above the default items */}        {children}      </>    );  }}/>

The Comment.DropdownItem component also accepts an icon prop to display an icon next to the item’s label.

<Comment.DropdownItem  onSelect={() => {    openDetails(comment.id);  }}  icon={<Icon.Details />}>  Details</Comment.DropdownItem>

Comment offers the same as Thread’s commentDropdownItems but named dropdownItems instead.

Props
  • threadThreadDataRequired

    The thread to display.

  • showComposerboolean | "collapsed"Default is "collapsed"

    How to show or hide the composer to reply to the thread.

  • showActionsboolean | "hover"Default is "hover"

    How to show or hide the actions.

  • showReactionsbooleanDefault is true

    Whether to show reactions.

  • showAttachmentsbooleanDefault is true

    Whether to show attachments.

  • showComposerFormattingControlsbooleanDefault is true

    Whether to show the composer’s formatting controls.

  • blurComposerOnSubmitbooleanDefault is true

    Whether to blur the composer editor when the composer is submitted.

  • showResolveActionbooleanDefault is true

    Whether to show the action to resolve the thread.

  • maxVisibleCommentsnumber | objectDefault is No limit

    The maximum number of comments to show.

  • indentCommentContentbooleanDefault is true

    Whether to indent the comments’ content.

  • showDeletedCommentsbooleanDefault is false

    Whether to show deleted comments.

  • showSubscriptionbooleanDefault is true

    Whether to show the thread’s subscription status.

  • commentDropdownItemsReactNode | (props) => ReactNode

    Add (or change) items to display in the comment’s dropdown.

  • onComposerSubmitfunction

    The event handler called when the composer is submitted.

  • onResolvedChangefunction

    The event handler called when changing the resolved status.

  • onThreadDeletefunction

    The event handler called when the thread is deleted. A thread is deleted when all its comments are deleted.

  • onCommentEditfunction

    The event handler called when a comment is edited.

  • onCommentDeletefunction

    The event handler called when a comment is deleted.

  • onAuthorClickfunction

    The event handler called when clicking on a comment’s author.

  • onMentionClickfunction

    The event handler called when clicking on a mention.

  • onAttachmentClickfunction

    The event handler called when clicking on a comment’s attachment.

  • overridesPartial<GlobalOverrides & ThreadOverrides & CommentOverrides & ComposerOverrides>

    Override the component’s strings.

  • componentsPartial<GlobalComponents & ThreadComponents>

    Override the component’s components.

components

Override the component’s components, including providing a custom Comment component.

  • CommentComponentType<CommentProps>

    The component used to display comments.

FloatingThread

Displays a floating thread attached to a trigger element.

<FloatingThread thread={thread}>  <button>Open thread</button></FloatingThread>
FloatingThread

FloatingThread can be combined with CommentPin in canvas-like UIs.

<FloatingThread thread={thread}>  <CommentPin    userId={thread.comments[0]?.userId}    style={{      position: "absolute",      left: thread.metadata.x,      top: thread.metadata.y,    }}  /></FloatingThread>
FloatingThread with CommentPin
Props

In addition to all Thread props:

  • childrenReactNodeRequired

    The element which opens the floating thread.

  • defaultOpenboolean

    Whether the floating thread is initially open.

  • openboolean

    Whether the floating thread is currently open.

  • onOpenChangefunction

    The event handler called when the open state changes.

  • side"top" | "right" | "bottom" | "left"Default is "right"

    The preferred side of the trigger to render the floating thread on.

  • sideOffsetnumberDefault is 6

    The side offset in pixels from the trigger.

  • align"start" | "center" | "end"Default is "start"

    How the floating thread is aligned against its trigger.

  • alignOffsetnumber

    The alignment offset in pixels.

Composer

Displays a composer for creating threads or comments.

<Composer />
Composer

By default, submitting the composer will create a new thread.

import { Composer } from "@liveblocks/react-ui";
// Creates a new threadfunction Component() { return <Composer />;}
Adding thread metadata

If you’d like to attach custom metadata to the newly created thread, you can add a metadata prop.

import { Composer } from "@liveblocks/react-ui";
// Creates a new thread with custom metadatafunction Component() { return ( <Composer metadata={{ // Custom metadata here, e.g. colors, coordinates color: "purple", x: 80, y: 120, }} /> );}
Typed metadata

You can use TypeScript to type your custom metadata by editing your config file. Metadata properties can be string, number, or boolean.

liveblocks.config.ts
declare global {  interface Liveblocks {    // Set your custom metadata types    ThreadMetadata: {      // Example types, e.g. colors, coordinates      color: string;      x: number;      y: number;    };    CommentMetadata: {      // Example types, e.g. tags, context, external IDs      tag?: string;      spam: boolean;      slackMessageTs: string;    };
// Other types // ... }}
Creating private threads

Threads are public by default. If you’d like the composer to create private threads, you can add a visibility prop.

import { Composer } from "@liveblocks/react-ui";
// Creates a new private threadfunction Component() { return <Composer visibility="private" />;}

Permissions are taken into account when threads are created and retrieved. A user without write access to private threads can’t create a private thread, and users without read access to private threads won’t receive private threads from useThreads.

Private threads are only available on Team and Enterprise plans.

Replying to a thread

If you provide a threadId, then submitting the composer will add a new reply to the thread.

import { Composer } from "@liveblocks/react-ui";
// Adds a new comment to a threadfunction Component({ threadId }) { return <Composer threadId={threadId} />;}
Adding comment metadata

If you’d like to attach custom metadata to a reply, you can add a commentMetadata prop. This prop is typed as CommentMetadata.

liveblocks.config.ts
declare global {  interface Liveblocks {    CommentMetadata: {      tag?: string;      spam: boolean;      slackMessageTs: string;    };  }}
import { Composer } from "@liveblocks/react-ui";
// Creates a new reply to an existing thread with custom metadatafunction Component({ threadId }) { return ( <Composer threadId={threadId} commentMetadata={{ // Custom metadata here, e.g. tags, context, external IDs tag: "important", spam: false, }} /> );}
Modifying a comment

If you provide both a threadId and a commentId, then submitting the composer will edit the comment.

import { Composer } from "@liveblocks/react-ui";
// Edits an existing commentfunction Component({ threadId, commentId }) { return <Composer threadId={threadId} commentId={commentId} />;}
Custom behavior

If you’d like to customize submission behavior, you can use event.preventDefault() in onComposerSubmit to disable the default behavior and call comment and thread mutation methods manually.

import { Composer } from "@liveblocks/react-ui";import { useEditComment, useAddReaction } from "@liveblocks/react/suspense";
// Custom submission behavior (edits a comment and adds a reaction)function Component({ threadId, commentId }) { const editComment = useEditComment(); const addReaction = useAddReaction();
return ( <Composer onComposerSubmit={({ body, attachments }, event) => { event.preventDefault();
// Example mutations editComment({ threadId, commentId, body, attachments }); addReaction({ threadId, commentId, emoji: "✅" });
// Other custom behavior // ... }} /> );}

Learn more about mutation hooks under @liveblocks/react.

Props
  • threadIdstring

    The ID of the thread to reply to or to edit a comment in.

  • commentIdstring

    The ID of the comment to edit.

  • metadataThreadMetadata

    The metadata of the thread to create.

  • visibility"public" | "private"Default is "public"

    Whether to create a public or private thread. Only applies when creating a new thread, and requires write access to the selected visibility.

  • commentMetadataCommentMetadata | Partial<CommentMetadata>

    The metadata of the comment to create or edit.

  • onComposerSubmitfunction

    The event handler called when the composer is submitted.

  • blurOnSubmitbooleanDefault is true

    Whether to blur the composer editor when the composer is submitted.

  • defaultValueCommentBody

    The composer’s initial value.

  • defaultAttachmentsCommentAttachment[]

    The composer’s initial attachments.

  • 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.

  • showAttachmentsbooleanDefault is true

    Whether to show and allow adding attachments.

  • showFormattingControlsbooleanDefault is true

    Whether to show formatting controls (e.g. a floating toolbar with formatting toggles when selecting text)

  • 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.

FloatingComposer

Displays a floating composer attached to a trigger element.

<FloatingComposer>  <button>Add comment</button></FloatingComposer>
FloatingComposer

Use metadata to attach context to the thread when submitting (for example canvas coordinates or table cell IDs).

FloatingComposer can be combined with CommentPin in canvas-like UIs.

<FloatingComposer metadata={{ x: 120, y: 80 }}>  <CommentPin    style={{      position: "absolute",      left: 120,      top: 80,    }}  /></FloatingComposer>
FloatingComposer with CommentPin
Props

In addition to all Composer props (except collapsed, onCollapsedChange, and defaultCollapsed):

  • childrenReactNodeRequired

    The element which opens the floating composer.

  • defaultOpenboolean

    Whether the floating composer is initially open.

  • openboolean

    Whether the floating composer is currently open.

  • onOpenChangefunction

    The event handler called when the open state changes.

  • side"top" | "right" | "bottom" | "left"Default is "right"

    The preferred side of the trigger to render the floating composer on.

  • sideOffsetnumberDefault is 6

    The side offset in pixels from the trigger.

  • align"start" | "center" | "end"Default is "start"

    How the floating composer is aligned against its trigger.

  • alignOffsetnumber

    The alignment offset in pixels.

Comment

Displays a single comment.

<Comment comment={comment} />
Comment

Map through thread.comments to render each comment in a thread. Threads can be retrieved with useThreads.

import { Comment } from "@liveblocks/react-ui";import { ThreadData } from "@liveblocks/client";
// Renders a list of comments attach to the specified `thread`function Component({ thread }: { thread: ThreadData }) { return ( <> {thread.comments.map((comment) => ( <Comment key={comment.id} comment={comment} /> ))} </> );}
Custom thread components

Comment can be used in combination with Composer to create a custom thread component. The composer in this example is used to reply to the existing thread.

import { Comment, Composer } from "@liveblocks/react-ui";import { ThreadData } from "@liveblocks/client";import { useThreads } from "@liveblocks/react/suspense";
// Renders a list of comments and a composer for adding new commentsfunction CustomThread({ thread }: { thread: ThreadData }) { return ( <> {thread.comments.map((comment) => ( <Comment key={comment.id} comment={comment} /> ))} <Composer threadId={thread.id} /> </> );}
// Renders a list of custom thread componentsfunction Component() { const { threads } = useThreads();
return ( <> {threads.map((thread) => ( <CustomThread key={thread.id} /> ))} </> );}
Props
  • commentCommentDataRequired

    The comment to display.

  • avatarReactNode

    The comment’s avatar. Can be combined with Comment.Avatar to easily follow default styles.

  • authorReactNode

    The comment’s author. Can be combined with Comment.Author to easily follow default styles.

  • dateReactNode

    The comment’s date. Can be combined with Comment.Date to easily follow default styles, or the Timestamp primitive for more control.

  • showActionsboolean | "hover"Default is "hover"

    How to show or hide the actions.

  • showReactionsbooleanDefault is true

    Whether to show reactions.

  • showAttachmentsbooleanDefault is true

    Whether to show attachments.

  • showComposerFormattingControlsbooleanDefault is true

    Whether to show the composer’s formatting controls when editing the comment.

  • indentContentbooleanDefault is true

    Whether to indent the comment’s content.

  • additionalContentReactNode

    Additional content to display below the comment’s body.

  • bodyReactNode | (props) => ReactNode

    Override only the comment’s rich-text body. Receives the comment data and the default content as children.

  • showDeletedbooleanDefault is false

    Whether to show the comment if it was deleted. If set to false, it will render deleted comments as null.

  • dropdownItemsReactNode | (props) => ReactNode

    Add (or change) items to display in the comment’s dropdown.

  • childrenReactNode | (props) => ReactNode

    Override the comment’s content. Receives the comment data and the default content as children.

  • onCommentEditfunction

    The event handler called when the comment is edited.

  • onCommentDeletefunction

    The event handler called when the comment is deleted.

  • onAuthorClickfunction

    The event handler called when clicking on the author.

  • onMentionClickfunction

    The event handler called when clicking on a mention.

  • onAttachmentClickfunction

    The event handler called when clicking on a comment’s attachment.

  • overridesPartial<GlobalOverrides & CommentOverrides & ComposerOverrides>

    Override the component’s strings.

Comment.Avatar

Displays a comment’s avatar. Use this within the avatar prop to follow default styles while customizing the avatar.

<Comment  comment={comment}  avatar={    <div className="custom-avatar-wrapper">      <Comment.Avatar userId={comment.userId} />      <div className="custom-badge" />    </div>  }/>
Props
  • userIdstringRequired

    The user ID to display the avatar for.

Comment.Author

Displays a comment’s author. Use this within the author prop to follow default styles while customizing the author.

<Comment  comment={comment}  author={    <span className="custom-author-wrapper">      <Comment.Author userId={comment.userId} />      <span>Custom label</span>    </span>  }/>
Props
  • userIdstringRequired

    The user ID to display the author for.

Comment.DropdownItem

Displays a dropdown item in the comment’s dropdown menu. Use this within the dropdownItems prop to add custom actions.

<Comment  comment={comment}  dropdownItems={    <>      <Comment.DropdownItem        onSelect={() => console.log("Custom action")}        icon={<Icon.QuestionMark />}      >        Custom action      </Comment.DropdownItem>    </>  }/>
Props
  • iconReactNode

    An optional icon displayed in this dropdown item.

  • onSelectfunction

    The event handler called when the dropdown item is selected.

CommentPin

Displays a comment pin that can be used as a trigger for FloatingComposer and FloatingThread, or anywhere else in your UI.

<CommentPin />
<CommentPin userId="stacy@example.com" />
CommentPin

Set the userId prop to display an avatar inside the pin, for example to represent the thread’s author.

<CommentPin userId={thread.comments[0]?.userId} />

Use the corner prop to choose which corner the pin points to, it will move itself to always point to wherever it is positioned.

<CommentPin  corner="top-left"  style={{    position: "absolute",    left: thread.metadata.x,    top: thread.metadata.y,  }}/>

You can either use the size prop or override --lb-comment-pin-size with CSS to change the pin’s size.

<CommentPin size={40} />
<CommentPin className="[--lb-comment-pin-size:3rem]" />

Pass children to display custom content inside the pin. When children are provided, the userId prop is ignored.

<CommentPin>  <Icon.Plus /></CommentPin>
Props
  • corner"top-left" | "top-right" | "bottom-right" | "bottom-left"Default is "bottom-left"

    The corner that points to the comment position.

  • userIdstring

    The user ID to optionally display an avatar for. Ignored if children is provided.

  • sizestring | number

    The size of the pin.

  • paddingstring | number

    The padding within the pin.

  • childrenReactNode

    The content shown in the pin. If provided, the userId prop is ignored.

Primitives

Primitives are unstyled, headless components that can be used to create fully custom commenting experiences. We have a primitives example highlighting how to use them.

Using primitives with TypeScript

If you run into the Cannot find module '@liveblocks/react-ui/primitives' or its corresponding type declarations error, you should update your tsconfig.json’s moduleResolution property to "node16" or "nodenext" (or "bundler" if you’re on TS >=5).

Composition

All primitives are composable; they forward their props and refs, merge their classes and styles, and chain their event handlers.

Inspired by Radix (and powered by its Slot utility), most of the primitives also support an asChild prop to replace the rendered element by any provided child, and both set of props will be merged.

import { Button } from "@/my-design-system";
// Use the default <button> element<Composer.Submit disabled>Send</Composer.Submit>;
// Use an existing custom <Button> component<Composer.Submit disabled asChild> <Button variant="primary">Send</Button></Composer.Submit>;

Learn more about this concept on Radix’s composition guide.

Composer

Used to render a composer for creating, or editing, threads and comments.

<Composer.Form>  <Composer.AttachmentsDropArea />  <Composer.Editor    components={{      Mention: () => <Composer.Mention />,      MentionSuggestions: () => (        <Composer.Suggestions>          <Composer.SuggestionsList>            <Composer.SuggestionsListItem />          </Composer.SuggestionsList>        </Composer.Suggestions>      ),      Link: () => <Composer.Link />,    }}  />  <Composer.AttachFiles />  <Composer.Submit /></Composer.Form>

Combine with useCreateThread to render a composer that creates threads.

import {  Composer,  CommentBodyLinkProps,  CommentBodyMentionProps,  ComposerEditorMentionSuggestionsProps,  ComposerSubmitComment,} from "@liveblocks/react-ui/primitives";import { useCreateThread, useUser } from "@liveblocks/react/suspense";import { FormEvent } from "react";
// Render a custom composer that creates a thread on submitfunction MyComposer() { const createThread = useCreateThread();
function handleComposerSubmit( { body, attachments }: ComposerSubmitComment, event: FormEvent<HTMLFormElement> ) { event.preventDefault();
// Create a new thread const thread = createThread({ body, attachments, metadata: {}, commentMetadata: {}, }); }
return ( <Composer.Form onComposerSubmit={handleComposerSubmit}> <Composer.Editor components={{ Mention, MentionSuggestions, Link, }} /> <Composer.Submit>Create thread</Composer.Submit> </Composer.Form> );}
// Render a mention in the composer's editor, e.g. "@Emil Joyce"function Mention({ mention }: CommentBodyMentionProps) { return <Comment.Mention>@{mention.id}</Comment.Mention>;}
// Render a list of mention suggestions, used after typing "@" in the editorfunction MentionSuggestions({ mentions, selectedMentionId,}: ComposerEditorMentionSuggestionsProps) { return ( <Composer.Suggestions> <Composer.SuggestionsList> {mentions.map((mention) => { switch (mention.kind) { case "user": return ( <UserMentionSuggestion key={mention.id} userId={mention.id} /> ); case "group": return ( <GroupMentionSuggestion key={mention.id} groupId={mention.id} /> ); } })} </Composer.SuggestionsList> </Composer.Suggestions> );}
// Render a single mention suggestion from a `userId`function UserMentionSuggestion({ userId }: { userId: string }) { const { user } = useUser(userId);
return ( <Composer.SuggestionsListItem value={user.id}> <img src={user.avatar} alt={user.name} /> {user.name} </Composer.SuggestionsListItem> );}
// Render a single mention suggestion from a `groupId`function GroupMentionSuggestion({ groupId }: { groupId: string }) { const { group } = useGroupInfo(groupId);
return ( <Composer.SuggestionsListItem value={group.id}> <img src={group.avatar} alt={group.name} /> {group.name} </Composer.SuggestionsListItem> );}
// Render a link in the composer's editor, e.g. "https://liveblocks.io"function Link({ href, children }: CommentBodyLinkProps) { return <Comment.Link href={href}>{children}</Comment.Link>;}
Composer.Form

Surrounds the composer’s content and handles submissions. By default, no action occurs when the composer is submitted. You must create your own mutations within onComposerSubmit for creating threads, creating comments, editing comments, etc.

<Composer.Form  onComposerSubmit={({ body, attachments }) => {    // Mutate your comments    // ...  }}>  {/* ... */}</Composer.Form>
  • defaultAttachmentsCommentAttachment[]

    The composer’s initial attachments.

  • pasteFilesAsAttachmentsbooleanDefault is false

    Whether to create attachments when pasting files into the editor.

  • preventUnsavedChangesbooleanDefault is true

    When preventUnsavedChanges is set on your Liveblocks client on LiveblocksProvider, then closing a browser tab will be prevented when there are unsaved changes. By default, that will include draft text or attachments that are being uploaded via this composer, but not submitted yet. If you want to prevent unsaved changes with Liveblocks, but not for this composer, you can opt-out this composer instance by setting this prop to false.

  • blurOnSubmitbooleanDefault is true

    Whether to blur the editor when the form is submitted.

  • onComposerSubmitfunction

    The event handler called when the form is submitted.

  • disabledbooleanDefault is false

    Whether the composer is disabled.

  • asChildbooleanDefault is false

    Replace the rendered element by the one passed as a child.

Composer.Editor

Displays the composer’s editor.

<Composer.Editor placeholder="Write a comment…" />
  • defaultValueCommentBody

    The editor’s initial value.

  • placeholderstring

    The text to display when the editor is empty.

  • disabledboolean

    Whether the editor is disabled.

  • autoFocusboolean

    Whether to focus the editor on mount.

  • dir"ltr" | "rtl"

    The reading direction of the editor and related elements.

  • componentsPartial<ComposerEditorComponents>

    The components displayed within the editor.

AttributeValue
data-focusedPresent when the component is focused.
data-disabledPresent when the component is disabled.
components

The components displayed within the editor.

  • MentionComponentType<ComposerEditorMentionProps>

    The component used to display mentions. Defaults to the mention’s id prefixed by an @.

  • MentionSuggestionsComponentType<ComposerEditorMentionSuggestionProps>

    The component used to display mention suggestions. Defaults to a list of the suggested mentions’ id.

  • LinkComponentType<ComposerEditorLinkProps>

    The component used to display links. Defaults to the link’s children property.

  • FloatingToolbarComponentType<ComposerEditorFloatingToolbarProps>

    The component used to display a floating toolbar attached to the selection.

Mention

The component used to display mentions.

<Composer.Editor  components={{    Mention: ({ mention, isSelected }) => (      <Composer.Mention>@{mention.id}</Composer.Mention>    ),  }}/>
  • mentionMentionData

    The mention to display.

  • isSelectedboolean

    Whether the mention is selected.

MentionSuggestions

The component used to display mention suggestions.

  • mentionsMentionData[]

    The list of suggested mentions.

  • selectedMentionIdstring

    The currently selected mention’s ID.

<Composer.Editor  components={{    MentionSuggestions: () => (      <Composer.Suggestions>        <Composer.SuggestionsList>          <Composer.SuggestionsListItem />        </Composer.SuggestionsList>      </Composer.Suggestions>    ),  }}/>
Link

The component used to display links.

<Composer.Editor  components={{    Link: ({ href, children }) => <Composer.Link>{children}</Composer.Link>,  }}/>
  • hrefstring

    The link’s absolute URL.

  • childrenReactNode

    The link’s content.

FloatingToolbar

Displays a floating toolbar attached to the selection within Composer.Editor.

<Composer.Editor  components={{    FloatingToolbar: () => (      <Composer.FloatingToolbar>        <Composer.MarkToggle mark="bold">Bold</Composer.MarkToggle>        <Composer.MarkToggle mark="italic">Italic</Composer.MarkToggle>      </Composer.FloatingToolbar>    ),  }}/>
Composer.Mention

Displays mentions within Composer.Editor.

<Composer.Mention>@{mention.id}</Composer.Mention>
  • asChildbooleanDefault is false

    Replace the rendered element by the one passed as a child.

AttributeValue
data-selectedPresent when the mention is selected.
Composer.Suggestions

Contains suggestions within Composer.Editor.

<Composer.Suggestions>{/* ... */}<Composer.Suggestions>
  • asChildbooleanDefault is false

    Replace the rendered element by the one passed as a child.

Composer.SuggestionsList

Displays a list of suggestions within Composer.Editor.

<Composer.SuggestionsList>  {mentions.map((mention) => (    <Composer.SuggestionsListItem key={mention.id} value={mention.id}>      @{mention.id}    </Composer.SuggestionsListItem>  ))}</Composer.SuggestionsList>
  • asChildbooleanDefault is false

    Replace the rendered element by the one passed as a child.

Composer.SuggestionsListItem

Displays a suggestion within Composer.SuggestionsList.

<Composer.SuggestionsListItem key={mention.id} value={mention.id}>  @{mention.id}</Composer.SuggestionsListItem>
  • valuestringRequired

    The suggestion’s value.

  • asChildbooleanDefault is false

    Replace the rendered element by the one passed as a child.

AttributeValue
data-selectedPresent when the item is selected.
Composer.Link

Displays links within Composer.Editor.

<Composer.Link href={href}>{children}</Composer.Link>
  • asChildbooleanDefault is false

    Replace the rendered element by the one passed as a child.

Composer.Submit

A button to submit the composer.

<Composer.Submit>Send</Composer.Submit>
  • asChildbooleanDefault is false

    Replace the rendered element by the one passed as a child.

Composer.FloatingToolbar

Displays a floating toolbar attached to the selection within Composer.Editor.

<Composer.FloatingToolbar>  <Composer.MarkToggle mark="bold">Bold</Composer.MarkToggle>  <Composer.MarkToggle mark="italic">Italic</Composer.MarkToggle></Composer.FloatingToolbar>
  • asChildbooleanDefault is false

    Replace the rendered element by the one passed as a child.

Composer.MarkToggle

A toggle button which toggles a specific text mark.

<Composer.MarkToggle mark="bold">Bold</Composer.MarkToggle>
  • markComposerBodyMarkRequired

    The text mark to toggle.

  • onValueChangefunction

    The event handler called when the mark is toggled.

  • asChildbooleanDefault is false

    Replace the rendered element by the one passed as a child.

Composer.AttachFiles

A button which opens a file picker to create attachments.

<Composer.AttachFiles>Attach files</Composer.AttachFiles>
  • asChildbooleanDefault is false

    Replace the rendered element by the one passed as a child.

Composer.AttachmentsDropArea

A drop area which accepts files to create attachments.

<Composer.AttachmentsDropArea>Drop files here</Composer.AttachmentsDropArea>
  • asChildbooleanDefault is false

    Replace the rendered element by the one passed as a child.

Comment

Used to render a single comment.

<Comment.Body  components={{    Mention: Comment.Mention,    Link: Comment.Link,  }}/>

Map through thread.comments to render each comment in a thread. Threads can be retrieved with useThreads.

import {  Comment,  CommentBodyLinkProps,  CommentBodyMentionProps,} from "@liveblocks/react-ui/primitives";import { ThreadData } from "@liveblocks/client";
// Render custom comments in a thread. Pass a thread from `useThreads`.function MyComments({ thread }: { thread: ThreadData }) { return ( <> {thread.comments.map((comment) => ( <div key={comment.id}> <Comment.Body body={comment.body} components={{ Mention, Link, }} /> </div> ))} </> );}
// Render a mention in the comment, e.g. "@Emil Joyce"function Mention({ mention }: CommentBodyMentionProps) { return <Comment.Mention>@{mention.id}</Comment.Mention>;}
// Render a link in the comment, e.g. "https://liveblocks.io"function Link({ href, children }: CommentBodyLinkProps) { return <Comment.Link href={href}>{children}</Comment.Link>;}
Comment.Body

Displays a comment body.

<Comment.Body body={comment.body} />
  • bodyCommentBody

    The comment body to display. If not defined, the component will render null.

  • componentsPartial<CommentBodyComponents>

    The components displayed within the comment body.

  • asChildbooleanDefault is false

    Replace the rendered element by the one passed as a child.

components

The components displayed within the comment body.

  • MentionComponentType<CommentBodyMentionProps>

    The component used to display mentions. Defaults to the mention’s id prefixed by an @.

  • LinkComponentType<CommentBodyLinkProps>

    The component used to display links. Defaults to the link’s children property.

Mention

The component used to display mentions.

<Comment.Body  components={{    Mention: ({ mention }) => <Comment.Mention>@{mention.id}</Comment.Mention>,  }}/>
  • mentionMentionData

    The mention to display.

Link

The component used to display links.

<Comment.Body  components={{    Link: ({ href, children }) => (      <Comment.Link href={href}>{children}</Comment.Link>    ),  }}/>
  • hrefstring

    The link’s absolute URL.

  • childrenReactNode

    The link’s content.

Comment.Mention

Displays mentions within Comment.Body.

<Comment.Mention>@{mention.id}</Comment.Mention>
  • asChildbooleanDefault is false

    Replace the rendered element by the one passed as a child.

Comment.Link

Displays links within Comment.Body.

<Comment.Link href={href}>{children}</Comment.Link>
  • asChildbooleanDefault is false

    Replace the rendered element by the one passed as a child.

Timestamp

Displays a formatted date, and automatically re-renders to support relative formatting. Defaults to relative formatting for nearby dates (e.g. “5 minutes ago” or "in 1 day") and a short absolute formatting for more distant ones (e.g. “25 Aug”).

<Timestamp date={new Date()} />

Use with comment.createdAt, comment.editedAt, or comment.deletedAt to display a human-readable time.

import { Timestamp, Comment } from "@liveblocks/react-ui/primitives";import { ThreadData } from "@liveblocks/client";
function MyComments({ thread }: { thread: ThreadData }) { return ( <> {thread.comments.map((comment) => ( <div key={comment.id}> <Timestamp date={comment.createdAt} /> <Comment.Body body={comment.body} components={/* ... */} /> </div> ))} </> );}
  • dateDate | string | numberRequired

    The date to display.

  • childrenfunction

    A function to format the displayed date. Defaults to a relative date formatting function.

  • titlestring | function

    The title attribute’s value or a function to format it. Defaults to an absolute date formatting function.

  • intervalnumber | falseDefault is 30000

    The interval in milliseconds at which the component will re-render. Can be set to false to disable re-rendering.

  • localestring

    The locale used when formatting the date. Defaults to the browser’s locale.

  • asChildbooleanDefault is false

    Replace the rendered element by the one passed as a child.

Duration

Displays a formatted duration, and automatically re-renders to if the duration is in progress. Defaults to a short format (e.g. “5s” or “1m 40s”).

<Duration duration={3 * 60 * 1000} />

Instead of providing a duration in milliseconds, you can also provide start and end dates for the duration via the from and to props. If only from is provided it means that the duration is in progress, and the component will re-render at an interval, customizable with the interval prop.

  • durationnumber

    The duration in milliseconds. If provided, from and to will be ignored.

  • fromDate | string | number

    The date at which the duration starts. If provided, duration will be ignored. If provided without to it means that the duration is in progress, and the component will re-render at an interval, customizable with the interval prop.

  • toDate | string | number

    The date at which the duration ends. If from is provided without to, Date.now() will be used.

  • childrenfunction

    A function to format the displayed date. Defaults to a short duration formatting function.

  • titlestring | function

    The title attribute’s value or a function to format it. Defaults to an longer duration formatting function.

  • intervalnumber | falseDefault is 500

    The interval in milliseconds at which the component will re-render if from is provided without to, meaning that the duration is in progress. Can be set to false to disable re-rendering.

  • localestring

    The locale used when formatting the duration. Defaults to the browser’s locale.

  • asChildbooleanDefault is false

    Replace the rendered element by the one passed as a child.

FileSize

Displays a formatted file size.

<FileSize size={100000} />

Use with attachment.size to display a human-readable file size.

import { FileSize } from "@liveblocks/react-ui/primitives";import { CommentData } from "@liveblocks/client";
function MyComment({ comment }: { comment: CommentData }) { return ( <div> {/* ... */}
{comment.attachments.map((attachment) => ( <div key={attachment.id}> {attachment.name} <FileSize size={attachment.size} /> </div> ))} </div> );}
  • sizenumberRequired

    The file size to display.

  • childrenfunction

    A function to format the displayed file size. Defaults to a human-readable file size formatting function.

  • localestring

    The locale used when formatting the file size. Defaults to the browser’s locale.

  • asChildbooleanDefault is false

    Replace the rendered element by the one passed as a child.

Emoji picker

Using Frimousse alongside useAddReaction, a package originally designed for Comments, you can easily add an emoji picker to your primitive Comments components.

import { EmojiPicker } from "frimousse";import { useAddReaction } from "@liveblocks/react/suspense";import { CommentData } from "@liveblocks/client";
export function MyEmojiPicker({ comment }: { comment: CommentData }) { const addReaction = useAddReaction();
return ( <EmojiPicker.Root onEmojiSelect={({ emoji }) => { addReaction({ threadId: comment.threadId, commentId: comment.id, emoji, }); }} > <EmojiPicker.Search /> <EmojiPicker.Viewport> <EmojiPicker.Loading>Loading…</EmojiPicker.Loading> <EmojiPicker.Empty>No emoji found.</EmojiPicker.Empty> <EmojiPicker.List /> </EmojiPicker.Viewport> </EmojiPicker.Root> );}

Find a full code snippet of this in our Comments primitives example.

Emoji reactions

A list of clickable emoji reactions can be created using the useAddReaction, useRemoveReaction, and useSelf hooks.

import { CommentData } from "@liveblocks/client";import {  useAddReaction,  useRemoveReaction,  useSelf,} from "@liveblocks/react/suspense";
export function MyEmojiReactions({ comment }: { comment: CommentData }) { const userId = useSelf().id; const addReaction = useAddReaction(); const removeReaction = useRemoveReaction();
return ( <> {comment.reactions.map((reaction) => { const hasPicked = reaction.users.some((user) => user.id === userId); const reactionObject = { threadId: comment.threadId, commentId: comment.id, emoji: reaction.emoji, };
return ( <button key={reaction.emoji} onClick={() => hasPicked ? removeReaction(reactionObject) : addReaction(reactionObject) } data-picked={hasPicked || undefined /* Use for CSS styling */} > {reaction.emoji} {reaction.users.length} </button> ); })} </> );}

Hooks

useComposer

Returns states and methods related to the composer. Can only be used within the Composer.Form primitive. All values listed below.

import { useComposer } from "@liveblocks/react-ui/primitives";
const { isEmpty, attachments, submit /* ... */ } = useComposer();
Custom composer behavior

useComposer can be used in combination with Composer primitives to create a custom composer, and control its behavior. For example, createMention allows you to create a button which focuses the editor, adds @, and opens the mention suggestions dropdown.

import { Composer, useComposer } from "@liveblocks/react-ui/primitives";import { useCreateThread } from "@liveblocks/react/suspense";
function MyComposer() { const createThread = useCreateThread();
return ( <Composer.Form onComposerSubmit={({ body, attachments }) => { const thread = createThread({ body, attachments, metadata: {}, commentMetadata: {}, }); }} > <Editor /> </Composer.Form> );}
function Editor() { const { createMention } = useComposer();
return ( <> <Composer.Editor components={/* Your custom component parts */} /> <button onClick={createMention}>Add mention</button> </> );}
Handle attachments

When using primitives, Composer.AttachFiles and Composer.AttachmentsDropArea add attachments to the composer, but they’re not rendered without useComposer. The attachments array can be used to render the current attachments, and removeAttachment allows you to remove them.

import { Composer, useComposer } from "@liveblocks/react-ui/primitives";import { useCreateThread } from "@liveblocks/react/suspense";
function MyComposer() { const createThread = useCreateThread();
return ( <Composer.Form onComposerSubmit={({ body, attachments }) => { const thread = createThread({ body, attachments, metadata: {}, commentMetadata: {}, }); }} > <Composer.Editor components={/* Your custom component parts */} /> <MyComposerAttachments /> <Composer.AttachFiles>Attach Files</Composer.AttachFiles> <Composer.Submit>Submit</Composer.Submit> </Composer.Form> );}
function MyComposerAttachments() { const { attachments, removeAttachment } = useComposer();
return ( <div> {attachments.map((attachment) => ( <div key={attachment.id}> {attachment.name} ({attachment.status}) <button onClick={() => removeAttachment(attachment.id)}> Remove </button> </div> ))} </div> );}
Values
  • isDisabledboolean

    Whether the composer is currently disabled.

  • isFocusedboolean

    Whether the editor is currently focused.

  • isEmptyboolean

    Whether the editor is currently empty.

  • canSubmitboolean

    Whether the composer can currently be submitted.

  • submitfunction

    Submit the editor programmatically.

  • clearfunction

    Clear the editor programmatically.

  • selectfunction

    Select the editor programmatically.

  • focusfunction

    Focus the editor programmatically.

  • blurfunction

    Blur the editor programmatically.

  • marksComposerBodyMarks

    Which text marks are currently active and which aren’t.

  • toggleMarkfunction

    Toggle a specific text mark.

  • createMentionfunction

    Start creating a mention at the current selection.

  • insertTextfunction

    Insert text at the current selection.

  • attachFilesfunction

    Open a file picker programmatically to create attachments.

  • attachmentsComposerAttachment[]

    The composer’s current attachments.

  • removeAttachmentfunction

    Remove an attachment by its ID.

Other hooks

Other Comments hooks are part of @liveblocks/react, you can find them on the React API reference page.

Notifications

Default components

InboxNotification

Displays a single inbox notification.

<InboxNotification inboxNotification={inboxNotification} />
InboxNotification

Map through inboxNotifications with useInboxNotifications to render a list of the room’s notifications.

import { InboxNotification } from "@liveblocks/react-ui";import { useInboxNotifications } from "@liveblocks/react/suspense";
function Component() { const { inboxNotifications } = useInboxNotifications();
return ( <> {inboxNotifications.map((inboxNotification) => ( <InboxNotification key={inboxNotification.id} inboxNotification={inboxNotification} /> ))} </> );}
Rendering notification kinds differently

Different kinds of notifications are available, for example thread which is triggered when using Comments, or $myCustomNotification which would be a custom notification you’ve triggered manually. You can choose to render each notification differently.

<InboxNotification  inboxNotification={inboxNotification}  kinds={{    thread: (props) => (      <InboxNotification.Thread {...props} showRoomName={false} />    ),    $myCustomNotification: (props) => (      <InboxNotification.Custom        {...props}        title="New notification"        aside={<InboxNotification.Icon></InboxNotification.Icon>}      >        My custom notification      </InboxNotification.Custom>    ),  }}/>

Adding these two properties to kinds will overwrite the default component that’s displayed for those two notification types. Using InboxNotification.Thread and InboxNotification.Custom in this way allow you to easily create components that fit into the existing design system, whilst still adding lots of customization. However, it’s also valid to render any custom JSX.

<InboxNotification  inboxNotification={inboxNotification}  kinds={{    $myCustomNotification: (props) => <div>New notification</div>,  }}/>
Typing custom notifications

To type custom notifications, edit the ActivitiesData type in your config file.

liveblocks.config.ts
declare global {  interface Liveblocks {    // Custom activities data for custom notification kinds    ActivitiesData: {      // Example, a custom $alert kind      $alert: {        title: string;        message: string;      };    };
// Other kinds // ... }}

Your activities data is now correctly typed in inline functions.

<InboxNotification  inboxNotification={inboxNotification}  kinds={{    $alert: (props) => {      // `title` and `message` are correctly typed, as defined in your config      const { title, message } = props.inboxNotification.activities[0].data;
return ( <InboxNotification.Custom {...props} title={title}