Skip to main content

Chat Conversation Block

ChatConversation is a responsive, highly customizable messaging interface built on MUI Stack and TextField inputs, supporting day-grouped message streams, mobile-optimized overflow menus, infinite-scroll pagination hooks, and layout composition.

Installation

npm install @nodeblocks/frontend-chat-conversation-block

What You Need

ItemWhy it matters
messagesArray of message bubbles to display; the block groups and sorts them by createdAt
chatViewChat panel configurations controlling headings, avatars, mobile overflow menus, and loader indicators
labelsText labels for the heading title, optional subheading, and desktop action button
placeholdersInput placeholders (such as message comment input text)
onNavigateCallback hook triggered when clicking navigation headers or linking avatars
Controlled component

ChatConversation is presentational and does not manage active state lists. Maintain local messages state in your application, update the array inside onMessageSubmit, and toggle page load spinners with chatView.isLoading.

Code Examples

Live Editor
function Example() {
  const [messages, setMessages] = React.useState([
    {
      id: 'msg-1',
      title: 'John Doe',
      content: 'Hey! Are we still on for the project review today?',
      createdAt: new Date(Date.now() - 3600000).toISOString(),
      isOwnMessage: false,
    },
    {
      id: 'msg-2',
      title: 'You',
      content: 'Yes! I have the slides ready.',
      createdAt: new Date(Date.now() - 1800000).toISOString(),
      isOwnMessage: true,
    },
  ]);

  const [isLoading, setIsLoading] = React.useState(false);
  const [lastEvent, setLastEvent] = React.useState('Ready');

  const handleMessageSubmit = text => {
    setLastEvent(`Sent: ${text}`);
    // Add user message
    const userMsg = {
      id: `msg-${Date.now()}`,
      title: 'You',
      content: text,
      createdAt: new Date().toISOString(),
      isOwnMessage: true,
    };
    setMessages(prev => [...prev, userMsg]);

    // Simulate reply
    setIsLoading(true);
    setTimeout(() => {
      setMessages(prev => [
        ...prev,
        {
          id: `reply-${Date.now()}`,
          title: 'John Doe',
          content: 'Awesome, see you in the meeting room!',
          createdAt: new Date().toISOString(),
          isOwnMessage: false,
          avatar: {children: 'JD'},
        },
      ]);
      setIsLoading(false);
    }, 1500);
  };

  return (
    <div style={{height: 500, border: '1px solid #e0e0e0', borderRadius: 8, overflow: 'hidden'}}>
      <ChatConversation
        chatView={{
          heading: {
            avatar: {children: 'JD'},
            buttonHref: '#home',
          },
          isLoading: isLoading,
        }}
        labels={{
          chatViewHeadingButtonText: 'Home',
          chatViewHeadingText: 'Project Chat',
          chatViewHeadingSubText: 'Review team',
        }}
        placeholders={{
          commentInput: 'Type your message...',
        }}
        messages={messages}
        onMessageSubmit={handleMessageSubmit}
        onNavigate={url => setLastEvent(`Navigating to: ${url}`)}
      />
      <div style={{marginTop: 8, fontSize: 12, color: '#666'}}>{lastEvent}</div>
    </div>
  );
}
Result
Loading...

Important Props

Core Props

PropTypeRequiredDefaultDescription
messagesArray<{ id: string; content: string; createdAt: string; title?: string; isOwnMessage?: boolean; avatar?: object }>Yes-Array of chat message records grouped and sorted by createdAt
onMessageSubmit(text: string) => voidNoundefinedCallback triggered when submitting input via the text field
onInputChange(text: string) => voidNoundefinedCallback triggered when comment input text is edited

Message Object

Configure properties inside the messages list array:

PropertyTypeRequiredDefaultDescription
idstringYes-Unique message record identifier
contentstringYes-Core content text displayed inside the chat bubble
createdAtstringYes-Date-time timestamp (ISO string format) used for day separator groupings
isOwnMessagebooleanNofalseHighlight own comments aligning bubbles to the right viewport edge when true
titlestringNoundefinedDisplay author header title above the chat bubble
avatarobjectNoundefinedIndividual comment Avatar config: MUI Avatar props plus optional href, isLoading, and link/click behavior (component="a" is supported)

