> ## Documentation Index
> Fetch the complete documentation index at: https://velt-codex-ui-customization-guide-refresh.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Headless

> Build fully custom Velt UI with headless hooks, data, and actions.

**What it is:** Velt gives you the **data and the actions** through React hooks. **You build 100% of the UI** with your own components. Velt renders nothing.

**Use it when:** the design needs your **own interactive components** inside the collaboration UI, a layout wireframe slots can't express, or you're rendering comments into a surface Velt can't draw into (a PDF/canvas/video timeline). (Decision tree Q4.)

**Don't use it when:** a wireframe could express the layout: headless means you own (and maintain) everything, including parity with Velt's built-in behavior. It's the most powerful and the most expensive layer.

> Headless is the most powerful and most expensive layer: reach for it only when no other layer fits.

***

## The model

Three kinds of hooks (full list grouped in [`Hooks`](/ui-customization/reference/hooks)):

* **Read (data/state):** `useCommentAnnotations`, `useCommentAnnotationById`, `useUnreadCommentAnnotationCountOnCurrentDocument`, `useCommentModeState`, … → reactive data you render.
* **Mutate (actions):** `useAddCommentAnnotation`, `useAddComment`, `useUpdateComment`, `useDeleteComment`, `useDeleteCommentAnnotation`, `useResolveCommentAnnotation`, `useUpdateStatus`, `useToggleReaction`, `useGetLink`, … → call these from your own buttons.
* **Control:** `useVeltClient`, `useSetDocuments`, `useIdentify`, `useVeltInitState`, … → init/scope/imperative control.

You wire your UI's events to the mutate hooks and render the read hooks' data. Velt still handles storage, sync, mentions, permissions: you're only replacing the **view**.

## Steps

### Step 1: Render data from a read hook

```tsx theme={null}
import { useCommentAnnotations } from "@veltdev/react";
import type { CommentAnnotation } from "@veltdev/types";

function CommentsPanel() {
  const annotations: CommentAnnotation[] = useCommentAnnotations() ?? [];
  return (
    <div className="my-panel">
      {annotations.map((a) => (
        <MyThreadCard key={a.annotationId} annotation={a} />
      ))}
    </div>
  );
}
```

`useCommentAnnotations()` is reactive: it re-renders when comments change anywhere (including other users in real time). You never poll.

### Step 2: Wire your buttons to action hooks

Your components are fully interactive (unlike wireframes). Call the action hooks from your own handlers:

```tsx theme={null}
import {
  useAddComment, useResolveCommentAnnotation, useCurrentUser,
} from "@veltdev/react";
import type { CommentAnnotation, User } from "@veltdev/types";

function MyThreadCard({ annotation }: { annotation: CommentAnnotation }) {
  const currentUser = useCurrentUser();            // the identified user
  const { addComment } = useAddComment();
  const { resolveCommentAnnotation } = useResolveCommentAnnotation();

  const reply = async (text: string, html: string) => {
    if (!currentUser) return;
    const comment = buildComment(currentUser, text, html);   // buildComment is defined in Step 3
    await addComment({ annotationId: annotation.annotationId, comment });
  };

  return (
    <article>
      {/* your fully custom, interactive UI */}
      <button onClick={() => resolveCommentAnnotation({ annotationId: annotation.annotationId })}>
        Resolve
      </button>
    </article>
  );
}
```

### Step 3: Build objects to the SDK data model

Mutations expect objects shaped like Velt's types (from `@veltdev/types`). You construct them yourself. Example `Comment` builder:

```tsx theme={null}
import type { Comment, User } from "@veltdev/types";

function buildComment(currentUser: User, text: string, html: string): Comment {
  return {
    commentId: Date.now(),
    type: "text",
    from: currentUser,
    status: "added",
    commentText: text,
    commentHtml: html,
    taggedUserContacts: [],
    attachments: [],
    reactionAnnotationIds: [],
    customList: [],
  } as unknown as Comment;   // cast: fill every field your action needs; cast bridges optional fields you omit (R13)
}
```

> Because you hand-build these, **read the type** (`Comment`, `CommentAnnotation`, `User` in `@veltdev/types`) and fill every field the action needs. Missing fields are the most common headless bug.

