# Comments Panel
URL: /docs/web/components/comments-panel
Comments Panel component.
**Demo:**
```tsx
'use client';
// @custom-demo
import { useState } from 'react';
import { CommentsPanel } from '@docyrus/ui/components/comments-panel';
import { Button } from '@docyrus/ui/primitives/ui/button';
import { cn } from '@docyrus/ui/primitives/lib/utils';
import { PropControls } from '@/components/prop-controls';
import { useDemoData } from '@/data/comments-panel-data';
export function CommentsPanelDemo() {
const { props, controls, actions } = useDemoData();
const [open, setOpen] = useState(false);
return (
);
}
```
The composer supports **`@` user mentions**, an **emoji picker**, and a **rich-formatting toggle** (a plain textarea by default, switching to the markdown editor on demand — Teams-style). Comments are stored and rendered as markdown regardless of which mode authored them, so mentions and emoji round-trip everywhere.
## Installation
```bash
pnpm dlx @docyrus/cli add @docyrus/ui-comments-panel
```
**Dependencies:**
- [react-markdown](https://www.npmjs.com/package/react-markdown)
- [remark-gfm](https://www.npmjs.com/package/remark-gfm)
- [date-fns](https://www.npmjs.com/package/date-fns)
- [lucide-react](https://www.npmjs.com/package/lucide-react)
Composes the [Emoji Picker](/docs/web/components/emoji-picker) and Simple Markdown Editor components — the CLI installs them automatically as registry dependencies.
## Usage
```tsx
import { CommentsPanel } from "@docyrus/ui/components/comments-panel";
import type { CommentsPanelProps } from "@docyrus/ui/components/comments-panel";
const comments: CommentsPanelProps['comments'] = [
{ id: "c-1", message: "Looks good to me.", attachments: null, created_on: "2025-12-17T10:00:00Z", last_modified_on: "2025-12-17T10:00:00Z", created_by: "u-alice", record_id: "rec-1", parent_id: null }
];
function Demo() {
return (
console.log(message, mentions)}
onUpdateComment={(id, message, mentions) => console.log(id, message, mentions)}
onDeleteComment={(id) => console.log("delete", id)}
/>
);
}
```
## Mentions, emoji & rich formatting
- **`@` mentions** — typing `@` opens a user picker sourced from the `users` prop. The selected user is stored inline as a markdown link (`[@Name](mention://)`) and rendered as a highlighted chip. `onCreateComment` / `onUpdateComment` receive the referenced user ids in `mentions`. Disable with `enableMentions={false}`.
- **Emoji** — the composer's emoji button inserts native unicode at the caret in both plain and rich modes. Disable with `enableEmoji={false}`.
- **Rich formatting** — a plain textarea by default; the `Aa` toggle switches to the markdown editor. The mode is authoring-only — the stored value is always markdown and always rendered as markdown, so a comment written in plain mode and one written in rich mode display identically. Control availability with `enableRichFormatting`, the initial mode with `defaultRichFormatting`, or persist the user's choice with the controlled `richFormatting` / `onRichFormattingChange` pair.
## API Reference
| Prop | Type | Default |
|------|------|---------|
| `comments` | `DocyrusComment[]` | — |
| `currentUser` | `CommentUser` | — |
| `users` | `CommentUser[]` | — |
| `title` | `string` | `"Comments"` |
| `editable` | `boolean` | `true` |
| `isLoading` | `boolean` | `false` |
| `maxHeight` | `number \| string` | `'24rem'` |
| `onCreateComment` | `({ message, parentId?, attachments?, mentions? }) => void \| Promise` | — |
| `onUpdateComment` | `(id: string, message: string, mentions?: string[]) => void \| Promise` | — |
| `onDeleteComment` | `(id: string) => void \| Promise` | — |
| `onUploadFile` | `(file: File) => Promise` | — |
| `isCreatePending` | `boolean` | `false` |
| `isDeletePending` | `boolean` | `false` |
| `enableRichFormatting` | `boolean` | `true` |
| `defaultRichFormatting` | `boolean` | `false` |
| `richFormatting` | `boolean` | — (controlled) |
| `onRichFormattingChange` | `(rich: boolean) => void` | — |
| `enableEmoji` | `boolean` | `true` |
| `enableMentions` | `boolean` | `true` |
| `className` | `string` | — |
## Components
| Component | Description |
|-----------|-------------|
| `CommentsPanel` | Public export |
| `CommentComposer` | Internal — `comment-composer.tsx` (create / reply / edit) |
| `CommentEmptyState` | Internal — `comment-empty-state.tsx` |
| `CommentItem` | Internal — `comment-item.tsx` |
| `CommentThread` | Internal — `comment-thread.tsx` |
| `MentionList` | Internal — `mention-list.tsx` |
| `useCommentMention` | Internal — `use-comment-mention.tsx` |
## Type Exports
| Type | Description |
|------|-------------|
| `CommentsPanelProps` | — |
| `DocyrusComment` | — |
| `CommentThread` | — |
| `CommentAttachment` | — |
| `CommentUser` | — |
## Type Reference
### DocyrusComment
| Field | Type | Description |
|-------|------|-------------|
| `id` | `string` | — |
| `message` | `string` | — |
| `attachments` | `Array \| null` | — |
| `created_on` | `string` | ISO date string |
| `last_modified_on` | `string` | ISO date string |
| `created_by` | `string` | user id |
| `record_id` | `string \| null` | — |
| `parent_id` | `string \| null` | reply to |
### CommentAttachment
| Field | Type | Description |
|-------|------|-------------|
| `id` | `string` | — |
| `file_name` | `string` | — |
| `file_type` | `string` | MIME type |
| `file_size` | `number` | bytes |
| `signed_url` | `string \| null` | download URL |
### CommentUser
| Field | Type | Description |
|-------|------|-------------|
| `id` | `string` | — |
| `firstname` | `string \| null` | — |
| `lastname` | `string \| null` | — |
| `photo` | `string \| null` | avatar image URL (mention list + comment avatars) |
### CommentThread
| Field | Type | Description |
|-------|------|-------------|
| `comment` | `DocyrusComment` | — |
| `replies` | `Array` | — |