The CometChatMessageBubble component is a versatile container component that renders chat messages with configurable view slots. It serves as the primary wrapper for displaying messages in the CometChat Angular UIKit, handling message alignment, options menus, status indicators, and content rendering based on message type.
Overview
The Message Bubble component provides a flexible architecture for rendering different message types while maintaining consistent styling and behavior:
Message Type Routing : Automatically renders appropriate bubble component based on message type (text, image, video, audio, file)
Configurable View Slots : Override any section (leading, header, content, footer, etc.) with custom templates
Message Options Menu : Context menu with hover/click behavior for message actions
Group vs 1-on-1 Handling : Automatically shows/hides avatar and sender name based on conversation type
Alignment Variants : Supports incoming (left), outgoing (right), and action (center) alignments
Status Indicators : Displays timestamps, read receipts, and edited labels
Reply Preview : Shows quoted message preview for replies
Accessibility : Full keyboard navigation and screen reader support
Message Bubble Structure
The Message Bubble is composed of several configurable view slots that can be customized independently:
+----------------------------------------------------------------------------+
| Message Bubble Structure |
+----------------------------------------------------------------------------+
| |
| +----------+ +--------------------------------------------------------+ |
| | | | +--------------------------------------------------+ | |
| | Leading | | | Header View | | |
| | View | | | (Sender Name) | | |
| | (Avatar) | | +--------------------------------------------------+ | |
| | | | +--------------------------------------------------+ | |
| | | | | Reply View | | |
| | | | | (Quoted Message) | | |
| | | | +--------------------------------------------------+ | |
| | | | +--------------------------------------------------+ | |
| | | | | Content View | | |
| | | | | (Message Content) | | |
| | | | | Text / Image / Video / Audio / File | | |
| | | | +--------------------------------------------------+ | |
| | | | +--------------------------------------------------+ | |
| | | | | Status Info View | | |
| | | | | (Timestamp, Receipts, Edited) | | |
| | | | +--------------------------------------------------+ | |
| | | | +--------------------------------------------------+ | |
| | | | | Bottom View | | |
| | | | | (Link Preview, etc.) | | |
| | | | +--------------------------------------------------+ | |
| +----------+ +--------------------------------------------------------+ |
| +----------------------------------------------------------+ |
| | Footer View | |
| | (Reactions) | |
| +----------------------------------------------------------+ |
| +----------------------------------------------------------+ |
| | Thread View | |
| | (Reply Count, Thread Icon) | |
| +----------------------------------------------------------+ |
| |
+----------------------------------------------------------------------------+
View Slot Descriptions
View Slot Description Default Content bubbleView Complete bubble override - replaces entire message bubble Header + Reply + Content + StatusInfo + Bottom + Footer leadingView Avatar section on the left side of incoming messages User avatar with online status headerView Sender information above the message content Sender name (shown in group chats) replyView Quoted message preview for reply messages Message preview component contentView Main message content area Text/Image/Video/Audio/File bubble statusInfoView Timestamp and delivery status Time, receipts, edited label bottomView Additional content below the message Link previews, load more button footerView Reactions and footer content Reaction emojis threadView Thread reply indicators Reply count and thread icon
Basic Usage
Simple Message Bubble
import { Component } from '@angular/core' ;
import { CometChat } from '@cometchat/chat-sdk-javascript' ;
import { CometChatMessageBubbleComponent , MessageBubbleAlignment } from '@cometchat/chat-uikit-angular' ;
@ Component ({
selector: 'app-message' ,
standalone: true ,
imports: [ CometChatMessageBubbleComponent ],
template: `
<cometchat-message-bubble
[message]="textMessage"
[alignment]="MessageBubbleAlignment.left">
</cometchat-message-bubble>
`
})
export class MessageComponent {
textMessage !: CometChat . TextMessage ;
MessageBubbleAlignment = MessageBubbleAlignment ;
}
See all 19 lines
Incoming vs Outgoing Messages
import { Component } from '@angular/core' ;
import { CometChat } from '@cometchat/chat-sdk-javascript' ;
import { CometChatMessageBubbleComponent , MessageBubbleAlignment } from '@cometchat/chat-uikit-angular' ;
@ Component ({
selector: 'app-message-list' ,
standalone: true ,
imports: [ CometChatMessageBubbleComponent ],
template: `
<!-- Incoming message (left-aligned) -->
<cometchat-message-bubble
[message]="incomingMessage"
[alignment]="MessageBubbleAlignment.left">
</cometchat-message-bubble>
<!-- Outgoing message (right-aligned) -->
<cometchat-message-bubble
[message]="outgoingMessage"
[alignment]="MessageBubbleAlignment.right">
</cometchat-message-bubble>
<!-- Action message (center-aligned) -->
<cometchat-message-bubble
[message]="actionMessage"
[alignment]="MessageBubbleAlignment.center">
</cometchat-message-bubble>
`
})
export class MessageListComponent {
incomingMessage !: CometChat . BaseMessage ;
outgoingMessage !: CometChat . BaseMessage ;
actionMessage !: CometChat . Action ;
MessageBubbleAlignment = MessageBubbleAlignment ;
}
See all 34 lines
With Message Options
import { Component } from '@angular/core' ;
import { CometChat } from '@cometchat/chat-sdk-javascript' ;
import {
CometChatMessageBubbleComponent ,
CometChatActionsIcon ,
MessageBubbleAlignment
} from '@cometchat/chat-uikit-angular' ;
@ Component ({
selector: 'app-interactive-message' ,
standalone: true ,
imports: [ CometChatMessageBubbleComponent ],
template: `
<cometchat-message-bubble
[message]="message"
[alignment]="MessageBubbleAlignment.left"
[options]="messageOptions"
(optionClick)="onOptionClick($event)">
</cometchat-message-bubble>
`
})
export class InteractiveMessageComponent {
message !: CometChat . BaseMessage ;
MessageBubbleAlignment = MessageBubbleAlignment ;
messageOptions : CometChatActionsIcon [] = [
new CometChatActionsIcon ({ id: 'reply' , title: 'Reply' , iconURL: 'assets/reply.svg' }),
new CometChatActionsIcon ({ id: 'copy' , title: 'Copy' , iconURL: 'assets/copy.svg' }),
new CometChatActionsIcon ({ id: 'delete' , title: 'Delete' , iconURL: 'assets/delete.svg' }),
];
onOptionClick ( option : any ) : void {
console . log ( 'Option clicked:' , option . id );
}
}
See all 35 lines
API Reference
Properties
Property Type Default Description messageCometChat.BaseMessagerequired The CometChat message object to render alignmentMessageBubbleAlignmentrightAlignment of the bubble: left (incoming), right (outgoing), center (action) groupCometChat.Group | nullnullGroup context for group conversations. Enables avatar and sender name display optionsArray<CometChatActionsIcon | CometChatActionsView>[]Message options to display in context menu quickOptionsCountnumber2Number of options to show directly on bubble before overflow textFormattersCometChatTextFormatter[][]Text formatters for text message content dateFormatCalendarObjectundefinedCustom date format for timestamp display translatedTextstringundefinedTranslated text to display for text messages reactionsRequestBuilderCometChat.ReactionsRequestBuilderundefinedCustom request builder for fetching reactions isSelectedbooleanfalseWhether the message bubble is currently selected ariaPosinsetnumberundefinedARIA position-in-set attribute for the message in the list ariaSetsizenumberundefinedARIA set-size attribute for the total message count
View Override Properties
Property Type Default Description bubbleViewTemplateRef<any>undefinedComplete bubble override - replaces entire message bubble leadingViewTemplateRef<any>undefinedCustom template for leading view (avatar section) headerViewTemplateRef<any>undefinedCustom template for header view (sender name section) replyViewTemplateRef<any>undefinedCustom template for reply view (quoted message preview) contentViewTemplateRef<any>undefinedCustom template for content view (main message content) bottomViewTemplateRef<any>undefinedCustom template for bottom view footerViewTemplateRef<any>undefinedCustom template for footer view (reactions) statusInfoViewTemplateRef<any>undefinedCustom template for status info view (timestamp, receipts) threadViewTemplateRef<any>undefinedCustom template for thread view
Display Control Properties
Property Type Default Description hideAvatarbooleanfalseWhether to hide the avatar hideSenderNamebooleanfalseWhether to hide the sender name hideReceiptsbooleanfalseWhether to hide read receipts hideTimestampbooleanfalseWhether to hide the timestamp showErrorbooleanfalseWhether to show error state indicator hideModerationViewbooleanfalseWhether to hide moderation status indicators disableInteractionbooleanfalseWhen true, disables all interactive elements within the bubble (context menu, reactions, etc.)
Events
Event Payload Type Description optionClickContextMenuItemEmitted when a message option is clicked avatarClickCometChat.UserEmitted when the avatar is clicked replyPreviewClickCometChat.BaseMessageEmitted when the reply preview is clicked threadRepliesClickCometChat.BaseMessageEmitted when the thread replies view is clicked reactionClick{ reaction: CometChat.ReactionCount, message: CometChat.BaseMessage }Emitted when a reaction is clicked reactionListItemClick{ reaction: CometChat.Reaction, message: CometChat.BaseMessage }Emitted when a reaction list item (user) is clicked mediaToggleCometChat.BaseMessageEmitted when Space key toggles media playback on audio/video bubbles messageActionsOpenCometChat.BaseMessageEmitted when message actions are triggered via keyboard
Bubble Parts Customization
Each bubble part can be customized independently using Angular TemplateRef. The customization follows a priority system:
Input template ref (passed directly to component) - Highest priority
Service configured view (via MessageBubbleConfigService) - Medium priority
Default rendering - Lowest priority
bubbleView
The bubbleView provides complete control over the entire message bubble. When set, it replaces all other views (header, content, footer, etc.).
Use Case : When you need a completely custom message layout that doesn’t follow the standard structure.
import { Component , TemplateRef , ViewChild } from '@angular/core' ;
import { CometChat } from '@cometchat/chat-sdk-javascript' ;
import {
CometChatMessageBubbleComponent ,
MessageBubbleAlignment
} from '@cometchat/chat-uikit-angular' ;
import { DatePipe } from '@angular/common' ;
@ Component ({
selector: 'app-custom-bubble' ,
standalone: true ,
imports: [ CometChatMessageBubbleComponent , DatePipe ],
template: `
<cometchat-message-bubble
[message]="message"
[alignment]="MessageBubbleAlignment.left"
[bubbleView]="customBubble">
</cometchat-message-bubble>
<ng-template #customBubble let-message>
<div class="custom-bubble">
<div class="custom-bubble__avatar">
<img [src]="message.getSender()?.getAvatar()" alt="Avatar" />
</div>
<div class="custom-bubble__content">
<span class="custom-bubble__sender">{{ message.getSender()?.getName() }}</span>
<p class="custom-bubble__text">{{ getMessageText(message) }}</p>
<span class="custom-bubble__time">{{ message.getSentAt() * 1000 | date:'shortTime' }}</span>
</div>
</div>
</ng-template>
` ,
styles: [ `
.custom-bubble {
display: flex;
gap: var(--cometchat-spacing-3);
padding: var(--cometchat-spacing-3);
background: var(--cometchat-background-color-02);
border-radius: var(--cometchat-radius-3);
}
.custom-bubble__avatar img {
width: 40px;
height: 40px;
border-radius: var(--cometchat-radius-max);
}
.custom-bubble__content {
display: flex;
flex-direction: column;
gap: var(--cometchat-spacing-1);
}
.custom-bubble__sender {
font: var(--cometchat-font-caption1-bold);
color: var(--cometchat-text-color-primary);
}
.custom-bubble__text {
font: var(--cometchat-font-body-regular);
color: var(--cometchat-text-color-primary);
margin: 0;
}
.custom-bubble__time {
font: var(--cometchat-font-caption2-regular);
color: var(--cometchat-text-color-tertiary);
}
` ]
})
export class CustomBubbleComponent {
message !: CometChat . BaseMessage ;
MessageBubbleAlignment = MessageBubbleAlignment ;
getMessageText ( message : CometChat . BaseMessage ) : string {
if ( message instanceof CometChat . TextMessage ) {
return message . getText ();
}
return '' ;
}
}
See all 76 lines
leadingView
The leadingView customizes the avatar section that appears on the left side of incoming messages in group conversations.
Use Case : Custom avatar styling, adding status badges, or showing additional user information.
import { Component , TemplateRef , ViewChild } from '@angular/core' ;
import { CometChat } from '@cometchat/chat-sdk-javascript' ;
import {
CometChatMessageBubbleComponent ,
MessageBubbleAlignment
} from '@cometchat/chat-uikit-angular' ;
@ Component ({
selector: 'app-custom-leading' ,
standalone: true ,
imports: [ CometChatMessageBubbleComponent ],
template: `
<cometchat-message-bubble
[message]="message"
[alignment]="MessageBubbleAlignment.left"
[group]="group"
[leadingView]="customLeading"
(avatarClick)="onAvatarClick($event)">
</cometchat-message-bubble>
<ng-template #customLeading let-message>
<div class="custom-leading"
tabindex="0"
role="button"
[attr.aria-label]="'View profile of ' + message.getSender()?.getName()">
<img
class="custom-leading__avatar"
[src]="message.getSender()?.getAvatar() || 'assets/default-avatar.png'"
[alt]="message.getSender()?.getName()" />
<span class="custom-leading__badge"
[class.custom-leading__badge--online]="message.getSender()?.getStatus() === 'online'">
</span>
</div>
</ng-template>
` ,
styles: [ `
.custom-leading {
position: relative;
cursor: pointer;
}
.custom-leading__avatar {
width: 36px;
height: 36px;
border-radius: var(--cometchat-radius-max);
border: 2px solid var(--cometchat-border-color-light);
}
.custom-leading__badge {
position: absolute;
bottom: 0;
right: 0;
width: 12px;
height: 12px;
border-radius: var(--cometchat-radius-max);
background: var(--cometchat-neutral-color-400);
border: 2px solid var(--cometchat-background-color-01);
}
.custom-leading__badge--online {
background: var(--cometchat-success-color);
}
` ]
})
export class CustomLeadingComponent {
message !: CometChat . BaseMessage ;
group !: CometChat . Group ;
MessageBubbleAlignment = MessageBubbleAlignment ;
onAvatarClick ( user : CometChat . User ) : void {
console . log ( 'Avatar clicked:' , user . getName ());
}
}
See all 70 lines
The headerView customizes the sender name section that appears above the message content in group conversations.
Use Case : Adding user roles, custom badges, or additional sender information.
import { Component , TemplateRef , ViewChild } from '@angular/core' ;
import { CometChat } from '@cometchat/chat-sdk-javascript' ;
import {
CometChatMessageBubbleComponent ,
MessageBubbleAlignment
} from '@cometchat/chat-uikit-angular' ;
@ Component ({
selector: 'app-custom-header' ,
standalone: true ,
imports: [ CometChatMessageBubbleComponent ],
template: `
<cometchat-message-bubble
[message]="message"
[alignment]="MessageBubbleAlignment.left"
[group]="group"
[headerView]="customHeader">
</cometchat-message-bubble>
<ng-template #customHeader let-message>
<div class="custom-header">
<span class="custom-header__name">{{ message.getSender()?.getName() }}</span>
@if (isAdmin(message.getSender())) {
<span class="custom-header__badge">Admin</span>
}
<span class="custom-header__status">• In meeting</span>
</div>
</ng-template>
` ,
styles: [ `
.custom-header {
display: flex;
align-items: center;
gap: var(--cometchat-spacing-2);
padding-bottom: var(--cometchat-spacing-1);
}
.custom-header__name {
font: var(--cometchat-font-caption1-bold);
color: var(--cometchat-text-color-primary);
}
.custom-header__badge {
font: var(--cometchat-font-caption2-medium);
color: var(--cometchat-static-white);
background: var(--cometchat-primary-color);
padding: 2px var(--cometchat-spacing-2);
border-radius: var(--cometchat-radius-1);
}
.custom-header__status {
font: var(--cometchat-font-caption2-regular);
color: var(--cometchat-text-color-tertiary);
}
` ]
})
export class CustomHeaderComponent {
message !: CometChat . BaseMessage ;
group !: CometChat . Group ;
MessageBubbleAlignment = MessageBubbleAlignment ;
isAdmin ( user : CometChat . User | undefined ) : boolean {
// Check if user is admin - implement your logic
return user ?. getRole () === 'admin' ;
}
}
See all 63 lines
replyView
The replyView customizes the quoted message preview that appears when a message is a reply to another message.
Use Case : Custom reply preview styling, showing additional context, or different preview formats.
import { Component , TemplateRef , ViewChild } from '@angular/core' ;
import { CometChat } from '@cometchat/chat-sdk-javascript' ;
import {
CometChatMessageBubbleComponent ,
MessageBubbleAlignment
} from '@cometchat/chat-uikit-angular' ;
@ Component ({
selector: 'app-custom-reply' ,
standalone: true ,
imports: [ CometChatMessageBubbleComponent ],
template: `
<cometchat-message-bubble
[message]="replyMessage"
[alignment]="MessageBubbleAlignment.left"
[replyView]="customReply"
(replyPreviewClick)="onReplyClick($event)">
</cometchat-message-bubble>
<ng-template #customReply let-message>
@if (getQuotedMessage(message); as quotedMessage) {
<div class="custom-reply"
tabindex="0"
role="button"
aria-label="Jump to original message"
(click)="onReplyClick(quotedMessage)"
(keydown.enter)="onReplyClick(quotedMessage)"
(keydown.space)="onReplyClick(quotedMessage)">
<div class="custom-reply__indicator"></div>
<div class="custom-reply__content">
<span class="custom-reply__sender">{{ quotedMessage.getSender()?.getName() }}</span>
<span class="custom-reply__text">{{ getPreviewText(quotedMessage) }}</span>
</div>
</div>
}
</ng-template>
` ,
styles: [ `
.custom-reply {
display: flex;
gap: var(--cometchat-spacing-2);
padding: var(--cometchat-spacing-2);
background: var(--cometchat-background-color-03);
border-radius: var(--cometchat-radius-2);
margin-bottom: var(--cometchat-spacing-2);
cursor: pointer;
}
.custom-reply:hover {
background: var(--cometchat-background-color-04);
}
.custom-reply:focus {
outline: 2px solid var(--cometchat-primary-color);
outline-offset: 2px;
}
.custom-reply__indicator {
width: 3px;
background: var(--cometchat-primary-color);
border-radius: var(--cometchat-radius-max);
}
.custom-reply__content {
display: flex;
flex-direction: column;
gap: var(--cometchat-spacing-1);
overflow: hidden;
}
.custom-reply__sender {
font: var(--cometchat-font-caption1-bold);
color: var(--cometchat-primary-color);
}
.custom-reply__text {
font: var(--cometchat-font-caption1-regular);
color: var(--cometchat-text-color-secondary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
` ]
})
export class CustomReplyComponent {
replyMessage !: CometChat . BaseMessage ;
MessageBubbleAlignment = MessageBubbleAlignment ;
getQuotedMessage ( message : CometChat . BaseMessage ) : CometChat . BaseMessage | null {
if ( typeof ( message as any ). getQuotedMessage === 'function' ) {
return ( message as any ). getQuotedMessage () || null ;
}
return null ;
}
getPreviewText ( message : CometChat . BaseMessage ) : string {
if ( message instanceof CometChat . TextMessage ) {
return message . getText (). substring ( 0 , 50 ) + ( message . getText (). length > 50 ? '...' : '' );
}
return `[ ${ message . getType () } ]` ;
}
onReplyClick ( message : CometChat . BaseMessage ) : void {
console . log ( 'Navigate to message:' , message . getId ());
}
}
See all 100 lines
contentView
The contentView customizes the main message content area. This is where the actual message (text, image, video, etc.) is displayed.
Use Case : Custom message rendering, adding interactive elements, or completely different content layouts.
import { Component , TemplateRef , ViewChild } from '@angular/core' ;
import { CometChat } from '@cometchat/chat-sdk-javascript' ;
import {
CometChatMessageBubbleComponent ,
MessageBubbleAlignment
} from '@cometchat/chat-uikit-angular' ;
@ Component ({
selector: 'app-custom-content' ,
standalone: true ,
imports: [ CometChatMessageBubbleComponent ],
template: `
<cometchat-message-bubble
[message]="message"
[alignment]="MessageBubbleAlignment.left"
[contentView]="customContent">
</cometchat-message-bubble>
<ng-template #customContent let-message>
<div class="custom-content">
@if (isTextMessage(message)) {
<p class="custom-content__text">{{ message.getText() }}</p>
} @else if (isImageMessage(message)) {
<div class="custom-content__image-wrapper">
<img
class="custom-content__image"
[src]="getImageUrl(message)"
[alt]="'Image from ' + message.getSender()?.getName()"
loading="lazy" />
</div>
} @else {
<p class="custom-content__fallback">{{ message.getType() }} message</p>
}
<button class="custom-content__action" (click)="onCustomAction(message)">
Custom Action
</button>
</div>
</ng-template>
` ,
styles: [ `
.custom-content {
display: flex;
flex-direction: column;
gap: var(--cometchat-spacing-2);
}
.custom-content__text {
font: var(--cometchat-font-body-regular);
color: var(--cometchat-text-color-primary);
margin: 0;
word-wrap: break-word;
}
.custom-content__image-wrapper {
max-width: 250px;
border-radius: var(--cometchat-radius-2);
overflow: hidden;
}
.custom-content__image {
width: 100%;
height: auto;
display: block;
}
.custom-content__fallback {
font: var(--cometchat-font-body-regular);
color: var(--cometchat-text-color-secondary);
font-style: italic;
}
.custom-content__action {
font: var(--cometchat-font-button-medium);
color: var(--cometchat-primary-color);
background: transparent;
border: 1px solid var(--cometchat-primary-color);
border-radius: var(--cometchat-radius-2);
padding: var(--cometchat-spacing-2) var(--cometchat-spacing-3);
cursor: pointer;
}
.custom-content__action:hover {
background: var(--cometchat-extended-primary-color-50);
}
` ]
})
export class CustomContentComponent {
message !: CometChat . BaseMessage ;
MessageBubbleAlignment = MessageBubbleAlignment ;
isTextMessage ( message : CometChat . BaseMessage ) : message is CometChat . TextMessage {
return message instanceof CometChat . TextMessage ;
}
isImageMessage ( message : CometChat . BaseMessage ) : message is CometChat . MediaMessage {
return message instanceof CometChat . MediaMessage && message . getType () === 'image' ;
}
getImageUrl ( message : CometChat . BaseMessage ) : string {
if ( message instanceof CometChat . MediaMessage ) {
return message . getAttachment ()?. getUrl () || '' ;
}
return '' ;
}
onCustomAction ( message : CometChat . BaseMessage ) : void {
console . log ( 'Custom action on message:' , message . getId ());
}
}
See all 103 lines
statusInfoView
The statusInfoView customizes the timestamp and delivery status section that appears inside the message bubble.
Use Case : Custom timestamp formats, additional status indicators, or different receipt icons.
import { Component , TemplateRef , ViewChild } from '@angular/core' ;
import { CometChat } from '@cometchat/chat-sdk-javascript' ;
import {
CometChatMessageBubbleComponent ,
MessageBubbleAlignment
} from '@cometchat/chat-uikit-angular' ;
import { DatePipe } from '@angular/common' ;
@ Component ({
selector: 'app-custom-status' ,
standalone: true ,
imports: [ CometChatMessageBubbleComponent , DatePipe ],
template: `
<cometchat-message-bubble
[message]="message"
[alignment]="MessageBubbleAlignment.right"
[statusInfoView]="customStatus">
</cometchat-message-bubble>
<ng-template #customStatus let-message>
<div class="custom-status" role="status" aria-live="polite">
@if (message.getEditedAt()) {
<span class="custom-status__edited">edited</span>
}
<span class="custom-status__time">
{{ message.getSentAt() * 1000 | date:'shortTime' }}
</span>
@if (isOutgoing(message)) {
<span class="custom-status__receipt" [attr.aria-label]="getReceiptLabel(message)">
@if (message.getReadAt()) {
<span class="custom-status__icon custom-status__icon--read">✓✓</span>
} @else if (message.getDeliveredAt()) {
<span class="custom-status__icon custom-status__icon--delivered">✓✓</span>
} @else if (message.getSentAt()) {
<span class="custom-status__icon custom-status__icon--sent">✓</span>
} @else {
<span class="custom-status__icon custom-status__icon--pending">○</span>
}
</span>
}
</div>
</ng-template>
` ,
styles: [ `
.custom-status {
display: flex;
align-items: center;
gap: var(--cometchat-spacing-1);
justify-content: flex-end;
}
.custom-status__edited {
font: var(--cometchat-font-caption2-regular);
color: var(--cometchat-text-color-tertiary);
font-style: italic;
}
.custom-status__time {
font: var(--cometchat-font-caption2-regular);
color: var(--cometchat-text-color-tertiary);
}
.custom-status__icon {
font-size: 12px;
}
.custom-status__icon--read {
color: var(--cometchat-info-color);
}
.custom-status__icon--delivered {
color: var(--cometchat-text-color-secondary);
}
.custom-status__icon--sent {
color: var(--cometchat-text-color-tertiary);
}
.custom-status__icon--pending {
color: var(--cometchat-text-color-tertiary);
}
` ]
})
export class CustomStatusComponent {
message !: CometChat . BaseMessage ;
MessageBubbleAlignment = MessageBubbleAlignment ;
private loggedInUser = CometChat . getLoggedinUser ();
isOutgoing ( message : CometChat . BaseMessage ) : boolean {
return message . getSender ()?. getUid () === this . loggedInUser ?. getUid ();
}
getReceiptLabel ( message : CometChat . BaseMessage ) : string {
if ( message . getReadAt ()) return 'Read' ;
if ( message . getDeliveredAt ()) return 'Delivered' ;
if ( message . getSentAt ()) return 'Sent' ;
return 'Sending' ;
}
}
See all 92 lines
bottomView
The bottomView customizes the section below the main content, typically used for link previews, warnings, or additional information.
Use Case : Link previews, content warnings, or expandable content sections.
import { Component , TemplateRef , ViewChild } from '@angular/core' ;
import { CometChat } from '@cometchat/chat-sdk-javascript' ;
import {
CometChatMessageBubbleComponent ,
MessageBubbleAlignment
} from '@cometchat/chat-uikit-angular' ;
@ Component ({
selector: 'app-custom-bottom' ,
standalone: true ,
imports: [ CometChatMessageBubbleComponent ],
template: `
<cometchat-message-bubble
[message]="message"
[alignment]="MessageBubbleAlignment.left"
[bottomView]="customBottom">
</cometchat-message-bubble>
<ng-template #customBottom let-message>
@if (hasLinkPreview(message)) {
<div class="custom-bottom">
<a class="custom-bottom__link"
[href]="getLinkUrl(message)"
target="_blank"
rel="noopener noreferrer">
<div class="custom-bottom__preview">
<img class="custom-bottom__image"
[src]="getLinkImage(message)"
alt="Link preview" />
<div class="custom-bottom__info">
<span class="custom-bottom__title">{{ getLinkTitle(message) }}</span>
<span class="custom-bottom__domain">{{ getLinkDomain(message) }}</span>
</div>
</div>
</a>
</div>
}
@if (hasWarning(message)) {
<div class="custom-bottom__warning">
<span class="custom-bottom__warning-icon">⚠️</span>
<span class="custom-bottom__warning-text">This message may contain sensitive content</span>
</div>
}
</ng-template>
` ,
styles: [ `
.custom-bottom {
margin-top: var(--cometchat-spacing-2);
}
.custom-bottom__link {
text-decoration: none;
color: inherit;
}
.custom-bottom__preview {
display: flex;
gap: var(--cometchat-spacing-2);
padding: var(--cometchat-spacing-2);
background: var(--cometchat-background-color-03);
border-radius: var(--cometchat-radius-2);
border: 1px solid var(--cometchat-border-color-light);
}
.custom-bottom__preview:hover {
background: var(--cometchat-background-color-04);
}
.custom-bottom__image {
width: 60px;
height: 60px;
object-fit: cover;
border-radius: var(--cometchat-radius-1);
}
.custom-bottom__info {
display: flex;
flex-direction: column;
gap: var(--cometchat-spacing-1);
}
.custom-bottom__title {
font: var(--cometchat-font-caption1-medium);
color: var(--cometchat-text-color-primary);
}
.custom-bottom__domain {
font: var(--cometchat-font-caption2-regular);
color: var(--cometchat-text-color-tertiary);
}
.custom-bottom__warning {
display: flex;
align-items: center;
gap: var(--cometchat-spacing-2);
padding: var(--cometchat-spacing-2);
background: var(--cometchat-warning-color);
background: rgba(255, 171, 0, 0.1);
border-radius: var(--cometchat-radius-2);
margin-top: var(--cometchat-spacing-2);
}
.custom-bottom__warning-text {
font: var(--cometchat-font-caption1-regular);
color: var(--cometchat-warning-color);
}
` ]
})
export class CustomBottomComponent {
message !: CometChat . BaseMessage ;
MessageBubbleAlignment = MessageBubbleAlignment ;
hasLinkPreview ( message : CometChat . BaseMessage ) : boolean {
// Check if message has link preview metadata
return false ; // Implement your logic
}
hasWarning ( message : CometChat . BaseMessage ) : boolean {
// Check if message should show warning
return false ; // Implement your logic
}
getLinkUrl ( message : CometChat . BaseMessage ) : string { return '' ; }
getLinkImage ( message : CometChat . BaseMessage ) : string { return '' ; }
getLinkTitle ( message : CometChat . BaseMessage ) : string { return '' ; }
getLinkDomain ( message : CometChat . BaseMessage ) : string { return '' ; }
}
See all 118 lines
The footerView customizes the reactions section that appears below the message content.
Use Case : Custom reaction display, adding additional footer actions, or different reaction layouts.
import { Component , TemplateRef , ViewChild } from '@angular/core' ;
import { CometChat } from '@cometchat/chat-sdk-javascript' ;
import {
CometChatMessageBubbleComponent ,
MessageBubbleAlignment
} from '@cometchat/chat-uikit-angular' ;
@ Component ({
selector: 'app-custom-footer' ,
standalone: true ,
imports: [ CometChatMessageBubbleComponent ],
template: `
<cometchat-message-bubble
[message]="message"
[alignment]="MessageBubbleAlignment.left"
[footerView]="customFooter"
(reactionClick)="onReactionClick($event)">
</cometchat-message-bubble>
<ng-template #customFooter let-message>
@if (getReactions(message).length > 0) {
<div class="custom-footer">
<div class="custom-footer__reactions" role="group" aria-label="Message reactions">
@for (reaction of getReactions(message); track reaction.getReaction()) {
<button
class="custom-footer__reaction"
[class.custom-footer__reaction--reacted]="hasUserReacted(reaction)"
(click)="onReactionClick({ reaction, message })"
[attr.aria-label]="reaction.getReaction() + ' reaction, ' + reaction.getCount() + ' users'"
[attr.aria-pressed]="hasUserReacted(reaction)">
<span class="custom-footer__emoji">{{ reaction.getReaction() }}</span>
<span class="custom-footer__count">{{ reaction.getCount() }}</span>
</button>
}
</div>
<button class="custom-footer__add" aria-label="Add reaction">
<span>+</span>
</button>
</div>
}
</ng-template>
` ,
styles: [ `
.custom-footer {
display: flex;
align-items: center;
gap: var(--cometchat-spacing-2);
margin-top: var(--cometchat-spacing-2);
}
.custom-footer__reactions {
display: flex;
flex-wrap: wrap;
gap: var(--cometchat-spacing-1);
}
.custom-footer__reaction {
display: flex;
align-items: center;
gap: var(--cometchat-spacing-1);
padding: var(--cometchat-spacing-1) var(--cometchat-spacing-2);
background: var(--cometchat-background-color-02);
border: 1px solid var(--cometchat-border-color-light);
border-radius: var(--cometchat-radius-max);
cursor: pointer;
transition: all 0.2s ease;
}
.custom-footer__reaction:hover {
background: var(--cometchat-background-color-03);
}
.custom-footer__reaction:focus {
outline: 2px solid var(--cometchat-primary-color);
outline-offset: 2px;
}
.custom-footer__reaction--reacted {
background: var(--cometchat-extended-primary-color-50);
border-color: var(--cometchat-primary-color);
}
.custom-footer__emoji {
font-size: 14px;
}
.custom-footer__count {
font: var(--cometchat-font-caption2-medium);
color: var(--cometchat-text-color-secondary);
}
.custom-footer__add {
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
background: var(--cometchat-background-color-02);
border: 1px dashed var(--cometchat-border-color-default);
border-radius: var(--cometchat-radius-max);
cursor: pointer;
font: var(--cometchat-font-body-medium);
color: var(--cometchat-text-color-secondary);
}
.custom-footer__add:hover {
background: var(--cometchat-background-color-03);
border-style: solid;
}
` ]
})
export class CustomFooterComponent {
message !: CometChat . BaseMessage ;
MessageBubbleAlignment = MessageBubbleAlignment ;
private loggedInUser = CometChat . getLoggedinUser ();
getReactions ( message : CometChat . BaseMessage ) : CometChat . ReactionCount [] {
return message . getReactions () || [];
}
hasUserReacted ( reaction : CometChat . ReactionCount ) : boolean {
return reaction . getReactedByMe () || false ;
}
onReactionClick ( event : { reaction : CometChat . ReactionCount ; message : CometChat . BaseMessage }) : void {
console . log ( 'Reaction clicked:' , event . reaction . getReaction ());
}
}
See all 119 lines
threadView
The threadView customizes the thread reply indicator that appears when a message has replies.
Use Case : Custom thread indicators, showing reply previews, or different thread navigation styles.
import { Component , TemplateRef , ViewChild } from '@angular/core' ;
import { CometChat } from '@cometchat/chat-sdk-javascript' ;
import {
CometChatMessageBubbleComponent ,
MessageBubbleAlignment
} from '@cometchat/chat-uikit-angular' ;
@ Component ({
selector: 'app-custom-thread' ,
standalone: true ,
imports: [ CometChatMessageBubbleComponent ],
template: `
<cometchat-message-bubble
[message]="message"
[alignment]="MessageBubbleAlignment.left"
[threadView]="customThread"
(threadRepliesClick)="onThreadClick($event)">
</cometchat-message-bubble>
<ng-template #customThread let-message>
@if (message.getReplyCount() > 0) {
<button
class="custom-thread"
(click)="onThreadClick(message)"
[attr.aria-label]="message.getReplyCount() + ' replies, click to view thread'">
<span class="custom-thread__icon">💬</span>
<span class="custom-thread__count">{{ message.getReplyCount() }}</span>
<span class="custom-thread__label">
{{ message.getReplyCount() === 1 ? 'reply' : 'replies' }}
</span>
@if (message.getUnreadRepliesCount() > 0) {
<span class="custom-thread__unread">
{{ message.getUnreadRepliesCount() }} new
</span>
}
<span class="custom-thread__arrow">→</span>
</button>
}
</ng-template>
` ,
styles: [ `
.custom-thread {
display: flex;
align-items: center;
gap: var(--cometchat-spacing-2);
padding: var(--cometchat-spacing-2) var(--cometchat-spacing-3);
background: transparent;
border: none;
cursor: pointer;
margin-top: var(--cometchat-spacing-2);
border-top: 1px solid var(--cometchat-border-color-light);
width: 100%;
}
.custom-thread:hover {
background: var(--cometchat-background-color-02);
}
.custom-thread:focus {
outline: 2px solid var(--cometchat-primary-color);
outline-offset: -2px;
}
.custom-thread__icon {
font-size: 14px;
}
.custom-thread__count {
font: var(--cometchat-font-caption1-bold);
color: var(--cometchat-primary-color);
}
.custom-thread__label {
font: var(--cometchat-font-caption1-regular);
color: var(--cometchat-primary-color);
}
.custom-thread__unread {
font: var(--cometchat-font-caption2-medium);
color: var(--cometchat-static-white);
background: var(--cometchat-primary-color);
padding: 2px var(--cometchat-spacing-2);
border-radius: var(--cometchat-radius-max);
}
.custom-thread__arrow {
margin-left: auto;
color: var(--cometchat-text-color-tertiary);
}
` ]
})
export class CustomThreadComponent {
message !: CometChat . BaseMessage ;
MessageBubbleAlignment = MessageBubbleAlignment ;
onThreadClick ( message : CometChat . BaseMessage ) : void {
console . log ( 'Open thread for message:' , message . getId ());
}
}
See all 92 lines
Service-Based Customization
For global customization across all message bubbles, use the MessageBubbleConfigService. This allows you to set custom views that apply to all instances without passing templates to each component.
Using MessageBubbleConfigService
import { Component , OnInit , TemplateRef , ViewChild , AfterViewInit } from '@angular/core' ;
import { CometChat } from '@cometchat/chat-sdk-javascript' ;
import {
CometChatMessageListComponent ,
MessageBubbleConfigService
} from '@cometchat/chat-uikit-angular' ;
@ Component ({
selector: 'app-global-customization' ,
standalone: true ,
imports: [ CometChatMessageListComponent ],
template: `
<cometchat-message-list [user]="user"></cometchat-message-list>
<!-- Global custom status info view -->
<ng-template #globalStatusInfo let-message>
<div class="global-status">
<span>{{ message.getSentAt() * 1000 | date:'shortTime' }}</span>
@if (message.getEditedAt()) {
<span class="global-status__edited">(edited)</span>
}
</div>
</ng-template>
<!-- Custom content view for text messages -->
<ng-template #customTextContent let-message>
<div class="custom-text">
<p>{{ message.getText() }}</p>
</div>
</ng-template>
` ,
styles: [ `
.global-status {
display: flex;
gap: var(--cometchat-spacing-1);
font: var(--cometchat-font-caption2-regular);
color: var(--cometchat-text-color-tertiary);
}
.global-status__edited {
font-style: italic;
}
.custom-text p {
margin: 0;
font: var(--cometchat-font-body-regular);
color: var(--cometchat-text-color-primary);
}
` ]
})
export class GlobalCustomizationComponent implements AfterViewInit {
@ ViewChild ( 'globalStatusInfo' ) globalStatusInfo !: TemplateRef < any >;
@ ViewChild ( 'customTextContent' ) customTextContent !: TemplateRef < any >;
user ?: CometChat . User ;
constructor ( private bubbleConfigService : MessageBubbleConfigService ) {}
ngAfterViewInit () : void {
// Set global status info view for all message types
this . bubbleConfigService . setGlobalStatusInfoView ( this . globalStatusInfo );
// Set custom content view for text messages only
this . bubbleConfigService . setBubbleView ( 'text_message' , {
contentView: this . customTextContent
});
// Set multiple message type customizations at once
this . bubbleConfigService . setMessageTemplates ({
'text_message' : { contentView: this . customTextContent },
'image_message' : { statusInfoView: this . globalStatusInfo }
});
}
}
See all 72 lines
MessageBubbleConfigService API
Method Parameters Description setBubbleViewmessageType: string, map: BubblePartMapSet custom views for a specific message type setGlobalStatusInfoViewview: TemplateRef<any>Set status info view for all message types setGlobalLeadingViewview: TemplateRef<any>Set leading view for all message types setGlobalHeaderViewview: TemplateRef<any>Set header view for all message types setMessageTemplatesmaps: Record<string, BubblePartMap>Set multiple message type customizations getViewmessageType: string, part: BubblePartGet the configured view for a message type and part clearAllnone Clear all configured views
Scoping for Multiple Instances
By default, MessageBubbleConfigService is a root-level singleton — all message bubbles share the same configuration. If you need different bubble customizations for different message lists (e.g., a main chat vs. a thread panel), create a wrapper component that provides its own instance:
@ Component ({
selector: 'app-thread-panel' ,
standalone: true ,
imports: [ CometChatMessageListComponent ],
providers: [ MessageBubbleConfigService ], // Scoped instance
template: `<cometchat-message-list [user]="user" [parentMessageId]="parentMessageId"></cometchat-message-list>`
})
export class ThreadPanelComponent implements AfterViewInit {
@ ViewChild ( 'minimalStatus' ) minimalStatus !: TemplateRef < any >;
// This is the LOCAL instance, not the root singleton
private bubbleConfig = inject ( MessageBubbleConfigService );
ngAfterViewInit () : void {
// Only affects bubbles inside this wrapper
this . bubbleConfig . setGlobalView ( 'statusInfoView' , this . minimalStatus );
}
}
See all 18 lines
See CometChatMessageList — Multiple Message Lists with Different Configurations for a complete example.
BubblePart Type
type BubblePart =
| 'bubbleView'
| 'contentView'
| 'bottomView'
| 'footerView'
| 'leadingView'
| 'headerView'
| 'statusInfoView'
| 'replyView'
| 'threadView' ;
See all 10 lines
CSS Customization
Styling with CSS Variables
The Message Bubble component uses CSS variables for consistent theming. Override these variables to customize the appearance:
/* Custom theme overrides */
:root {
/* Spacing */
--cometchat-spacing-1 : 4 px ;
--cometchat-spacing-2 : 8 px ;
--cometchat-spacing-3 : 12 px ;
--cometchat-spacing-4 : 16 px ;
/* Typography */
--cometchat-font-body-regular : 400 14 px / 1.4 'Roboto' , sans-serif ;
--cometchat-font-caption1-medium : 500 12 px / 1.2 'Roboto' , sans-serif ;
--cometchat-font-caption2-regular : 400 10 px / 1.2 'Roboto' , sans-serif ;
/* Colors */
--cometchat-background-color-02 : #F5F5F5 ;
--cometchat-primary-color : #6852D6 ;
--cometchat-text-color-primary : #141414 ;
--cometchat-text-color-secondary : #666666 ;
--cometchat-text-color-tertiary : #999999 ;
/* Border */
--cometchat-border-color-light : #E0E0E0 ;
--cometchat-radius-3 : 12 px ;
}
See all 24 lines
Dark Theme
/* Dark theme overrides */
[ data-theme = "dark" ] {
--cometchat-background-color-01 : #1A1A1A ;
--cometchat-background-color-02 : #2C2C2C ;
--cometchat-background-color-03 : #3D3D3D ;
--cometchat-text-color-primary : #FFFFFF ;
--cometchat-text-color-secondary : #B0B0B0 ;
--cometchat-text-color-tertiary : #808080 ;
--cometchat-border-color-light : #404040 ;
}
See all 10 lines
Component-Specific Styling
Target specific bubble elements using BEM class names:
/* Incoming message bubble */
.cometchat-message-bubble-incoming {
/* Custom styles for incoming messages */
}
/* Outgoing message bubble */
.cometchat-message-bubble-outgoing {
/* Custom styles for outgoing messages */
}
/* Action/system message bubble */
.cometchat-message-bubble-action {
/* Custom styles for action messages */
}
/* Message type specific */
.cometchat-message-bubble__text-message {
/* Custom styles for text messages */
}
.cometchat-message-bubble__image-message {
/* Custom styles for image messages */
}
/* Bubble body */
.cometchat-message-bubble__body {
/* Custom styles for bubble body */
}
/* Status info section */
.cometchat-message-bubble__status-info-view {
/* Custom styles for status info */
}
/* Reply preview */
.cometchat-message-bubble__body-reply-view {
/* Custom styles for reply preview */
}
See all 38 lines
Advanced Usage
Group Conversation with Avatar and Sender Name
import { Component } from '@angular/core' ;
import { CometChat } from '@cometchat/chat-sdk-javascript' ;
import { CometChatMessageBubbleComponent , MessageBubbleAlignment } from '@cometchat/chat-uikit-angular' ;
@ Component ({
selector: 'app-group-message' ,
standalone: true ,
imports: [ CometChatMessageBubbleComponent ],
template: `
<cometchat-message-bubble
[message]="message"
[alignment]="MessageBubbleAlignment.left"
[group]="currentGroup"
(avatarClick)="onAvatarClick($event)">
</cometchat-message-bubble>
`
})
export class GroupMessageComponent {
message !: CometChat . BaseMessage ;
currentGroup !: CometChat . Group ;
MessageBubbleAlignment = MessageBubbleAlignment ;
onAvatarClick ( user : CometChat . User ) : void {
console . log ( 'Avatar clicked:' , user . getName ());
// Navigate to user profile
}
}
See all 27 lines
With Text Formatters
import { Component } from '@angular/core' ;
import { CometChat } from '@cometchat/chat-sdk-javascript' ;
import {
CometChatMessageBubbleComponent ,
CometChatTextFormatter ,
MessageBubbleAlignment
} from '@cometchat/chat-uikit-angular' ;
// Custom hashtag formatter
class HashtagFormatter extends CometChatTextFormatter {
readonly id = 'hashtag-formatter' ;
priority = 25 ;
format ( text : string , message : CometChat . BaseMessage ) : string {
return text . replace ( /# ( \w + ) / g , '<span class="hashtag">#$1</span>' );
}
}
@ Component ({
selector: 'app-formatted-message' ,
standalone: true ,
imports: [ CometChatMessageBubbleComponent ],
template: `
<cometchat-message-bubble
[message]="message"
[alignment]="MessageBubbleAlignment.left"
[textFormatters]="formatters">
</cometchat-message-bubble>
` ,
styles: [ `
:host ::ng-deep .hashtag {
color: var(--cometchat-primary-color);
font-weight: 500;
cursor: pointer;
}
:host ::ng-deep .hashtag:hover {
text-decoration: underline;
}
` ]
})
export class FormattedMessageComponent {
message !: CometChat . TextMessage ;
MessageBubbleAlignment = MessageBubbleAlignment ;
formatters = [ new HashtagFormatter ()];
}
See all 45 lines
With Message Translation
import { Component } from '@angular/core' ;
import { CometChat } from '@cometchat/chat-sdk-javascript' ;
import { CometChatMessageBubbleComponent , MessageBubbleAlignment } from '@cometchat/chat-uikit-angular' ;
@ Component ({
selector: 'app-translated-message' ,
standalone: true ,
imports: [ CometChatMessageBubbleComponent ],
template: `
<cometchat-message-bubble
[message]="message"
[alignment]="MessageBubbleAlignment.left"
[translatedText]="translatedText">
</cometchat-message-bubble>
`
})
export class TranslatedMessageComponent {
message !: CometChat . TextMessage ;
translatedText = 'This is the translated text' ;
MessageBubbleAlignment = MessageBubbleAlignment ;
}
See all 21 lines
Accessibility
The Message Bubble component is fully accessible and follows WCAG 2.1 Level AA guidelines:
Keyboard Support
Key Action TabNavigate between interactive elements Enter / SpaceActivate focused element (avatar, options menu, reactions) EscapeClose options menu and return focus to bubble Arrow KeysNavigate within options menu when open
ARIA Attributes
Attribute Element Purpose role="article"Wrapper Identifies message as an article aria-labelWrapper Describes message sender and type aria-describedbyWrapper Links to message content role="status"Status info Identifies status area aria-live="polite"Status info Announces status changes role="button"Interactive elements Identifies clickable elements aria-pressedReactions Indicates if user has reacted role="group"Reactions container Groups related reactions
Screen Reader Announcements
The component provides meaningful announcements for:
Message sender and type
Message status (sent, delivered, read)
Edited messages
Reactions and their counts
Thread reply counts
Best Practices
Use the group property to enable avatar and sender name display in group conversations.
Always provide the complete CometChat message object to ensure proper rendering of all message types.
The component automatically determines the appropriate bubble component based on message type. Use contentView only when you need custom rendering.
Handle optionClick events to implement message actions like reply, copy, delete, etc.
When using custom templates, ensure you maintain accessibility by including proper ARIA attributes and keyboard navigation.
Use CSS variables for styling to ensure your customizations work correctly with both light and dark themes.