# Comment

> A discussion row with replies, a chat bubble, and a composer that sends on Enter.

Source: https://ngwr.dev/reference/components/comment  
Kind: Component  
Added in v15.5

## Installation

Import the class, and `@use` the entry point in your global styles. Nothing warns you when the `@use` is missing.

```typescript
import { Component } from '@angular/core';
import { WrComment, WrCommentComposer } from 'ngwr/comment';

@Component({ imports: [WrComment, WrCommentComposer] })
export class MyComponent {}
```

```scss
@use 'ngwr/comment';
```

## Thread

Project the avatar, the body, the actions and the replies. The app owns the data and formats the time; `datetime` makes it a real `<time>`.

```angular-html
<wr-comment author="Ann Lee" time="2 hours ago" [datetime]="posted">
  <wr-avatar size="2.5rem" alt="">AL</wr-avatar>
  Looks good. One question on the empty state below.
  <div wrCommentActions>
    <button wr-btn size="sm" outlined>Reply</button>
  </div>
  <wr-comment author="Bob Ray" time="an hour ago">
    <wr-avatar size="2rem" alt="">BR</wr-avatar>
    It falls back to the placeholder, same as the input.
  </wr-comment>
</wr-comment>
```

## Chat

`variant="bubble"` draws a message; `side="end"` is the reader's own. Both sides follow the writing direction. The composer below sends on Enter.

```angular-html
<div role="log" aria-label="Chat">
  @for (m of messages(); track m.id) {
    <wr-comment variant="bubble" [side]="m.own ? 'end' : 'start'" [time]="m.at | wrDate: 'time'" [datetime]="m.at">
      {{ m.text }}
    </wr-comment>
  }
</div>
<wr-comment-composer placeholder="Message" [(value)]="draft" (send)="post($event)" />
```

## Composer

`sending` holds the button while a request is out. With `clearOnSend` off the text stays until the app clears it, so a failed request loses nothing.

```angular-html
<wr-comment-composer
  placeholder="Reply"
  [sending]="posting()"
  [clearOnSend]="false"
  [(value)]="draft"
  (send)="postSlowly($event)"
/>
```

| Keys | Does |
| --- | --- |
| `Enter` | Sends. With `[enterSends]="false"` it adds a line instead. |
| `Shift+Enter` | Adds a line. |
| `Ctrl+Enter`, `Cmd+Enter` | Sends, whatever Enter does. |
| Enter during IME composition | Confirms the composition and never sends. |

## Forms

