The model
A primitive is a real component:VeltCommentDialog in React, <velt-comment-dialog> as a custom element everywhere else. Because it’s real, your design-system components work normally around it, and your own state and click handlers keep working.
The trade versus wireframes is the data plumbing: you write what Velt would otherwise do. To show every thread you fetch the annotations, loop them, and pass each annotationId into a VeltCommentDialog. In exchange you get full layout control and your UI library.
The one limit: leaf pieces (the deepest components, with no children) can’t be restructured as primitives. Customize a leaf with that leaf’s wireframe, even inside an otherwise-primitive build.
Component catalog. Props: Component config. You can also explore every primitive interactively in Storybook.
Steps
Drop in the component
- React / Next.js
- Other Frameworks
shadowDom={false} if you’ll style them (see CSS).Toggle features with props
Trim the UI to your design by switching features off, never by hiding them with CSS:Every
- React / Next.js
- Other Frameworks
<VeltComments> prop is in Props; layout and mode props for the sidebar, dialog, and notifications are in Component config. Don’t guess prop names.Fetch, loop, render
For your own layout, you do the data plumbing: fetch the annotations, loop them, and render a dialog per annotation by passing its You can filter, sort, or group the annotations before rendering. To create a thread from your own composer, pair this with the action hooks (
annotationId.- React / Next.js
- Other Frameworks
useAddCommentAnnotation, useAddComment: see headless).Prefer
VeltCommentDialog for new code. It’s the actively developed per-thread primitive with the same annotationId API. VeltCommentThread still works but is no longer the recommended path (see Comment Thread).defaultCondition: you control show/hide
Every primitive has an internal visibility condition: VeltCommentDialog, for example, renders only when its annotation is selected. Setting defaultCondition={false} bypasses that gate, so the component renders whenever you mount it and you own the show/hide logic (your own {show && …}, routing, tabs).
- React / Next.js
- Other Frameworks
What it can and can’t do
The two reasons not to reach for primitives: a wireframe already expresses the layout with less work, or you only need to restructure a single leaf.
Worked example: a custom dropdown in a custom header
A common need: you build your own comment dialog from primitives, and its header needs status / priority / options dropdowns that use your UI-library dropdown for the open/close shell while still driving Velt’s real behavior. Velt ships a primitive dropdown family for each (…Trigger with name/icon/arrow parts, plus …Content with per-item children). Exact names: Component catalog. You have three ways to make it yours:
Option A: Velt’s shell, your item styling (least effort). Use the primitive dropdown as-is and restyle each item by registering its wireframe content (VeltCommentDialogStatusDropdownContentWireframe → .Item → .Icon / .Name). Velt keeps the open/close and set-status behavior; the items look fully custom.
Option B: your shell, Velt’s items. Render your library’s dropdown, and inside it render Velt’s primitive content items, which carry the click-to-set behavior. They work interactively here because primitives are real components, so the wireframe cloning limit doesn’t apply. Keep them inside the Velt dropdown container so they receive the annotation context.
- React / Next.js
- Other Frameworks
statuses list is the same one you pass to the customStatus prop (see Component config). To restyle the item internals, register the item’s wireframe (VeltCommentDialogStatusDropdownContentWireframe.Item). The same shape works for Priority and the Options actions.
Option C: fully headless (max control). Render your own dropdown entirely and set the value with hooks: useUpdateStatus(), useUpdatePriority(), and the options actions (useResolveCommentAnnotation, useDeleteComment, useAssignUser). No Velt dropdown component at all. See Headless.
Checklist
- Used real component and prop names (from
Component catalogandComponent config). - Features trimmed with props, not
display:none. -
shadowDom={false}on anything you style, and theming done with--velt-*variables (CSS). - UI-library wrappers go around the primitive, never as interactivity inside a wireframe.

