> ## Documentation Index
> Fetch the complete documentation index at: https://www.cometchat.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Pinned Messages

> A panel component listing the pinned messages of a one-on-one or group conversation

The `CometChatPinnedMessages` component lists every message pinned in a conversation, newest pin first. Pins are conversation-wide — everyone in the chat sees the same list — so the panel is a shared, always-current view of what the conversation has singled out.

## Overview

The Pinned Messages panel provides:

* **Conversation-scoped list**: Pass either a `user` or a `group`; the panel fetches that conversation's pins
* **Real message bubbles**: Each row renders the actual message bubble, so media, polls, and formatted text look as they do in the chat
* **Pinned-by attribution**: Each row is labelled with who pinned it and when
* **Inline unpin**: A per-row unpin control, behind a confirmation dialog
* **Row options**: Save, Copy, Info, Translate, Report, and Message privately under a three-dot menu
* **Message information**: Opens over the panel, without leaving it — Info is self-contained, so it is not forwarded to the host
* **System pins respected**: A pin the app placed app-wide cannot be lifted by a member, so Unpin is withheld on those rows
* **Live updates**: Edits, deletions, reactions, and pin changes are reflected without a refetch
* **Focus trap**: Traps keyboard focus within the panel for modal-like behavior

<Note>
  Pin Message is gated by the `features.ux.messages.pinned.enabled` app setting, which CometChat provisions server-side. Until it is on, `CometChat.isPinMessageEnabled()` resolves `false` and the pin surfaces do not render. See [Enabling Pin and Save](/docs/ui-kit/angular/guides/pin-and-save-messages#enabling-the-feature) for the development override.
</Note>

<Info>
  **Live Preview** — a group conversation with several pinned messages, newest pin first.
  [Open in Storybook ↗](https://storybook.cometchat.io/angular/?path=/story/components-messages-cometchat-pinned-messages--default)
</Info>

<iframe src="https://storybook.cometchat.io/angular/iframe.html?id=components-messages-cometchat-pinned-messages--default&viewMode=story&shortcuts=false&singleStory=true" className="w-full rounded-xl" loading="lazy" style={{height: "600px", border: "1px solid #e0e0e0"}} title="CometChat Pinned Messages — Default" allow="clipboard-write" />

## Basic Usage

### Group Pinned Messages

```typescript expandable theme={null}
import { Component } from '@angular/core';
import { CometChat } from '@cometchat/chat-sdk-javascript';
import { CometChatPinnedMessagesComponent } from '@cometchat/chat-uikit-angular';

@Component({
  selector: 'app-pinned-demo',
  standalone: true,
  imports: [CometChatPinnedMessagesComponent],
  template: `
    <cometchat-pinned-messages
      [group]="group"
      (messageClick)="onMessageClick($event)"
      (closeClick)="onClose()">
    </cometchat-pinned-messages>
  `
})
export class PinnedDemoComponent {
  group!: CometChat.Group;

  /** Jump the main message list to the tapped message. */
  onMessageClick(message: CometChat.BaseMessage): void {
    console.log('scroll to', message.getId());
  }

  onClose(): void {
    console.log('Panel closed');
  }
}
```

### One-on-One Pinned Messages

Pass `user` instead of `group`. The two are mutually exclusive — set exactly one.

```typescript expandable theme={null}
@Component({
  selector: 'app-pinned-dm-demo',
  standalone: true,
  imports: [CometChatPinnedMessagesComponent],
  template: `
    <cometchat-pinned-messages
      [user]="user"
      (closeClick)="onClose()">
    </cometchat-pinned-messages>
  `
})
export class PinnedDmDemoComponent {
  user!: CometChat.User;

  onClose(): void {}
}
```

### Opening the Panel from the Message Header

`CometChatMessageHeader` can add a **Pinned messages** entry to its overflow menu. The header only asks for the panel — the host decides where it appears.

```typescript expandable theme={null}
@Component({
  selector: 'app-messages',
  standalone: true,
  imports: [CometChatMessageHeaderComponent, CometChatPinnedMessagesComponent],
  template: `
    <cometchat-message-header
      [group]="group"
      [showPinnedMessagesOption]="true"
      (pinnedMessagesClick)="showPinned = true">
    </cometchat-message-header>

    @if (showPinned) {
      <cometchat-pinned-messages
        [group]="group"
        (messageClick)="onMessageClick($event)"
        (closeClick)="showPinned = false">
      </cometchat-pinned-messages>
    }
  `
})
export class MessagesComponent {
  group!: CometChat.Group;
  showPinned = false;

  onMessageClick(message: CometChat.BaseMessage): void {}
}
```

### Handling Forwarded Options

Unpin, Save, Unsave, Copy, and Message Information are completed by the panel itself. **Translate, Report, and Message privately** need surfaces the panel does not own — a translation cache, a report dialog, another conversation — so they are handed to the host rather than half-built here. Message privately also fires `CometChatUIEvents.ccOpenChat`, so a host already listening for that receives both.

```typescript expandable theme={null}
@Component({
  selector: 'app-pinned-options-demo',
  standalone: true,
  imports: [CometChatPinnedMessagesComponent],
  template: `
    <cometchat-pinned-messages
      [group]="group"
      (messageOptionClick)="onOptionClick($event)">
    </cometchat-pinned-messages>
  `
})
export class PinnedOptionsDemoComponent {
  group!: CometChat.Group;

  onOptionClick(event: { option: ContextMenuItem; message: CometChat.BaseMessage }): void {
    console.log(event.option.id, event.message.getId());
  }
}
```

## Filtering

Pass a `messagesRequestBuilder` to control which pinned messages are fetched — the page size, most commonly. Call `setPinned(true)` on the builder: it is what scopes the request to pinned messages, and without it the request is an ordinary history read.

```typescript expandable theme={null}
import { Component } from '@angular/core';
import { CometChat } from '@cometchat/chat-sdk-javascript';
import { CometChatPinnedMessagesComponent } from '@cometchat/chat-uikit-angular';

@Component({
  selector: 'app-pinned-filtered',
  standalone: true,
  imports: [CometChatPinnedMessagesComponent],
  template: `
    <cometchat-pinned-messages
      [group]="group"
      [messagesRequestBuilder]="builder">
    </cometchat-pinned-messages>
  `
})
export class PinnedFilteredComponent {
  group!: CometChat.Group;

  builder = new CometChat.MessagesRequestBuilder()
    .setPinned(true) // required — scopes the fetch to pinned messages
    .setLimit(30);
}
```

<Note>
  The component re-asserts `setPinned(true)` and the `user` / `group` conversation scope on whatever builder you pass, so those are safe even if you omit them — but keep `setPinned(true)` in your code to make the intent explicit. Do not set a different conversation scope on the builder.
</Note>

## Properties

| Property                     | Type                                                                                | Default     | Description                                                                                                                                                                                               |
| ---------------------------- | ----------------------------------------------------------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `user`                       | `CometChat.User`                                                                    | `undefined` | Scopes the list to a one-on-one conversation. Mutually exclusive with `group`                                                                                                                             |
| `group`                      | `CometChat.Group`                                                                   | `undefined` | Scopes the list to a group. Mutually exclusive with `user`                                                                                                                                                |
| `hideCloseButton`            | `boolean`                                                                           | `false`     | Hides the close button, for hosts that supply their own chrome                                                                                                                                            |
| `headerView`                 | `TemplateRef<unknown>`                                                              | `undefined` | Replaces the default header row                                                                                                                                                                           |
| `emptyView`                  | `TemplateRef<unknown>`                                                              | `undefined` | Replaces the built-in empty state                                                                                                                                                                         |
| `errorView`                  | `TemplateRef<unknown>`                                                              | `undefined` | Replaces the built-in error state                                                                                                                                                                         |
| `loadingView`                | `TemplateRef<unknown>`                                                              | `undefined` | Replaces the built-in loading shimmer                                                                                                                                                                     |
| `messagesRequestBuilder`     | `CometChat.MessagesRequestBuilder`                                                  | `undefined` | Custom request builder. Used as supplied apart from `setPinned` and the conversation scope, which are re-asserted                                                                                         |
| `quickOptionsCount`          | `number`                                                                            | `1`         | How many options sit outside the overflow menu as bare icons                                                                                                                                              |
| `itemView`                   | `TemplateRef<{ $implicit: CometChat.BaseMessage; message: CometChat.BaseMessage }>` | `undefined` | Replaces a whole pinned row. The message arrives as `$implicit` and again as `message`. A replaced row owns its own interaction                                                                           |
| `textFormatters`             | `CometChatTextFormatter[]`                                                          | `undefined` | Formatters applied to each row's text — mentions, links, markdown, or a custom one. Falls back to the global config's set when unset. See [Text Formatters](/docs/ui-kit/angular/guides/custom-text-formatter) |
| `hideUnpinMessageOption`     | `boolean`                                                                           | `false`     | Hides Unpin. Role gating still wins — see [Permissions](#permissions)                                                                                                                                     |
| `hideSaveMessageOption`      | `boolean`                                                                           | `false`     | Hides Save                                                                                                                                                                                                |
| `hideUnsaveMessageOption`    | `boolean`                                                                           | `false`     | Hides Unsave                                                                                                                                                                                              |
| `hideMessageInfoOption`      | `boolean`                                                                           | `false`     | Hides Message Information                                                                                                                                                                                 |
| `hideTranslateMessageOption` | `boolean`                                                                           | `false`     | Hides Translate                                                                                                                                                                                           |
| `hideCopyMessageOption`      | `boolean`                                                                           | `false`     | Hides Copy                                                                                                                                                                                                |
| `hideFlagMessageOption`      | `boolean`                                                                           | `false`     | Hides Report                                                                                                                                                                                              |
| `hideMessagePrivatelyOption` | `boolean`                                                                           | `false`     | Hides "Message privately"                                                                                                                                                                                 |

## Events

| Event                | Payload Type                                                  | Description                                                                                                                                                                                         |
| -------------------- | ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `closeClick`         | `void`                                                        | Emitted when the panel close button is clicked, or Escape is pressed with no overlay open                                                                                                           |
| `messageClick`       | `CometChat.BaseMessage`                                       | Emitted when a row is tapped. Clicks landing on a control inside the row — a menu, an audio player, a link — are not forwarded                                                                      |
| `messageOptionClick` | `{ option: ContextMenuItem; message: CometChat.BaseMessage }` | Emitted for options the panel cannot complete on its own: Translate, Report, and Message privately. Unpin, Save, Unsave, Copy, and Message Information are handled internally and are not forwarded |
| `error`              | `CometChat.CometChatException`                                | Emitted when fetching or unpinning fails                                                                                                                                                            |

## Behavior

### Row Options

Options appear in a fixed order, and `quickOptionsCount` decides how many stay outside the ⋮:

| Option              | Shown when                                                                                                                                          |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| Unpin               | The viewer may unpin — see [Permissions](#permissions)                                                                                              |
| Save / Unsave       | Save Message is enabled for the app. The title and icon follow the message's own `savedAt`                                                          |
| Message Information | The message was sent by the viewer                                                                                                                  |
| Translate           | The message is a text message                                                                                                                       |
| Copy                | The message is a text message                                                                                                                       |
| Report              | The message was sent by someone else                                                                                                                |
| Message privately   | A group conversation, and the message was sent by someone else — there is no private channel to open with yourself, and a one-on-one already is one |

The list is read-only in every other respect: opening it marks nothing as read, moves no unread count, and nothing here edits or deletes a message.

### Permissions

There is **no client-side role gate**. Unpin is offered to every member and the server is the sole authority — a member without the permission still sees the option, the call is refused with `ERR_ACTION_NOT_ALLOWED`, and the optimistic flip reverts with a toast.

Unpin is deliberately **not** restricted to whoever pinned the message either — anyone the server allows can remove any pin.

<Note>
  `[hideUnpinMessageOption]="true"` is the only thing that withholds Unpin from the panel, apart from a [system pin](#system-pins). Set it yourself where your app already knows the viewer cannot unpin; the panel will not work that out on its own.
</Note>

### System Pins

An app can pin a message itself, app-wide, rather than on behalf of a member. A **system pin** (`pinnedBy === "app_system"`) belongs to no one, and the server refuses to lift it for any member — so Unpin is not offered on those rows at all, here or in the message list. Save is untouched: it is private to the viewer and has nothing to do with who pinned.

System pins are capped separately from member pins, through the `features.ux.messages.pinned.system.limit` app setting.

### Confirmation

Unpinning asks for confirmation; pinning does not. Pinning is trivially reversible and a dialog for it would only be friction, whereas unpinning removes something the whole conversation can see.

### Rendering Large Lists

The pinned read is not cursor-paginated — the server ignores `sentAt`/`id` when filtering by pinned — so the panel fetches in one request (limit `100` unless `messagesRequestBuilder` says otherwise) and windows locally: it renders 30 rows at a time and extends the window as you scroll. A conversation with hundreds of pins does not pay to build every bubble up front.

### Live Updates

The panel subscribes to [`CometChatPinSaveEvents`](/docs/ui-kit/angular/events#cometchatpinsaveevents), so a pin or unpin made anywhere — by another member, or by this user on another device — is reflected without a refetch. Edits, deletions, and reactions on a pinned message update its row in place.

## Customization

### CSS Variables

| Variable                                                | Default                                    | Description                                                                     |
| ------------------------------------------------------- | ------------------------------------------ | ------------------------------------------------------------------------------- |
| `--cometchat-pinned-messages-width`                     | `100%`                                     | Panel width — the host owns the width, so the panel fills the space it is given |
| `--cometchat-pinned-messages-height`                    | `100%`                                     | Panel height                                                                    |
| `--cometchat-pinned-messages-background`                | `--cometchat-background-color-01`          | Panel background                                                                |
| `--cometchat-pinned-messages-border`                    | `1px solid --cometchat-border-color-light` | Leading edge border                                                             |
| `--cometchat-pinned-messages-header-padding`            | `12px 16px`                                | Header padding                                                                  |
| `--cometchat-pinned-messages-title-font`                | `--cometchat-font-heading3-bold`           | Header title font                                                               |
| `--cometchat-pinned-messages-item-padding`              | `8px 8px`                                  | Row padding                                                                     |
| `--cometchat-pinned-messages-item-background-hover`     | `--cometchat-extended-primary-color-100`   | Row hover background                                                            |
| `--cometchat-pinned-messages-entry-name-font`           | `--cometchat-font-body-medium`             | "Pinned by" name font                                                           |
| `--cometchat-pinned-messages-entry-date-font`           | `--cometchat-font-caption1-regular`        | Pin timestamp font                                                              |
| `--cometchat-pinned-messages-empty-icon-size`           | `120px`                                    | Empty-state illustration size                                                   |
| `--cometchat-pinned-messages-empty-title-font`          | `--cometchat-font-heading4-bold`           | Empty-state headline font                                                       |
| `--cometchat-pinned-messages-empty-subtitle-gap`        | `--cometchat-margin-2`                     | Gap between headline and explanation                                            |
| `--cometchat-pinned-messages-info-panel-width`          | `90%`                                      | Message-information overlay width                                               |
| `--cometchat-pinned-messages-info-panel-max-width`      | `420px`                                    | Message-information overlay maximum width                                       |
| `--cometchat-pinned-messages-info-panel-shadow`         | `--cometchat-shadow-sm`                    | Message-information overlay shadow                                              |
| `--cometchat-pinned-messages-dialog-overlay-background` | `--cometchat-overlay-background`           | Confirmation dialog scrim                                                       |

## Accessibility

### Keyboard Navigation

* **Escape** dismisses the topmost layer only: the information overlay first, then the confirmation dialog, then the panel itself
* **Tab** cycles within the panel; focus does not escape to the page behind it
* **Enter** / **Space** on a row activates it, as a click does

### Focus Management

Focus is trapped on open and released on destroy, so the panel behaves as a modal surface while it is up.

### Screen Reader Support

* The panel is a labelled `region`
* Rows are exposed as buttons
* Loading, empty, and error states are announced via `role="status"`
* Decorative glyphs — pin markers, media icons, illustrations — are `aria-hidden`

## Related

* [Pin and Save Messages](/docs/ui-kit/angular/guides/pin-and-save-messages) — the feature guide, including how to enable it
* [CometChatSavedMessages](/docs/ui-kit/angular/components/cometchat-saved-messages) — the per-user counterpart
* [CometChatMessageInformation](/docs/ui-kit/angular/components/cometchat-message-information) — opened from a row's Info option
* [CometChatMessageList](/docs/ui-kit/angular/components/cometchat-message-list) — where messages are pinned from
