Skip to main content
Velt’s building-block components, composed by you. Each one ships Velt’s default design and full behavior, and there’s a sub-component for nearly every child piece. You fetch the data, loop it, arrange the pieces into any layout, and wrap them in any UI library. Most control, most effort. Use it when you need your own UI component library inside the comment UI, your own interactivity, to place Velt pieces anywhere in your tree, or layouts beyond what wireframe slots offer. Don’t when a wireframe already expresses the layout: Velt does the data-fetching and looping there, so it’s less work.
Have this design in Figma? The UI Customization Plugin for Cursor and Claude Code runs this whole loop for you: it picks the approach, builds it in your app, and verifies the result against the design in a real browser (comments and notifications today).

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.
Names: Component catalog. Props: Component config. You can also explore every primitive interactively in Storybook.

Steps

1

Drop in the component

That alone gives you fully working comments with Velt’s default design. Keep shadowDom={false} if you’ll style them (see CSS).
2

Toggle features with props

Trim the UI to your design by switching features off, never by hiding them with CSS:
Every <VeltComments> prop is in Props; layout and mode props for the sidebar, dialog, and notifications are in Component config. Don’t guess prop names.
3

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 annotationId.
You can filter, sort, or group the annotations before rendering. To create a thread from your own composer, pair this with the action hooks (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).
4

Wrap in your UI library

Your <Card> keeps its own state, handlers, and styling. This is exactly what wireframes cannot do: their slot markup is cloned, so interactive components inside them go dead.

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).
Omit it and the primitive uses its own built-in condition. This is what makes the fetch-loop-render pattern above work: you’re deliberately rendering a dialog per row, so none of them should wait to be selected.
Wireframes have no equivalent. A wireframe always renders through Velt’s internal condition; velt-if only reacts to Velt’s state, it can’t override whether Velt renders the component at all. If your design needs to force show or hide on your own logic, that’s a reason to pick a primitive.

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.
The 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.
Want Velt’s behavior with your looks? Option A or B. Want to own everything? Option C. All three keep Velt’s data and sync intact.

Checklist

  • Used real component and prop names (from Component catalog and Component 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.