diff --git a/docs.json b/docs.json
index 4e87f68d2..713a8f1a8 100644
--- a/docs.json
+++ b/docs.json
@@ -2698,6 +2698,7 @@
"group": "Message Bubbles",
"pages": [
"ui-kit/angular/components/cometchat-text-bubble",
+ "ui-kit/angular/components/cometchat-card-bubble",
"ui-kit/angular/components/cometchat-image-bubble",
"ui-kit/angular/components/cometchat-video-bubble",
"ui-kit/angular/components/cometchat-audio-bubble",
@@ -2764,6 +2765,7 @@
"ui-kit/angular/guides/search-messages",
"ui-kit/angular/guides/rich-text-formatting",
"ui-kit/angular/guides/custom-message-types",
+ "ui-kit/angular/guides/card-messages",
"ui-kit/angular/guides/custom-text-formatter",
"ui-kit/angular/guides/mentions-formatter",
"ui-kit/angular/guides/shortcut-formatter",
diff --git a/ui-kit/angular/components/cometchat-card-bubble.mdx b/ui-kit/angular/components/cometchat-card-bubble.mdx
new file mode 100644
index 000000000..3b1da5a46
--- /dev/null
+++ b/ui-kit/angular/components/cometchat-card-bubble.mdx
@@ -0,0 +1,238 @@
+---
+title: "Card Bubble"
+description: "Angular component that renders a developer Card Message inside a message bubble using the prebuilt CometChat Cards renderer, with pure-forwarded card actions."
+---
+
+The `CometChatCardBubble` component renders a **developer Card Message** (`message.category === "card"`) as a card bubble inside a conversation. It is the card equivalent of [`CometChatTextBubble`](/ui-kit/angular/components/cometchat-text-bubble): the surrounding [`CometChatMessageBubble`](/ui-kit/angular/components/cometchat-message-bubble) wrapper supplies the container, receipts, reactions, long-press options, reply and thread view — this component only replaces the **content view** with the rendered card.
+
+## Overview
+
+The UIKit is a **render-only** consumer of cards: it draws cards delivered by the SDK and forwards card actions to your app. It never parses or mutates the card body, and never sends or creates cards. When a card message arrives, the UIKit routes it to this bubble automatically.
+
+The component follows a strict **render-only contract**:
+
+- **Render-only** — the raw card payload from `message.getCard()` is serialized verbatim and handed to the prebuilt `CometChatCardView` renderer (from [`@cometchat/cards-angular`](https://www.npmjs.com/package/@cometchat/cards-angular)) as a `cardJson` string. The UI Kit performs **zero transformation** of the payload.
+- **No behavior** — the bubble runs no action logic of its own. When a user taps an interactive element, the renderer's raw action is **pure-forwarded** on two channels (see [Card Actions](#card-actions)); your app owns all behavior.
+- **Graceful fallback** — when the payload is empty or invalid, a single-line fallback text is shown instead of an empty bubble (see [Fallback Behavior](#fallback-behavior)).
+
+
+ Card rendering (layout, theming, interactive elements) is owned by the prebuilt `@cometchat/cards-angular` renderer library, **not** by the UI Kit. The UI Kit is responsible only for delivering the payload to the renderer and forwarding actions back out.
+
+
+## Automatic Rendering
+
+In the standard chat flow you do **not** instantiate this component yourself. [`CometChatMessageList`](/ui-kit/angular/components/cometchat-message-list) and `CometChatMessageBubble` route any message with category `"card"` to `CometChatCardBubble` automatically, keyed by **category** (the developer `type` is arbitrary). To handle card actions in this flow, subscribe to the [`ccCardActionClicked`](#card-actions) event bus — no component wiring is required.
+
+Use the component directly only when you are building a fully custom message renderer.
+
+## Basic Usage
+
+```typescript expandable
+import { Component } from '@angular/core';
+import { CometChat } from '@cometchat/chat-sdk-javascript';
+import {
+ CometChatCardBubbleComponent,
+ MessageBubbleAlignment,
+} from '@cometchat/chat-uikit-angular';
+
+@Component({
+ selector: 'app-card-message',
+ standalone: true,
+ imports: [CometChatCardBubbleComponent],
+ template: `
+
+ `,
+})
+export class CardMessageComponent {
+ cardMessage!: CometChat.CardMessage;
+ MessageBubbleAlignment = MessageBubbleAlignment;
+}
+```
+
+### Incoming vs Outgoing Messages
+
+```typescript expandable
+import { Component } from '@angular/core';
+import { CometChat } from '@cometchat/chat-sdk-javascript';
+import {
+ CometChatCardBubbleComponent,
+ MessageBubbleAlignment,
+} from '@cometchat/chat-uikit-angular';
+
+@Component({
+ selector: 'app-card-list',
+ standalone: true,
+ imports: [CometChatCardBubbleComponent],
+ template: `
+
+
+
+
+
+ `,
+})
+export class CardListComponent {
+ incomingCard!: CometChat.CardMessage;
+ outgoingCard!: CometChat.CardMessage;
+ MessageBubbleAlignment = MessageBubbleAlignment;
+}
+```
+
+## Properties
+
+| Property | Type | Default | Description |
+|----------|------|---------|-------------|
+| `message` | `CometChat.CardMessage` | **required** | The card message to render. The raw payload is read from `message.getCard()`. |
+| `alignment` | `MessageBubbleAlignment` | `MessageBubbleAlignment.left` | Bubble alignment. `left` for incoming/receiver messages, `right` for outgoing/sender messages. Kept for parity with the text bubble. |
+| `themeMode` | `CometChatCardThemeMode` | `'auto'` | Renderer theme mode. One of `'auto'`, `'light'`, or `'dark'`. Passed straight to `CometChatCardView`. |
+| `themeOverride` | `CometChatCardThemeOverride` | `undefined` | Optional renderer theme overrides (colors, spacing, typography for the rendered card). Passed straight to `CometChatCardView`. |
+
+
+ `CometChatCardThemeMode` and `CometChatCardThemeOverride` are exported by `@cometchat/cards-angular`. Type these inputs precisely — passing `unknown` will fail strict-template AOT compilation.
+
+
+## Events
+
+| Event | Payload Type | Description |
+|-------|-------------|-------------|
+| `onCardAction` | `CardBubbleAction` | Emitted when a user taps an interactive element on the card. Carries the raw renderer action. |
+
+```typescript
+/** Payload emitted by the bubble's onCardAction output. */
+export interface CardBubbleAction {
+ message: CometChat.CardMessage; // the card message the action originated from
+ action: unknown; // the renderer's raw discriminated action
+}
+```
+
+## Card Actions
+
+The Cards renderer is a **pure renderer** — it emits actions through callbacks without executing them. The bubble forwards each action on **both** channels and runs no behavior of its own:
+
+1. **`(onCardAction)` output** — for apps that render this bubble directly.
+2. **`CometChatMessageEvents.ccCardActionClicked` event bus** — for the standard, internally rendered flow where the bubble is created by the UI Kit. This is the recommended channel for the default message list.
+
+
+ Act on **one** channel only to avoid double-handling the same tap. In the standard `CometChatMessageList` flow, use the `ccCardActionClicked` bus.
+
+
+### Handling actions via the output
+
+```typescript expandable
+import { Component } from '@angular/core';
+import { CometChat } from '@cometchat/chat-sdk-javascript';
+import {
+ CometChatCardBubbleComponent,
+ CardBubbleAction,
+} from '@cometchat/chat-uikit-angular';
+
+@Component({
+ selector: 'app-card-message',
+ standalone: true,
+ imports: [CometChatCardBubbleComponent],
+ template: `
+
+ `,
+})
+export class CardMessageComponent {
+ cardMessage!: CometChat.CardMessage;
+
+ onCardAction(event: CardBubbleAction): void {
+ // event.action is the raw renderer action (a CometChatCardAction).
+ // Your app implements the behavior — the UI Kit performs none.
+ console.log('Card action on message', event.message.getId(), event.action);
+ }
+}
+```
+
+### Handling actions via the event bus
+
+```typescript expandable
+import { Injectable, DestroyRef, inject } from '@angular/core';
+import {
+ CometChatMessageEvents,
+ ICardActionEvent,
+} from '@cometchat/chat-uikit-angular';
+import type { CometChatCardAction } from '@cometchat/cards-angular';
+
+@Injectable({ providedIn: 'root' })
+export class CardActionHandler {
+ private readonly destroyRef = inject(DestroyRef);
+
+ start(): void {
+ // Subscribe once; auto-unsubscribes when the injector is destroyed.
+ CometChatMessageEvents.subscribeOnCardActionClicked((event: ICardActionEvent) => {
+ const action = event.action as CometChatCardAction;
+ switch (action.type) {
+ case 'openUrl':
+ window.open(action.url, '_blank', 'noopener,noreferrer');
+ break;
+ case 'copyToClipboard':
+ navigator.clipboard?.writeText(action.value);
+ break;
+ // ...handle the remaining action types
+ }
+ }, this.destroyRef);
+ }
+}
+```
+
+The full set of action types and a complete reference handler are covered in the [Card Messages guide](/ui-kit/angular/guides/card-messages#handling-card-actions).
+
+## Fallback Behavior
+
+When `getCard()` returns nothing drawable (`null`, a blank string, or an empty object), the bubble renders a single line of fallback text instead of an empty card. The fallback is resolved in this order:
+
+1. `message.getFallbackText()`
+2. `message.getText()`
+3. Localized `"Card Message"` (the `card_message` localization key)
+
+This keeps the conversation readable even if a card payload is malformed or a client cannot render it.
+
+## Customization
+
+The card's internal layout, colors, and typography are controlled by the renderer through [`themeMode`](#properties) and [`themeOverride`](#properties). The bubble wrapper and fallback line are styled with CSS variables:
+
+```css expandable
+cometchat-card-bubble {
+ /* Spacing around the card content */
+ --cometchat-spacing-2: 8px;
+ --cometchat-spacing-3: 12px;
+
+ /* Fallback text */
+ --cometchat-font-body-regular: 400 14px 'Inter';
+ --cometchat-text-color-secondary: #666666;
+
+ /* Bubble surface (incoming) */
+ --cometchat-background-color-02: #F5F5F5;
+ --cometchat-radius-3: 12px;
+}
+```
+
+To restyle the rendered card itself (button colors, header, body), pass a `themeOverride` to the bubble rather than overriding CSS — the card DOM is owned by the renderer.
+
+## Technical Details
+
+- **Standalone Component** — import and use independently.
+- **Change Detection** — `OnPush` for optimal performance; uses Angular signals for the card payload and fallback.
+- **Renderer dependency** — `CometChatCardViewComponent` from `@cometchat/cards-angular`.
+- **Callback before schema** — the renderer's `(onAction)` output is bound before `[cardJson]` is assigned, so the action callback is registered before the card schema is rendered.
+
+## Related
+
+- **[Card Messages guide](/ui-kit/angular/guides/card-messages)** — the full card rendering implementation: developer cards, agent cards, streaming cards, and the complete action vocabulary.
+- **[CometChatMessageBubble](/ui-kit/angular/components/cometchat-message-bubble)** — routes card messages to this bubble.
+- **[CometChatMessageList](/ui-kit/angular/components/cometchat-message-list)** — renders card bubbles in the conversation.
+- **[Events](/ui-kit/angular/events)** — the `ccCardActionClicked` event reference.
diff --git a/ui-kit/angular/events.mdx b/ui-kit/angular/events.mdx
index 6475955f7..95062e838 100644
--- a/ui-kit/angular/events.mdx
+++ b/ui-kit/angular/events.mdx
@@ -54,6 +54,7 @@ Events provide decoupled communication between UIKit components using a publish/
| **ccMessageDeleted** | Triggered when the user successfully deletes a message. |
| **ccMessageRead** | Triggered when the sent message is read by the receiver. |
| **ccLiveReaction** | Triggered when the user sends a live reaction. |
+| **ccCardActionClicked** | Triggered when a user taps an interactive element on a card (developer card or nested agent card). Carries an `ICardActionEvent` with the owning `message` (or `null` for a streaming card), the raw renderer `action`, and the originating `elementId`/`cardJson`. The UIKit forwards the action untouched and runs no behavior. See the [Card Messages guide](/ui-kit/angular/guides/card-messages#handling-card-actions). |
### SDK Listener Events
@@ -69,6 +70,7 @@ Events provide decoupled communication between UIKit components using a publish/
| **onMessageEdited** | Emitted when the CometChat SDK listener indicates that a message has been edited. |
| **onMessageDeleted** | Emitted when the CometChat SDK listener indicates that a message has been deleted. |
| **onTransientMessageReceived** | Emitted when the CometChat SDK listener receives a transient message. |
+| **onCardMessageReceived** | Emitted when the CometChat SDK listener receives a developer card message (`category: "card"`). Carries a `CometChat.CardMessage`. The UIKit renders cards but never sends or creates them. |
## CometChatCallEvents
diff --git a/ui-kit/angular/guides/card-messages.mdx b/ui-kit/angular/guides/card-messages.mdx
new file mode 100644
index 000000000..5ef5a1f09
--- /dev/null
+++ b/ui-kit/angular/guides/card-messages.mdx
@@ -0,0 +1,241 @@
+---
+title: "Card Messages"
+description: "How the Angular UIKit renders Card Messages — developer cards, persisted agent cards, and streaming agent cards — and how to handle card actions."
+---
+
+
+
+| Field | Value |
+| --- | --- |
+| Package | `@cometchat/chat-uikit-angular` |
+| Renderer | `@cometchat/cards-angular` (`CometChatCardView`) |
+| Components | `CometChatCardBubble`, `CometChatAIAssistantMessageBubble`, `CometChatStreamMessageBubble` |
+| Key event | `CometChatMessageEvents.ccCardActionClicked` |
+| Required setup | `CometChatUIKit.init(uiKitSettings)` then `CometChatUIKit.login("UID")` |
+| Purpose | Render structured, interactive card payloads inside conversations and forward card actions to your app |
+| Related | [Card Bubble](/ui-kit/angular/components/cometchat-card-bubble) \| [AI Assistant Chat](/ui-kit/angular/components/cometchat-ai-assistant-chat) \| [Events](/ui-kit/angular/events) |
+
+
+
+Card Messages are structured, interactive cards delivered inside conversations. The Angular UIKit renders them through the prebuilt [`@cometchat/cards-angular`](https://www.npmjs.com/package/@cometchat/cards-angular) renderer and forwards every card action back to your application.
+
+
+ The UIKit is a **render-only** consumer of cards: it draws cards delivered by the SDK and forwards their actions to your app. It never parses or mutates the card body, and never sends or creates cards.
+
+
+## The render-only contract
+
+The UIKit is a **render-only** consumer of cards. For every card surface, the rules are identical:
+
+1. **Pass the payload through unchanged.** The raw card payload is read from the SDK, serialized verbatim (`JSON.stringify`), and handed to `CometChatCardView` as a `cardJson` string. The UIKit performs **no transformation**.
+2. **Forward actions, run no behavior.** The renderer emits actions through callbacks. The UIKit forwards them on the `ccCardActionClicked` event bus. Your app implements **all** behavior (open a URL, start a chat, call an API, …).
+3. **Fall back gracefully.** When a payload is empty or invalid, a single fallback line is shown instead of a broken card.
+
+This contract is centralized in a small set of helpers so the developer bubble, the agent bubble, and the streaming bubble all treat payloads identically.
+
+## Three delivery paths
+
+A card can reach the conversation in three ways. The UIKit routes each one to the correct renderer automatically.
+
+| Path | Source | Component | Routed by |
+| --- | --- | --- | --- |
+| **Developer card** | `CardMessage` with `category: "card"` | [`CometChatCardBubble`](/ui-kit/angular/components/cometchat-card-bubble) | message **category** |
+| **Persisted agent card** | `AIAssistantMessage` content block | `CometChatAIAssistantMessageBubble` | element `type: "card"` |
+| **Streaming agent card** | Live `card_start` / `card` / `card_end` events | `CometChatStreamMessageBubble` | stream event type |
+
+### Developer cards
+
+A message with category `"card"` is routed to `CometChatCardBubble` by [`CometChatMessageBubble`](/ui-kit/angular/components/cometchat-message-bubble), keyed on **category** (the developer `type` is arbitrary). The bubble renders `message.getCard()` and forwards taps on the `ccCardActionClicked` bus.
+
+In the conversation list, a card message's preview is its `getText()` if present, otherwise the localized `"Card Message"` label.
+
+To react to incoming developer cards in real time, listen on the message bus:
+
+```typescript expandable
+import { Injectable, DestroyRef, inject } from '@angular/core';
+import { CometChat } from '@cometchat/chat-sdk-javascript';
+import { CometChatMessageEvents } from '@cometchat/chat-uikit-angular';
+
+@Injectable({ providedIn: 'root' })
+export class CardListener {
+ private readonly destroyRef = inject(DestroyRef);
+
+ start(): void {
+ CometChatMessageEvents.subscribeOnCardMessageReceived(
+ (card: CometChat.CardMessage) => {
+ console.log('Card received:', card.getId(), card.getCard());
+ },
+ this.destroyRef
+ );
+ }
+}
+```
+
+See the [Card Bubble](/ui-kit/angular/components/cometchat-card-bubble) component reference for inputs, events, and theming.
+
+### Persisted agent cards
+
+After an AI agent run completes, the persisted `AIAssistantMessage` exposes its content as an **ordered list of blocks** via `getElements()`. `CometChatAIAssistantMessageBubble` renders them in order:
+
+- a `text` block renders as Markdown (via [`CometChatMarkdownRenderer`](/ui-kit/angular/components/cometchat-markdown-renderer));
+- a `card` block renders through `CometChatCardView`, using the same render-only path as developer cards.
+
+When a message has no elements (older messages), the bubble falls back to `getText()`. No additional wiring is required — the [AI Assistant Chat](/ui-kit/angular/components/cometchat-ai-assistant-chat) flow instantiates this bubble for you. Taps on a nested agent card are forwarded on the same `ccCardActionClicked` bus.
+
+### Streaming agent cards
+
+While an agent run is streaming, cards arrive progressively and are rendered by `CometChatStreamMessageBubble`, which subscribes to the streaming service's `messageStream$`:
+
+| Stream event | Behavior |
+| --- | --- |
+| `card_start` | Shows an in-place loader labeled with the event's `executionText`, keyed by `cardId`. |
+| `card` | Replaces the loader (correlated by `cardId`) with the rendered card. |
+| `card_end` | No-op — the run-complete persisted `AIAssistantMessage` replaces the streamed bubble. |
+
+Because no persisted message exists yet during streaming, a tap on a streaming card is forwarded with `message: null` on the `ccCardActionClicked` bus; the persisted bubble that follows is the source of truth.
+
+## Handling card actions
+
+The Cards renderer emits actions but never executes them. The UIKit forwards each action **untouched** on `CometChatMessageEvents.ccCardActionClicked`. Subscribe **once** at app startup and dispatch by action type.
+
+### The event payload
+
+```typescript
+export interface ICardActionEvent {
+ /**
+ * The owning message — CardMessage (developer) or AIAssistantMessage
+ * (persisted agent card). null only for a card tapped while the agent run
+ * is still streaming.
+ */
+ message: CometChat.BaseMessage | null;
+ /** The renderer's raw discriminated action (CometChatCardAction). */
+ action: unknown;
+ /** The renderer element id that emitted the action (used by customCallback). */
+ elementId?: string;
+ /** The raw card JSON the action originated from (used by customCallback). */
+ cardJson?: string;
+}
+```
+
+### Action types
+
+`action` is a `CometChatCardAction` — a discriminated union (from `@cometchat/cards-angular`) narrowed by its `type`. The UIKit forwards all of them; your app decides what each one does:
+
+| `type` | Intended behavior |
+| --- | --- |
+| `openUrl` | Open a URL (in a new tab or a webview). |
+| `copyToClipboard` | Copy a value to the clipboard. |
+| `downloadFile` | Download a file. |
+| `sendMessage` | Send a text message to a user/group (or the current conversation). |
+| `apiCall` | Make an HTTP request. |
+| `chatWithUser` | Open a one-to-one chat with a user. |
+| `chatWithGroup` | Open a group chat. |
+| `initiateCall` | Start an audio/video call. |
+| `customCallback` | Invoke an app-defined handler, keyed by `callbackId`. |
+
+### Reference handler
+
+Subscribe once (a root-provided service is the natural home) and dispatch by `type`. The snippet below mirrors the sample app's reference implementation:
+
+```typescript expandable
+import { Injectable, DestroyRef, inject } from '@angular/core';
+import { CometChat } from '@cometchat/chat-sdk-javascript';
+import {
+ CometChatMessageEvents,
+ ICardActionEvent,
+} from '@cometchat/chat-uikit-angular';
+import type { CometChatCardAction } from '@cometchat/cards-angular';
+
+/** Narrows the discriminated card action union to one variant by its type. */
+type Action = Extract;
+
+@Injectable({ providedIn: 'root' })
+export class CardActionService {
+ private readonly destroyRef = inject(DestroyRef);
+ private started = false;
+
+ /** Subscribe once. Idempotent; auto-unsubscribes when the injector is destroyed. */
+ start(): void {
+ if (this.started) return;
+ this.started = true;
+ CometChatMessageEvents.subscribeOnCardActionClicked(
+ (event) => this.dispatch(event),
+ this.destroyRef
+ );
+ }
+
+ private dispatch(event: ICardActionEvent): void {
+ const action = event.action as CometChatCardAction | null | undefined;
+ if (!action || typeof action !== 'object') return;
+
+ switch (action.type) {
+ case 'openUrl':
+ this.openUrl(action);
+ break;
+ case 'copyToClipboard':
+ this.copyToClipboard(action);
+ break;
+ case 'chatWithUser':
+ void this.chatWithUser(action);
+ break;
+ // ...handle downloadFile, sendMessage, apiCall, chatWithGroup,
+ // initiateCall, and customCallback the same way.
+ default:
+ break; // Unknown action — the renderer owns the action vocabulary.
+ }
+ }
+
+ private openUrl(a: Action<'openUrl'>): void {
+ if (!a.url) return;
+ const target = a.openIn === 'webview' ? '_self' : '_blank';
+ window.open(a.url, target, 'noopener,noreferrer');
+ }
+
+ private copyToClipboard(a: Action<'copyToClipboard'>): void {
+ if (a.value == null) return;
+ navigator.clipboard?.writeText(a.value);
+ }
+
+ private async chatWithUser(a: Action<'chatWithUser'>): Promise {
+ if (!a.uid) return;
+ const user = await CometChat.getUser(a.uid);
+ // Navigate your app to the conversation with `user`.
+ }
+}
+```
+
+Start the service once your shell is ready — for example from your home component's `ngOnInit`:
+
+```typescript
+export class HomeComponent implements OnInit {
+ constructor(private cardActions: CardActionService) {}
+
+ ngOnInit(): void {
+ this.cardActions.start();
+ }
+}
+```
+
+
+ Each tap is forwarded on **one** bus to all subscribers. Subscribe in a single place (a root service) so an action runs exactly once. If you also bind the [`(onCardAction)` output](/ui-kit/angular/components/cometchat-card-bubble#card-actions) on a directly-rendered card bubble, handle the action on only one of the two channels.
+
+
+### `customCallback` actions
+
+A `customCallback` action carries a `callbackId` (and optional `payload`). Map each `callbackId` to an app-defined handler, and use `event.elementId` / `event.cardJson` / `event.message?.getId()` for context. No server message is sent by the UIKit for a custom callback — it is entirely app-defined.
+
+## Fallback behavior
+
+Every card surface resolves a single-line fallback when the payload is empty or invalid:
+
+1. `getFallbackText()` (when available on the message/element)
+2. `getText()`
+3. Localized `"Card Message"`
+
+This guarantees a readable conversation even when a card cannot be rendered.
+
+## Related
+
+- **[Card Bubble](/ui-kit/angular/components/cometchat-card-bubble)** — the developer card component reference.
+- **[AI Assistant Chat](/ui-kit/angular/components/cometchat-ai-assistant-chat)** — the agent chat experience that renders persisted and streaming agent cards.
+- **[Events](/ui-kit/angular/events)** — `ccCardActionClicked` and `onCardMessageReceived` reference.