First, always
- Turn on debug logs. Set
forceDebugModeinsessionStorage(sessionStorage.setItem('forceDebugMode','true')) and reload: Velt logs verbosely. - Confirm Velt is initialized. Gate on
useVeltInitState(). If it’s nevertrue, fix setup (API key,identify,setDocuments) before debugging UI: you can’t style what isn’t rendering. - Compare against default. Temporarily remove your customization. If the default works and yours doesn’t, it’s your customization; if the default is also broken, it’s setup or data.
- Inspect the element. With
shadowDom={false}, open DevTools on the element: read its realvelt-*classes and attributes, and where it sits in the DOM.
Symptom → cause → fix
”My CSS does nothing”
- Shadow DOM is on. Variables cross it, but class and selector CSS doesn’t → either set
shadowDom={false}, or keep it on and push your CSS into the shadow root withclient.injectCustomCss({ type:'styles', value:'…' }). - Specificity. Velt injects high-specificity styles → add
!important. Inspect → target thevelt-*class →!important. - Wrong class. You guessed a class name → inspect and use the actual
velt-*class (CSS classes).
”My wireframe renders nothing”
- Feature component not mounted. A
Velt…Wireframeonly registers a template: you must also mount the live feature (VeltComments/VeltCommentsSidebar/VeltCommentDialog). - Two
<VeltWireframe>roots. Merge is first-with-content-wins → your template may be ignored. Use one. - Wrong slot name. Verify against
Wireframe components. - List/repeater slot. Custom layout around a list slot is ignored: customize the item wireframe instead (
Wireframes).
”My wireframe markup renders inline on the page, and the component still shows defaults”
- No
<VeltWireframe>registry root wrapping your slot templates. A slot element placed directly in the page renders its children inline where it sits, and the live component falls back to its default UI because the template was never registered. Wrap all slot templates in exactly one<VeltWireframe>root (in plain HTML: one<velt-wireframe>).
”My wireframe’s empty state works, but the header, search, and list vanished”
- You declared a container slot and omitted its structural children. Declaring
velt-comments-sidebar-v2-wireframewith only a custom empty-placeholder renders the empty state but drops the search, filter, and list. Container slots replace their layout: re-declare every structural child you want (panel → header(search, filter) → list → empty-placeholder). Leaf slots fall back to Velt’s default; containers do not (Wireframes).
”My wireframe applies in the wrong places, or not where I want”
- Scoping. A nested child wireframe is scoped to that parent’s render; a root-level child is global. Move it accordingly (
Wireframes). - First-with-content-wins. If the same component is registered both nested and at root, the first one with content wins: don’t register it both ways.
”A button, onClick, or hook inside my wireframe does nothing”
- Expected. Wireframe markup is cloned and React interactivity is stripped (the interactivity rule). Use the Velt slot for built-in actions, or
VeltButtonWireframe+useVeltEventCallback('veltButtonClick')for custom actions (Action Components). For real interactive components, use primitives.
”velt-data shows blank, or velt-if never matches”
- Wrong or undefined variable. Names are case-sensitive and finite: check
Template Variables. A wrong name resolves toundefined. - Wrong context. Some variables exist only in certain slots (
{comment}and{commentIndex}only inside a thread card;{notification}only in the notifications panel;{focusedAnnotation}only in the sidebar). - Nested access not supported. Only the roots in the nested-access list allow
{root.nested}; others resolve to the root only. - Flat-config feature. Try the explicit
{componentConfig.<name>}form (cursor, presence, huddle, recording, reactions, area, arrow, tag, autocomplete).
”velt-class toggles, but nothing looks different”
- You haven’t defined the class.
velt-classonly adds and removes the class name; the styling is yours to write. Define it in your own CSS, with!importantif it competes with Velt’s (Conditional Classes).
”Default styling is still there even though I wireframed it”
- Expected. A wireframe replaces a slot’s content, not the surrounding default styling (borders, padding, backgrounds, fixed widths, popover chrome). Inspect → find the
velt-*class → override with!important. Treat this as part of every wireframe pass (recipe).
”My sidebar or list won’t scroll, or won’t take the available height”
- Broken flex/height chain, including Velt’s internal elements. Every element from your wrapper down to the scroll container needs
min-height:0(plusflex:1orheight:100%), including Velt’s own internal containers (e.g.app-comment-sidebar-panel) that sit between your layout and the list. Inspect to find the hidden link and force it, often with!important. One missing link kills the scroll (full recipe). Re-test that scrolling actually works: it’s easy to get 90% right and still have a dead scroll.
”My dialog, pin, or primitive renders in the wrong place (top-left, escaping its box)”
- Absolute positioning needs a positioned ancestor. Some Velt pieces use
position: absolute. Give the parent you mount them inposition: relativeso they anchor correctly (recipe).
”Dark mode colors are wrong”
- Hard-coded colors. Move dark values under
:root[data-velt-theme="dark"]using--velt-dark-mode-*.
”A primitive layout won’t compose, or a child won’t restructure”
- Leaf component. Leaf primitives have no sub-components → restructure via that leaf’s wireframe instead (
Primitives). - You forgot the composition. Primitives don’t auto-loop: fetch the data,
.map(), and passannotationIdorcommentyourself.
”My custom dropdown doesn’t set status or priority”
- Non-Velt items. Your own menu items carry no behavior. Render Velt’s primitive
…DropdownContentItemcomponents inside your shell, or set the value yourself withuseUpdateStatus()/useUpdatePriority(). - Missing annotation context. The items need to know the annotation: pass
annotationId, or keep them inside the Velt dropdown container so they inherit it (Primitives).
”Headless mutation does nothing, or errors”
- Object shape. A hand-built
CommentorCommentAnnotationis missing required fields → fill every field the action needs; read the type in@veltdev/types. - Wrong hook usage. Most hooks return an object:
const { addComment } = useAddComment(). Read-hook returns are often wrappers (e.g.…Count→{ count }): read the field (Hooks).
”My UI shows stale data after someone else changes a comment”
- You snapshotted a reactive value.
useCommentAnnotations()and its siblings update when other users change data. Render the hook’s current value; don’t copy the array into your own state and render that.
”SSR or hydration error (Next.js)”
- Mark customization and hook components
'use client', gate onuseVeltInitState(), and don’t expect Velt UI during SSR.
When you’re still stuck
- Re-check the reference pages:
Wireframe components(slots and props),Template Variables(tokens),CSS classes(stateful classes),Layout config(props). - Inspect the running UI in DevTools (
shadowDom={false}) to see the real elements, classes, and structure Velt rendered. - Isolate: reproduce the one surface in a minimal setup with default everything, then add your customization back piece by piece.
- Confirm it’s fixable at all. If every cause above is ruled out, the design may need something the SDK’s customization surface doesn’t expose. Record what you needed and what you shipped instead, rather than hacking against Velt’s internal DOM.

