Mix CSS, wireframes, primitives, and headless hooks safely across Velt UI surfaces.
The four layers are not an app-wide setting. You pick CSS, wireframes, primitives, or headless per feature and per surface, then layer CSS on top of everything for theming. Most real apps mix two or more, and different features landing on different layers is normal and correct.Use it when different features need different layers, which is the usual case. Run the decision tree on each feature; its worked examples show what a given design intent usually lands on. If one surface genuinely needs a single layer, go straight to that layer’s page: CSS, Wireframes, Primitives, or Headless.
Mixing is also the UI Customization Plugin’s default: it picks the cheapest workable layer per surface, exactly like the decision tree.
Use one <VeltWireframe> registry in the whole app. It can hold as many feature wireframes as you like. Multiple registries merge first-with-content-wins, which is order-dependent and conflict-prone.
Beyond that, the layers compose freely:
CSS composes with everything.--velt-* variables apply globally, so theming sits on top of any other layer.
Different surfaces can use different layers. A wireframed dialog and a plain primitive sidebar don’t interfere.
The same surface can use more than one layer. You can render a VeltCommentDialog primitive and wireframe a single piece of that same dialog. Leaf pieces have no sub-components, so their wireframe is the only way to restructure them, which makes this combination necessary rather than exotic.
Mixing doesn’t suspend each layer’s own rules: wireframe interactivity limits and headless cost still apply. Picking a layer more expensive than the design needs is the one real mistake here, and the escalation signs tell you when you’ve done it.
Dialog = wireframe, sidebar = primitive, badge = headless, all themed with CSS, one <VeltWireframe>.
React / Next.js
Other Frameworks
// 1) Global theme (CSS): applies to everythingimport "./velt/ui-customization/styles.css";// 2) One wireframe registry (custom dialog layout)<VeltCustomization /> {/* the single <VeltWireframe> */}// 3) Live feature components<VeltComments shadowDom={false} /> {/* renders dialogs via the wireframe */}<VeltCommentsSidebar shadowDom={false} /> {/* default sidebar = primitive */}// 4) A headless extra in your own headerfunction HeaderBadge() { // returns an object ({ count } | null): read .count, don't render the object const unread = useUnreadCommentCountOnCurrentDocument(); return <span className="badge">{unread?.count ?? 0}</span>;}
<!-- 1) Global theme (CSS): applies to everything --><link rel="stylesheet" href="/velt/ui-customization/styles.css" /><!-- 2) One wireframe registry (custom dialog layout) --><velt-wireframe style="display:none;"> <!-- your feature wireframes --></velt-wireframe><!-- 3) Live feature components --><velt-comments shadow-dom="false"></velt-comments><velt-comments-sidebar shadow-dom="false"></velt-comments-sidebar><!-- 4) A headless extra in your own header --><span id="badge" class="badge">0</span><script>const commentElement = Velt.getCommentElement();// emits an object ({ count } | null): read .countconst subscription = commentElement.getUnreadCommentCountOnCurrentDocument().subscribe((unread) => { document.getElementById('badge').textContent = unread?.count ?? 0;});subscription?.unsubscribe();</script>
In React, VeltCustomization is your own wrapper component: the single file that holds your <VeltWireframe> registry and CSS import, per the recommended folder structure.