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

# Behaviors & composition

> Reference default behaviors, prop interactions, and positioning ownership across Velt components.

[`Props`](https://velt.dev/docs/ui-customization/reference/props) tells you a prop's name, type, and default. This page tells you **what it actually does**: how Velt behaves with no props at all, which props fight each other, and what you take over when you leave wireframes.

> [!NOTE]
>
> Generated from the Velt SDK. A name that isn't listed here doesn't exist: don't guess.

## Per-component behavior: every prop (exhaustive)

Every prop's runtime effect, per component. These pages are source-derived, so they win on any conflict with other docs.

| Component group | File |
| - | - |
| `VeltComments` (core), all \~129 props | [`Comments core`](https://velt.dev/docs/ui-customization/reference/behaviors/comments-core) |
| `VeltCommentsSidebar` (V1), `VeltCommentsSidebarV2`, `VeltSidebarButton` | [`Sidebar`](https://velt.dev/docs/ui-customization/reference/behaviors/sidebar) |
| `VeltCommentDialog`, `VeltMultiThreadCommentDialog`, `VeltCommentText`, `VeltInlineCommentsSection`, `VeltInlineReactionsSection` | [`Dialog and inline`](https://velt.dev/docs/ui-customization/reference/behaviors/dialog) |
| `VeltCommentPin`, `VeltCommentBubble`, `VeltCommentTool` | [`Pins, bubbles, and tools`](https://velt.dev/docs/ui-customization/reference/behaviors/pins-tools) |
| `VeltNotificationsPanel`, `VeltNotificationsTool` | [`Notifications`](https://velt.dev/docs/ui-customization/reference/behaviors/notifications) |
| `VeltPresence`, `VeltCursor`, `VeltReactionTool` | [`Presence, cursors, reactions`](https://velt.dev/docs/ui-customization/reference/behaviors/presence-reactions) |
| `VeltRecorderControlPanel`, `VeltRecorderPlayer`, `VeltVideoPlayer`, `VeltVideoEditor`, `VeltHuddle`, `VeltUserInviteTool` | [`Recorder, video, huddle`](https://velt.dev/docs/ui-customization/reference/behaviors/recorder-huddle) |

Events are in [`Events`](https://velt.dev/docs/ui-customization/reference/events); data fields in [`Data models`](https://velt.dev/docs/ui-customization/reference/data-models).

## Default behaviors (no prop needed)

What Velt does out of the box. These are the baseline a prop modifies.

| Behavior | Default | How to change it |
| - | - | - |
| **Composer hidden until the dialog is selected** | On an unselected pin dialog that already has comments, the composer doesn't render. A thread with no comments always shows it. | No "always show composer" prop exists on the pin dialog. Use `persistentCommentMode` or a standalone `VeltCommentComposer`. |
| **Composer opens collapsed** | `composerMode: 'default'` renders it single-line, expanding to full actions on focus. | `composerMode="expanded"` starts it open. |
| **Dialog shows a preview, not the full thread** | An unselected pin dialog shows the first comment only. `showAllComments` defaults `true`, so selecting it shows all. | `collapsedComments`, `collapsedRepliesPreview`, `fullExpanded`. See the matrix below. |
| **Dialog body scrolls** | The thread list is a scroll container and auto-scrolls to the bottom on a new comment. | No prop disables it. Restructure with a [thread-card wireframe](https://velt.dev/docs/ui-customization/layout.md). |
| **Selecting a pin expands the dialog** | With `expandOnSelection: true` (default), selecting reveals all comments and the composer. | `expandOnSelection={false}` selects without expanding. |
| **Sidebar filters restore from the session** | The sidebar re-applies the last-used filters on open, **overriding `defaultMinimalFilter`**. | `defaultMinimalFilter` only applies when the session has no saved filter. |
| **Mentions, reactions, attachments, status, resolve are on** | `userMentions`, `reactions`, `attachments`, `status`, `resolveButton`, `seenByUsers` default on. `priority`, `visibilityOptions`, `screenshot` default off. | [`Feature flags`](https://velt.dev/docs/ui-customization/reference/feature-flags). |
| **Enter inserts a newline** | `enterKeyToSubmit` defaults off, so Cmd/Ctrl+Enter submits. | `enterKeyToSubmit={true}` makes Enter submit. |
| **Dialog and pin are shadow-DOM wrapped** | `dialogShadowDom` / `pinShadowDom` default `true`. | Set the per-surface flag or master `shadowDom` to `false`. |

## Prop-interaction matrix

### `collapsedComments` × `collapsedRepliesPreview`

The most misunderstood pair. Which comment cards render depends on three things: whether the dialog is **selected**, `showAllComments` (default `true`), and `collapsedRepliesPreview`.

| State | What renders |
| - | - |
| Selected, `showAllComments` true (default) | Every comment. |
| Selected, `showAllComments` false (what `collapsedComments` sets) | First + last only, with a "N more replies" divider between. |
| Unselected, `collapsedRepliesPreview` true | The same first + last teaser, before selection. |
| Unselected, default | First comment only. |

```
┌─────────────────────────────┐
│  first comment   (index 0)  │
│  ── N more replies ──       │
│  last comment  (index n-1)  │
└─────────────────────────────┘
```

| Prop | Default | What it does |
| - | - | - |
| `collapsedComments` | `false` | Collapses even a **selected** dialog to first + last. |
| `collapsedRepliesPreview` | `false` | Shows that teaser in the **unselected** state. |

> [!WARNING]
>
> **First + second + last is not achievable with props.** The collapsed view is strictly `index === 0 || index === length - 1`. There's no prop for "first three" or "first + newest two". For any other pattern, use a [thread-card wireframe](https://velt.dev/docs/ui-customization/layout.md) or [primitives](https://velt.dev/docs/ui-customization/primitives.md). `maxReplyAvatars` (default `3`) caps **avatars**, not comment cards.

### `sortBy` × `sortOrder`

`sortBy` picks the field (`'createdAt' | 'lastUpdated'`), `sortOrder` the direction (`'asc' | 'desc'`). They apply together to the comment list in the dialog and inline section. With neither set, comments render in creation order. They only reorder; they don't hide cards.

On the inline section, the older `sortData` is deprecated: pass `sortBy` + `sortOrder` instead.

### `visibilityOptions` (requires `isPrivateCommentsEnabled`)

`visibilityOptions` (default `false`) shows the per-comment visibility picker. It's **gated**: the banner renders only when the SDK's private-comments capability is also on. Turning it on without that shows nothing. `privateCommentMode` makes comments private by default.

### `shadowDom` vs `dialogShadowDom`

`shadowDom` is the master wrap for `VeltComments`. The per-surface flags (`dialogShadowDom`, `pinShadowDom`, `textCommentToolShadowDom`, …) default `true` and isolate each surface independently.

To style a surface with your own CSS, turn off the shadow DOM that wraps **it**: `dialogShadowDom={false}` reaches dialog internals, `pinShadowDom={false}` reaches the pin. Master `shadowDom={false}` drops isolation everywhere. A per-surface `true` keeps that surface isolated even when others are off. See [`CSS classes`](https://velt.dev/docs/ui-customization/reference/css-classes) for what becomes selectable.

### `defaultMinimalFilter` (overridden by saved state)

Sets the sidebar's initial quick-filter (`'all' | 'unread' | 'resolved' | 'open' | 'assignedToMe' | …`). **Saved session state wins**, so this is a first-run default, not a forced value. To force a filter on every open, set it programmatically with `setCommentSidebarFilters` ([`Layout config`](https://velt.dev/docs/ui-customization/reference/props#part-3-layout-config-and-custom-data)).

### `embedMode` / `pageMode` (sidebar)

* **`embedMode`** renders the sidebar inline in your layout instead of docked. The React prop is `boolean` on both V1 and V2; the `embed-mode` HTML attribute on both versions still takes a string. It makes `position` and `floatingMode` moot, since you own placement.
* **`pageMode`** switches to page-level comments: no pins on the DOM, and the composer lives at the sidebar level (`pageModePlaceholder`, `pageModeComposerVariant`). Mutually exclusive with pin-based commenting.

### `fullExpanded` vs default

`fullExpanded` (default `false`) forces threads fully expanded, with message text untruncated. It's the opposite of `collapsedComments`: don't set both. `messageTruncation` (+ `messageTruncationLines`, default `4`) is a separate axis that truncates **long single messages**, independent of how many cards show.

### `commentPlaceholder` vs `replyPlaceholder` priority

The composer's placeholder resolves in this order:

1. **Generic `placeholder`** overrides everything.
2. Otherwise the thread's comment count decides: **no comments → `commentPlaceholder`**, **has comments → `replyPlaceholder`**.
3. Otherwise a built-in default that varies with `userMentions`.

So the two never both apply at once. Edit mode has its own chain: `editPlaceholder` → `editCommentPlaceholder` / `editReplyPlaceholder`, again with generic `placeholder` on top.

## Dialog state machine

Selection state and composer state are tracked **per dialog instance**, so the sidebar list, a focused thread, and a floating pin dialog for the same annotation don't leak state into each other.

**Dialog selection:**

```
unselected ──(click pin / select)──▶ selected ──(expandOnSelection: true, default)──▶ expanded
   │                                     │
   │ shows: first comment only           │ shows: composer + all comments
   │ (or first+last if                   │ (or first+last if collapsedComments)
   │  collapsedRepliesPreview)           │
   ◀──(deselect / close / Esc)───────────┘
```

* **unselected → selected:** clicking the pin, or via `dialogOnHover` / `dialogOnTargetElementClick`.
* **selected → expanded:** automatic when `expandOnSelection` is true. Set it `false` and selection won't expand: the dialog stays a preview and the composer stays hidden.
* **any → unselected:** deselect, close, or `forceCloseAllOnEsc`.

**Composer state** (independent, per instance):

```
collapsed ──(focus / click composer)──▶ open ──(start recording)──▶ recording ──(stop)──▶ open
   ▲                                       │
   └──────────(submit / clear)─────────────┘
```

* **collapsed → open:** focusing the composer. With `composerMode: 'expanded'` it starts open and never collapses.
* **open → recording:** a recorder config is active. Recording takes precedence over open.
* **open → collapsed:** on submit or clear (text, attachments, recordings, and tagged users all reset).

## Variant → context scoping

The same dialog renders in several contexts, and **each has its own variant prop**. This is how you make the sidebar-opened dialog look different from the floating pin dialog.

| Context | Variant prop |
| - | - |
| **Floating pin dialog** (the default) | `variant` on `VeltComments` |
| **Sidebar-opened dialog** | `dialogVariant` on the sidebar or sidebar button |
| **Focused thread** | `focusedThreadDialogVariant` on the sidebar |
| **Page-mode composer** | `pageModeComposerVariant` on the sidebar or sidebar button |
| **Inline section dialog** | `dialogVariant` / `composerVariant` on the inline section |

Register a named [wireframe variant](https://velt.dev/docs/ui-customization/layout.md) and point only the relevant prop at it. Setting the base `variant` on `VeltComments` changes the **floating pin dialog only**; the other contexts keep their defaults.

> [!TIP]
>
> "The dialog changed in the sidebar but not on pins" (or the reverse) is almost always the wrong variant prop for the context.

## Positioning ownership (anchored surfaces)

`VeltComments` owns pin placement and dialog anchoring. The dialog component takes no position or coordinate prop and doesn't place itself: it asks the host to re-anchor when its size changes (composer expands, recording starts), and the host does the placement. `targetElementId` names an anchor element, not a coordinate.

**What each approach forfeits:**

| Approach | Positioning |
| - | - |
| **Wireframes** | You keep Velt's anchoring for free. Pins drop where clicked, dialogs anchor and re-position on resize. This is why wireframes are the cheapest option for anchored surfaces. |
| **Primitives** | You own the container. Placing your dialog *at the pin* is your job. |
| **Headless** | You own everything: pin position, dialog position, re-anchor on resize. |

> [!WARNING]
>
> The public API doesn't expose a pin's resolved screen coordinates, so a custom primitive or headless dialog **cannot** be anchored to a Velt pin's position.

**Which surfaces Velt positions:**

| Velt-positioned | You position |
| - | - |
| Pin comment **dialog** (anchors to its pin) | **Sidebar** (docks, or `embedMode` inline) |
| **Pins** (drop where clicked) | **Inline comments section** (sits where you mount it) |
| **Bubbles** (attach to a target element) | **Notifications panel** (where you mount it) |
| **Cursors** (track live pointers) | |
| **Minimap** (corner placement via `minimapPosition`) | |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.