**Minimal `Comment` for `addComment` / `updateComment`** (from the `Comment` type): `commentId` (number: auto if omitted), `type` (`'text' | 'voice'`, default `'text'`), `from` (a full `User`: **required**), `commentText`, `commentHtml`, `status` (`'added' | 'updated'`), and the array fields you touch (`attachments`, `taggedUserContacts`, `reactionAnnotationIds`). `from` is the field most often missed.

#### What each action expects (the request object)

Action hooks take a single **request object**, not loose args. The required fields per common mutation (optional ones omitted; all also accept `options?`):

| Hook                                                       | Request fields (required)                                                       | Notes                                                                                                                                                            |
| ---------------------------------------------------------- | ------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `useAddCommentAnnotation` → `addCommentAnnotation`         | `{ annotation: CommentAnnotation }`                                             | Creates a new thread; build the full `CommentAnnotation`.                                                                                                        |
| `useAddComment` → `addComment`                             | `{ annotationId: string; comment: Comment }`                                    | Adds a reply to an existing thread. Optional: `assignedTo?: User`, `assigned?: boolean`.                                                                         |
| `useUpdateComment` → `updateComment`                       | `{ annotationId: string; comment: Comment }`                                    | Optional `merge?: boolean` to patch rather than replace.                                                                                                         |
| `useDeleteComment` → `deleteComment`                       | `{ annotationId: string; commentId: number }`                                   | `commentId` is a **number**.                                                                                                                                     |
| `useResolveCommentAnnotation` → `resolveCommentAnnotation` | `{ annotationId: string }`                                                      | Toggles resolved.                                                                                                                                                |
| `useUpdateStatus` → `updateStatus`                         | `{ annotationId: string; status: CustomStatus }`                                | `status` is a full `CustomStatus` object (`{ id, name, color, type, … }`), not a string: see [`Component config`](/ui-customization/reference/component-config). |
| `useToggleReaction` → `toggleReaction`                     | `{ annotationId: string; commentId: number; reaction: { reactionId: string } }` | Optional `reaction.customReaction?`.                                                                                                                             |

> Exact, current shapes are the `*Request` interfaces in `@veltdev/types` (`AddCommentRequest`, `UpdateStatusRequest`, …): read the type if a call errors. The table is the field-level companion to R13 ("fill every field the action needs").

***

## What it can and can't do

| ✅ Headless can                                                                                                                                  | ❌ Headless can't                                                                                                                                                                                     |
| ----------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Give you **100% custom UI**: Velt renders nothing; you build every component                                                                    | Save you from **re-implementing and maintaining** thread layout, the composer (@mentions, attachments, reactions, formatting), filtering/sorting, empty/loading states, accessibility, and dark mode |
| Expose all **data + actions** through React hooks (Velt still does storage, sync, mentions, permissions)                                        | Auto-inherit **new Velt UI features**: the upgrade burden is yours; new features won't appear in your UI automatically                                                                               |
| Render comments onto a **non-DOM surface** (PDF, canvas, a video timeline) where Velt can't draw                                                | Be the cheap choice: it's the **most expensive** layer; if a [wireframe](/ui-customization/layout) can express the layout, use it instead                                                            |
| Build a panel entirely from your **own interactive design-system components**, and drive bespoke workflows/filtering via hooks + the client API |                                                                                                                                                                                                      |

## Notes & deep-dives

### The cost (be honest about this)

Going headless means **you re-implement and maintain** everything Velt's UI gave you for free:

* Thread layout, replies, collapsing, edit-in-place, resolved state.
* The composer: @mentions UI, attachments, reactions, formatting.
* Filtering/sorting (compute client-side over `useCommentAnnotations()`).
* Empty/loading states, accessibility, dark mode.
* **Upgrade burden:** new Velt features won't appear in your UI automatically.

If a [wireframe](/ui-customization/layout) can express the layout, prefer it: you keep Velt's behavior for free and only restyle.

### When headless is the right call

* Comments rendered onto a non-DOM surface (PDF, canvas, a video timeline) where Velt can't draw.
* A panel built entirely from your own interactive design-system components.
* Behavior that no slot offers (custom inline workflows, bespoke filtering UIs): drive Velt via hooks + the client API.

## Checklist

* [ ] Using real hook names from [`Hooks`](/ui-customization/reference/hooks).
* [ ] Objects passed to mutations match `@veltdev/types` (all required fields).
* [ ] Reactive data comes from read hooks (no manual polling).
* [ ] You've confirmed a wireframe genuinely *can't* do it (headless is the costly last resort).