The composer is a value control: bind it with `[formField]`, `[(value)]` or [any Angular form](https://ngwr.dev/guides/forms).

```angular-html
<wr-comment-composer placeholder="Message" [formField]="form.draft" (send)="post($event)" />
```

## Accessibility

- Each comment is an `article` named after its `author`.
- Replies sit in a `group` named `repliesLabel`, from the `comment.replies` catalog key.
- No heading level is assumed. Put a chat in `role="log"` so new messages are announced.
- The send button is named `sendLabel`, from `comment.send`. Focus stays in the field after a send.

## API

`<wr-comment>` inputs.

| Name | Description | Type | Default |
| --- | --- | --- | --- |
| `author` | Who wrote it. Also the article's accessible name. Leave it empty when a `[wrCommentAuthor]` element draws the name instead. | `string` | `''` |
| `time` | When it was written, already formatted: the component does no date formatting. Run a `Date` through the `wrDate` pipe on the way in. | `string` | `''` |
| `datetime` | The same moment, machine-readable. With it the time renders as a `<time datetime>`: a `Date` is written as ISO 8601, a string as given. | `Date \| string \| null` | `null` |
| `variant` | `row` is a discussion entry; `bubble` is a chat message, which takes no replies. | `WrCommentVariant` | `'row'` |
| `side` | Which inline side a bubble sits on. `end` is the reader's own message. Ignored by the `row` variant. | `WrCommentSide` | `'start'` |
| `repliesLabel` | Accessible name of the replies group. Falls back to `comment.replies`, then `'Replies'`. | `string \| null` | `null` |

## Composer API

`<wr-comment-composer>` inputs and outputs.

| Name | Description | Type | Default |
| --- | --- | --- | --- |
| `placeholder` | Native placeholder text. | `string` | `''` |
| `ariaLabel` | Accessible name for the field. Falls back to the placeholder: the native `<textarea>` lives inside the component, so a `<label for>` outside cannot reach it. | `string \| null` | `null` |
| `sendLabel` | Accessible name of the send button. Falls back to `comment.send`, then `'Send'`. | `string \| null` | `null` |
| `rows` | Visible row count while the field is empty. | `number` | `1` |
| `maxRows` | Rows the field grows to before it scrolls on its own. | `number` | `6` |
| `disabled` | Disable the field and the button. Bound automatically from the field's disabled state when used with `[formField]`. The text is kept. | `boolean` | `false` |
| `readonly` | Read-only state: the text is shown and announced, keeps its tab stop and cannot be edited or sent. | `boolean` | `false` |
| `sending` | A send is in flight: the button is disabled, the host is `aria-busy` and Enter does nothing. The field stays editable and the text is kept. | `boolean` | `false` |
| `enterSends` | `true`: Enter sends and Shift+Enter adds a line. `false`: Enter adds a line and only Ctrl+Enter or Cmd+Enter sends. | `boolean` | `true` |
| `clearOnSend` | Clear the field before `send` emits. With `false` the text stays until the app clears the model itself. | `boolean` | `true` |
| `value` | The draft. Bound by `[formField]`, or two-way via `[(value)]`. | `string` | `''` |
| `(touch)` | Emitted on blur so a bound field can mark itself touched. | `void` | — |
| `(send)` | The trimmed text, once per send. Where it goes is the app's business. | `string` | — |

## CSS variables

Set these on the element itself, with a rule more specific than its Declared on selector. In a global stylesheet, put a class of yours on an ancestor it really has: `<body>` for an overlay panel, which renders outside your markup. In a component stylesheet the selector alone works, but only for an element in that component's own template. A value on a wrapper or `:root` is shadowed, because `ngwr/comment` declares each default on its own element.

| Variable | Default | Declared on |
| --- | --- | --- |
| `--wr-comment-actions-gap` | `var(--wr-space-sm)` | `.wr-comment` |
| `--wr-comment-author-color` | `var(--wr-color-on-surface)` | `.wr-comment` |
| `--wr-comment-author-font-weight` | `var(--wr-font-weight-semibold)` | `.wr-comment` |
| `--wr-comment-body-color` | `var(--wr-color-on-surface)` | `.wr-comment` |
| `--wr-comment-body-gap` | `var(--wr-space-xs)` | `.wr-comment` |
| `--wr-comment-bubble-bg` | `var(--wr-color-fill)` | `.wr-comment` +1 variant override |
| `--wr-comment-bubble-color` | `var(--wr-color-on-surface)` | `.wr-comment` +1 variant override |
| `--wr-comment-bubble-max-width` | `80%` | `.wr-comment` |
| `--wr-comment-bubble-padding-x` | `var(--wr-space-md)` | `.wr-comment` |
| `--wr-comment-bubble-padding-y` | `var(--wr-space-sm)` | `.wr-comment` |
| `--wr-comment-bubble-radius` | `var(--wr-border-radius-lg)` | `.wr-comment` |
| `--wr-comment-bubble-time-color` | `var(--wr-color-on-surface-muted)` | `.wr-comment` +1 variant override |
| `--wr-comment-composer-bg` | `var(--wr-color-surface)` | `.wr-comment-composer` +2 variant overrides |
| `--wr-comment-composer-border` | `var(--wr-border-base)` | `.wr-comment-composer` +1 variant override |
| `--wr-comment-composer-gap` | `var(--wr-space-xs)` | `.wr-comment-composer` |
| `--wr-comment-composer-padding` | `var(--wr-space-xs)` | `.wr-comment-composer` |
| `--wr-comment-composer-radius` | `var(--wr-control-radius-md)` | `.wr-comment-composer` |
| `--wr-comment-composer-ring` | `transparent` | `.wr-comment-composer` +1 variant override |
| `--wr-comment-composer-send-size` | `2.25rem` | `.wr-comment-composer` |
| `--wr-comment-font-family` | `var(--wr-font-family-base)` | `.wr-comment` |
| `--wr-comment-font-size` | `var(--wr-text-sm)` | `.wr-comment` |
| `--wr-comment-gap` | `var(--wr-space-md)` | `.wr-comment` |
| `--wr-comment-head-gap` | `var(--wr-space-sm)` | `.wr-comment` |
| `--wr-comment-indent` | `var(--wr-space-xl)` | `.wr-comment` |
| `--wr-comment-line-height` | `var(--wr-text-sm-leading)` | `.wr-comment` |
| `--wr-comment-replies-gap` | `var(--wr-space-lg)` | `.wr-comment` |
| `--wr-comment-replies-margin-top` | `var(--wr-space-lg)` | `.wr-comment` |
| `--wr-comment-time-color` | `var(--wr-color-on-surface-muted)` | `.wr-comment` |
| `--wr-comment-time-font-size` | `var(--wr-text-xs)` | `.wr-comment` |

## Dependencies

| Built on | [Button](https://ngwr.dev/reference/components/button), [Textarea](https://ngwr.dev/reference/components/textarea) |
| --- | --- |

## See also

- [Avatar](https://ngwr.dev/reference/components/avatar) — A user's picture, with initials as the fallback.
- [Textarea](https://ngwr.dev/reference/components/textarea) — A multi-line text field that can grow with its content.
- [Timeline](https://ngwr.dev/reference/components/timeline) — A list of events in order, vertical or horizontal.
- [Mention](https://ngwr.dev/reference/components/mention) — Suggest names or tags while typing @ in a text field.
- [wrDate](https://ngwr.dev/reference/pipes/wr-date) — Format a date with a named preset or a token string.
- [Button](https://ngwr.dev/reference/components/button) — A button for an action or a link.
