This page is the concepts and rules. The rest of the section:
- Setup Wireframes: step-by-step setup with React and Other Frameworks examples.
- Layout Customization: targeted vs full-tree overrides, variants, replace/remove/reorder recipes.
- Template Variables: the complete
{…}variable catalog per component. - Conditional Templates: show or hide parts with
velt-if. - Conditional Classes: toggle CSS classes with
velt-class. - Action Components: custom buttons, toggles, and select groups.
The model
Two pieces work together:<VeltWireframe>: an invisible registry (display:none). You put your wireframe templates inside it. Use one per app: more than one merges first-with-content-wins, which causes hard-to-debug conflicts.Velt…Wireframeslot components: e.g.VeltCommentDialogWireframe,VeltCommentsSidebarWireframe, with nested static slots like.Header,.Body,.ThreadCard,.Composer. You fill these with your own markup.
VeltComments, VeltCommentsSidebar, VeltCommentDialog, …). It renders using the template you registered.
So a wireframe doesn’t render anything by itself: it registers a template that the live feature component picks up.
Quick example
Register a template inside the (single)<VeltWireframe> registry, then mount the live feature component as normal. Your own elements provide structure and visuals; the Velt…Wireframe.X slots are where Velt’s behavior renders.
- React / Next.js
- Other Frameworks
The interactivity rule
Read this before you write any wireframe. It is the #1 source of wireframe bugs.
Inside a wireframe, your own React interactivity does NOT run. Behavior comes only from Velt’s Velt…Wireframe.X slot components.
When Velt renders a wireframe it copies your slot markup into its own render tree: it serializes your slot to HTML and re-instantiates only the velt-* slot elements inside it. The copy is plain DOM, which strips React listeners. So for markup you put in a slot:
What to do instead: want a working button? Use the Velt slot for it (
VeltCommentDialogWireframe.ResolveButton, .Options.Content.Delete, .Composer.ActionButton, …). Your markup goes inside that slot as its appearance:
- React / Next.js
- Other Frameworks
See it for yourself. Put a
<button onClick={…}> in a wireframe slot and open DevTools: it renders as two nodes. The hidden React original (display:none) still has its React props and a working handler. The visible cloned copy injected into the live dialog has no React props, and its handler never fires. The node users can actually click is the one without the handler.Scoping: global vs scoped wireframes
Where you place a child wireframe changes where it applies:- Nested inside its parent wireframe → scoped to that parent’s render. It travels as part of the parent’s cloned subtree, so it only customizes the child as it appears inside that parent. A
ThreadCardlayout placed insideVeltCommentDialogWireframecustomizes thread cards in the dialog, not elsewhere. - Placed directly at the
<VeltWireframe>root → global. It registers under its own key and applies to that component everywhere it renders (dialog, sidebar, inline section, …).
variant), never by parent. The root scan registers only its direct children as global keys, so a nested child isn’t registered globally: it rides inside the parent’s clone. Collisions resolve first-with-content-wins, so a root definition is not overwritten by a nested one.
Rule: nest to scope, root-level to go global; don’t register the same component both ways.
Slot granularity
The slot tree is very fine-grained (hundreds of slots across the SDK). Inside a comment dialog alone you’ll find composer →Input / Attachments / AssignUser; thread-card → Avatar / Name / Time / Message / Options / Reactions; options dropdown → Edit / Delete.
You only fill the slots you care about: a slot you never declare falls back to Velt’s default. So you can do a tiny override (just the empty state) or a near-total rebuild (40+ slots across the dialog and sidebar).
List and repeater slots are the other exception. The complete slot list per feature: Wireframe components. Worked targeted-vs-full-tree examples: Layout Customization.
Variants
By default a component has one registered wireframe. Variants let you register several templates for the same component and choose which one renders by name, so one component can look different in different contexts (floating dialog vs sidebar row vs focused thread vs page-mode composer).- Register:
variant="<name>"on theVelt…Wireframe. - Select: the matching prop on the live component (
variant,dialogVariant,focusedThreadDialogVariant,pageModeComposerVariant). Which ones a given component accepts is listed per component inProps. - Fallback: no matching variant means the base (no-variant) wireframe renders.
List and repeater slots
A few slots are list/repeater containers: the comments list, the presence avatar list, the reactions panel items, the activity-log list. These keep rendering Velt’s own loop, so your layout around them is ignored.- ❌ Wrapping a list slot in your own grid/flex layout, or adding sibling markup inside it, won’t take effect.
- ✅ Customize the repeated item instead. Velt passes the item template straight to the child component, so you restructure each row through its own item wireframe.
Tokens: live data in your markup
Wireframe markup reads Velt’s live state through{…} tokens. There are three things you can do with one:
Variable names are a fixed set (
{user}, {annotation}, {comment}, {commentIndex}, {noCommentsFound}, {darkMode}, …). A name outside the catalog resolves to undefined, so never invent one: the full list is in Template Variables.
Page mode
“Page mode” renders the comments sidebar anchored to elements on your page (one thread per form question, for example), with a per-element comment-count bubble and a page-mode composer. It’s still just wireframes: fill the sidebar, thread, composer, and bubble-count slots and Velt keeps the behavior. Page mode usually pairs with context: attaching your domain data (the question id or title) to each comment and reading it back in the dialog or composer. SeeContext.
What it can and can’t do
Where to look things up
- Slots & slot props:
Wireframe components(every wireframe + full slot trees) and the overview inComponent catalog. - Variables/tokens: the full
{…}catalog inTemplate Variables; condition syntax inConditional TemplatesandConditional Classes. - Stateful CSS classes (to style state without a slot):
CSS classes. - Props on the live component (variant selection,
shadowDom, feature toggles):Props.
Troubleshooting
Common wireframe symptoms, inDebugging:
- “My wireframe renders nothing”
- “My wireframe markup renders inline on the page”
- “My wireframe’s empty state works, but the header, search, and list vanished”
- “My wireframe applies in the wrong places”
- “A button, onClick, or hook inside my wireframe does nothing”
- “
velt-datashows blank, orvelt-ifnever matches” - “Default styling is still there even though I wireframed it”
Checklist
- Exactly one
<VeltWireframe>in the app. - The live feature component (
VeltComments/VeltCommentsSidebar/VeltCommentDialog) is mounted in addition to the wireframe. - No React
onClick/useState/hooks inside slot markup: interactivity comes fromVelt…Wireframe.Xslots only. - For list/repeater slots, customized the item, not the container layout (list and repeater slots).
- Only real slot names (
Wireframe components) and real{…}variables (Template Variables). -
shadowDom={false}if you style the result. - Decided scope per child wireframe: nested = scoped, root = global (scoping).
- Unfilled slots intentionally left to Velt defaults.