Content Props

PropTypeRequiredDefaultDescription
chatView{ heading?: { avatar?: object; buttonHref: string; onBack?: () => void; menuItems?: Array<{ label: string; href?: string; icon?: ReactNode; onClick?: (e: MouseEvent<HTMLElement>) => void }> }; isLoading?: boolean; onScrollTop?: (earliestId: string) => void }Yes-Panel layouts configuring headings, mobile menus, back clicks, and infinite scrolling; heading avatars accept standard MUI Avatar props, including linked avatars via component="a" + href
labels{ chatViewHeadingButtonText?: string; chatViewHeadingText?: string; chatViewHeadingSubText?: string }Yes-Main heading labels and exit button text
placeholders{ commentInput: string }Yes-Wording configurations for inputs
onNavigate(url: string) => voidYes-Standard redirect hook mapping clicked elements and settings
commentInput{ isDisabled?: boolean; errorText?: string }NoundefinedConfiguration for bottom comment inputs
commentSubmitButton{ isDisabled?: boolean }NoundefinedActive states for the send button

chatView Structure

Configure layout heading indicators inside chatView:

PropertyTypeRequiredDefaultDescription
heading{ avatar?: object; buttonHref: string; onBack?: () => void; menuItems?: ChatConversationHeadingMenuItem[] }NoundefinedConfiguration for heading actions. buttonHref is required when heading is provided.
isLoadingbooleanNofalseDisplays top pagination loaders or full viewport spinners during fetches
onScrollTop(earliestId: string) => voidNoundefinedCallback triggered when scrolling to top (used for loading previous history)

Option configurations for heading overflow dropdown menu lists (menuItems):

PropertyTypeRequiredDefaultDescription
labelstringYes-Label text displayed on the menu option
hrefstringNoundefinedRedirect target mapped via onNavigate
iconReactNodeNoundefinedIcon element prepended to menu option labels
onClick(e: MouseEvent<HTMLElement>) => voidNoundefinedHandler triggered when clicking the option

Layout and Composition Props

PropTypeRequiredDefaultDescription
childrenBlocksOverride | ReactNodeNoundefinedCompound child elements or override function child
classNamestringNoundefinedStyling class applied on the outer container
sxSxPropsNoundefinedMUI SX styling overrides passed to the outer Stack

ChatConversation inherits all StackProps (except children). Default block order is ['heading', 'body'] (defaultBlockOrder).

Default UI Blocks

BlockBuilt onNotes
ChatConversation (root)StackStacks heading and body panels vertically
ChatConversation.HeadingStackDisplays back button, avatar details, combined titles, and exit buttons
ChatConversation.BodyStackGroups messages by day, sorts by createdAt, triggers onScrollTop boundaries, and mounts the input

TypeScript

import {ChatConversation} from '@nodeblocks/frontend-chat-conversation-block';
import type {MouseEvent, ReactNode} from 'react';

type ChatMessage = {
id: string;
content: string;
createdAt: string;
title?: string;
isOwnMessage?: boolean;
avatar?: {
component?: 'a';
onClick?: (event: MouseEvent<HTMLElement>) => void;
children?: ReactNode;
src?: string;
srcSet?: string;
href?: string;
isLoading?: boolean;
sx?: object;
};
};

type MenuItem = {
label: string;
href?: string;
icon?: ReactNode;
onClick?: (event: MouseEvent<HTMLElement>) => void;
};

const messages: ChatMessage[] = [
{
id: 'msg_001',
title: 'Customer support',
content: 'We are processing your request. Please hold on.',
createdAt: '2026-05-27T02:00:00Z',
isOwnMessage: false,
avatar: {children: 'CS', sx: {bgcolor: 'primary.main'}},
},
];

const menuItems: MenuItem[] = [{label: 'View Profile', onClick: () => void 0}];

<ChatConversation
chatView={{
heading: {
buttonHref: '/support',
menuItems,
},
isLoading: false,
}}
labels={{
chatViewHeadingButtonText: 'Exit',
chatViewHeadingText: 'Customer Care',
}}
placeholders={{
commentInput: 'Describe your issue...',
}}
messages={messages}
onMessageSubmit={text => void text}
onNavigate={url => void url}
/>;