Three golden principles
- Use the cheapest layer that can express the design. Reach in this order: CSS → Wireframes → Primitives → Headless. CSS is always available and layers on top of every approach.
- Wireframes are the default for structural customization. Escalate to primitives only for your own UI library or interactivity; headless is the last resort.
- You can mix layers, even on the same surface. One caveat: on anchored surfaces (pin dialog, pins, bubbles, cursors), dropping below wireframes forfeits Velt’s positioning engine; see
Behaviors.
How to use this
Split the design into features/surfaces (comment dialog, comments sidebar, comment pin, notifications panel, …) and walk the questions below once per surface; stop at the first “yes”. Different surfaces can land on different layers, and one surface can mix layers; that’s expected. Not sure which Velt component a design element is? Match it first inComponent definitions.
The questions (per feature)
Q1: Is the only difference colors, spacing, fonts, radius, or shadows? (The layout and which elements appear are basically Velt’s default.) → CSS. Theme with--velt-* variables; override classes with !important. See CSS.
Q2: Does the design change the structure/layout (custom header, reordered parts, custom thread-card, custom empty state) while the features stay the same, with no interactive components of yours inside?
→ Wireframes (+ CSS). This is the default for structural customization: prefer it. Velt fetches the data, loops the threads/comments, and wires each slot’s behavior; you just supply layout markup. See Wireframes.
Q3: Do you need full control: your own UI component library, your own interactivity/state, or to compose Velt’s building blocks yourself and place them anywhere in your tree?
→ Primitives (+ CSS, + a leaf’s wireframe where needed). You write the React (loop annotations, pass annotation-id, loop comments, pass each comment); you compose the building-block components and their sub-components into any layout; you wrap them in any UI library. More effort, most control. See Primitives.
Q4: Does nothing Velt renders fit: you must own 100% of the UI, or render on a surface Velt can’t draw into (PDF, canvas, video timeline)?
→ Headless (last resort). Data + actions via hooks; you build everything. See Headless.
Still unsure between wireframes and primitives? Default to wireframes and only switch to primitives when you hit one of the Q3 triggers (UI library, custom interactivity, leaf-deep changes, arbitrary placement).
Flowchart
After you pick: eight quick checks
Q1-Q4 pick the primary layer. These checks refine it; run through them once per surface.Does the design surface custom data?
Does the design surface custom data?
Custom statuses / priorities / categories / reactions → configure
customStatus / customPriority / customCategory / customReactions (Component config). The design surfaces your app’s own data in the comment UI → that’s context. Neither changes the layer: they’re data config layered on top.Where do your UI-library components sit?
Where do your UI-library components sit?
If the design is built from your own component library, where those components sit decides wireframe vs primitive. Static shell + classes only → wireframe is OK; must stay interactive → primitive. See the next section.
Mixing layers on one surface?
Mixing layers on one surface?
A surface can combine layers: wireframe most of it + use a leaf’s wireframe for one piece; or a primitive for the surface + a leaf wireframe for one part; + CSS on either. Note the per-piece split, and keep one
<VeltWireframe> and one stylesheet globally (Combining approaches).Do you need to control show/hide yourself?
Do you need to control show/hide yourself?
If the design requires your logic to decide when a piece appears (not Velt’s internal condition), that’s a primitive with
defaultCondition={false}: wireframes have no equivalent (Primitives). This can flip a Q2 “wireframe” answer to a primitive; resolve it here.Shadow DOM: will your class CSS reach the surface?
Shadow DOM: will your class CSS reach the surface?
- Wireframing a surface’s ROOT wireframe (e.g.
VeltCommentDialogWireframe,VeltCommentPinWireframe) → Velt auto-removes that surface’s shadow DOM → your class CSS reaches it, no flag needed. - Wireframing only a nested / leaf slot (no root wireframe for that surface) → shadow is not removed → set
shadowDom={false}(or the per-surface flag /injectCustomCss) for class CSS. - CSS variables (
--velt-*) and inlinestyle=""always work, shadow or not. SeeCSS. (Which wireframes are roots: the per-surface map inComponent catalog.)
Is the surface anchored? (positioning)
Is the surface anchored? (positioning)
If the surface is anchored (pin dialog, pins, bubbles, cursors, minimap), Velt’s host owns its on-page position: the dialog component itself only requests re-anchoring and has no position input. Wireframes keep that positioning for free; primitives/headless mean you own it, which for a pin-anchored dialog may not even be feasible via the public API. Treat this as a strong reason to stay at wireframes; escalate only after a positioning path is verified feasible. Statically-placed surfaces have no penalty. See
Behaviors.Confirm the data exists
Confirm the data exists
Before concluding a goal needs primitives/headless or is not possible, confirm in
Data models that the required fields/events exist. If they do → feasible via headless at worst. If they’re a documented absence → genuinely not achievable client-side. Name the first layer that achieves the goal (default behavior → prop/config → wireframe → primitive → headless) and go with that. If these docs don’t cover it, check against the live SDK in a running app before declaring it impossible.By the end you should know, per surface: the layer (or mix), which feature flags to enable, any custom-data config, where your UI-library components sit, shadow-DOM handling, and whether the surface is anchored. With those answered, the build is mechanical.
The UI-component-library question (don’t miss this)
If your design is built from your own component library (MUI, shadcn/ui, Ant, Radix, Chakra, Tailwind UI…), where those components live changes everything:
Rule of thumb: want to reuse your interactive design-system components inside the collaboration UI? → Primitives (or Headless). Not wireframes.
Comparison matrix
When a layer “breaks down” → escalate
Signs you’ve outgrown your current layer:- CSS → Wireframes: you’re writing
display:noneto hide parts, or you need to reorder/add/remove parts or restructure the header/composer/thread-card layout. - Wireframes → Primitives: you need your own interactive component inside the UI, your own UI component library there, to place Velt pieces arbitrarily in your tree, custom data composition/conditionals beyond
velt-if, or to customize a leaf piece more than its slot allows. (For a single leaf you can stay in wireframes and use that leaf’s wireframe; switch to primitives when the whole surface needs that level of control.) - Primitives → Headless: Velt can’t render where you need it (PDF/canvas), or you want to own 100% of the data→view pipeline.
- Went primitives just to restructure layout that wireframe slots already expose? Wireframes are less work.
- Went headless just to recolor or reflow? CSS or wireframes would do it.
Worked examples
Picked your layer or layer mix? Head to Setup, then open the matching approach page.

