Skip to main content
Everything you need to make Velt’s collaboration UI (comments, sidebar, notifications, reactions, presence, and more) match your design: from changing a color to building a 100% custom UI.
This guide explains the customization model from first principles, then gives you a repeatable workflow for turning a design, such as a Figma file, into a working Velt implementation. It covers UI customization only. It does not cover auth, permissions, data providers, or self-hosting, except the one setup flag (shadowDom) that customization depends on.

The one idea to remember

Velt owns the behavior, data, and real-time sync. You own the presentation. Every customization approach in this guide is just a different amount of “you own the presentation”: from recolor it to rebuild it from scratch.

The four layers (in 10 seconds)

Ordered by how you should reach for them: CSS → Wireframes → Primitives → Headless:
LayerWhat you doDesign controlEffort
CSSChange colors/spacing/fonts via CSS variables (+ class overrides with !important)Theme onlyLowest
WireframesKeep Velt’s behavior/data-wiring but supply your own HTML layout for each slotHigh (layout/structure)Low
PrimitivesCompose Velt’s building-block components yourself (you do the looping, conditionals, prop-passing): full control + works with any UI libraryHighest (short of headless)Medium-High
HeadlessVelt gives you data + actions via hooks; you build 100% of the UITotalHighest
Default preference: wireframes. Use CSS for pure theming, wireframes for custom structure (the usual choice), primitives when you need full control / your own UI component library / your own interactivity (or to customize a leaf piece), and headless only as a last resort. You can mix them: even on the same surface (e.g. drop in a VeltCommentDialog primitive and wireframe parts of it; or wireframe the dialog, primitive sidebar, CSS on both). See Combining approaches.

How to use this guide

Read in this order. Don’t skip the first three: they prevent 90% of mistakes.
  1. How Velt customization works: the mental model + how the layers relate. (5 min)
  2. Decision tree: start here for any new design. Pick the right layer(s) for what you’re building. (the spine of this guide)
  3. Setup: prerequisites, the shadowDom rule, and the folder structure to put your code in.
  4. Build methodology: HOW to build: the extract → map → build → measure pipeline. Extract the exact numbers + icon SVGs deterministically (Extraction), map each element to a real slot/prop/icon (Velt Code Connect, Manifest: props-first, supply every slot), build in small patches applying the exact numbers, then measure (delta table, not eyeballing). Pair with Build gotchas (the traps + the fix).
  5. The approach you picked: open the matching approach page from the UI Customization navigation.
  6. The feature you’re customizing: comments are covered throughout the approach guides; to resolve a specific comment surface (dialog, sidebar V1/V2, sidebar button, pin, bubble, comment tool) to its primitive · root wireframe · key props · flags · variables, use the Surface lookup map in Component catalog. For any other feature (notifications, reactions, recorder, mentions, presence/cursors, activity log, …) open the matching feature guide, starting with Other features.
  7. Does the design need a component that isn’t showing? Check Feature flags: many features (reply avatars, priority, minimap, @here, …) are off by default and just need a prop.
  8. Reference: the dictionary: look up exact CSS variables, classes, wireframe slots/tokens, props, behaviors (Behaviors), data fields/events (Data models), and hooks while you build. You look things up here: you don’t read it cover to cover.
  9. Rules: the non-negotiables. Read once; re-check before you ship.
  10. Verifying a customization: after each surface, confirm it matches the design, behavior is intact, and the rules pass. The definition of “done”.
When stuck: Debugging (symptom → fix), SDK gaps and blockers (fixable problem vs. real SDK gap, and how to record a gap honestly), Patterns and tips (proven recipes), Context (attach/read your own data on comments), Cross-cutting concerns (a11y/i18n/RTL/responsive/testing), and Edge cases and limitations.

Docs map

Use these public routes when you share or link to this guide:

Implementation checklist

  1. Recognize first: map the design to Velt components. Before using the decision tree, identify which Velt component each design element represents, whether it is available, and what needs to be enabled. Use Component definitions for design intent, visual cues, positioning cues, look-alikes, and off-by-default flags. When two components match, confirm the intended component. When nothing matches, treat it as host UI or an SDK gap; do not force a Velt mapping.
  2. Run the decision tree (Decision tree) against each recognized surface. It returns the right layer, or per-feature mix, with the reason.
  3. Treat Rules as hard constraints. They cover the core invariants: one <VeltWireframe> per app, shadowDom={false} when styling with selectors, no interactive React inside a wireframe, and no brittle DOM or timing workarounds.
  4. Use only real identifiers. The Reference pages are the source of truth for CSS variables, classes, wireframe slots, primitives, variables, props, config, feature flags, hooks, and component definitions. If a name is not documented there, do not use it unless you have verified it against the live SDK and added it back to the reference.
  5. Follow each approach page in order. The CSS, wireframes, primitives, headless, and combining approaches pages are written as step-ordered guides.
  6. Match the folder structure in Setup. This keeps Velt setup, customization, assets, and host app UI separated.
  7. Verify each surface before moving on (Verifying a customization). Drive its states, confirm Velt behavior is intact, run the static rules scan, and record a PASS, PARTIAL, FAIL, or BLOCKED verdict. Work one surface at a time.
  8. When a goal seems impossible, run the gap flow (SDK gaps and blockers). Rule out fixable causes such as shadow DOM, specificity, wrong layer, off-by-default features, and custom data before recording a real SDK gap.