- General workflow: applies to any approach
- CSS-specific: pure styling tricks
- Wireframe-specific: slot + token tricks
- Primitives-specific: composition tricks
- Mixed: recipes that combine layers
!important (R9b: Velt’s runtime styles are high-specificity).
General workflow (any approach)
Progressive escalation. Start at the cheapest layer; escalate only when blocked: CSS → Wireframes → Primitives → Headless. Never start at headless “to be safe”: it’s the most expensive. SeeDecision tree.
Per-feature layering. Decide approach per surface; theme everything with one CSS file. Typical healthy mix: wireframe the dialog, primitive sidebar, headless badge in your header, CSS on top. See Combining approaches.
Gate rendering on init. Velt is client-side: don’t render custom UI against an uninitialized client:
{…} / prop / hook → it’s in reference/ or it doesn’t exist. The reference map: Wireframe components (slots), Wireframe variables (tokens), CSS classes (stateful classes), Component config (props).
'use client' (Next.js): customization components and anything using hooks must be client components.
CSS-specific
Theme-token layer. Define your brand once as your own variables, then map them onto--velt-*:
.velt-thread-card--container:hover; you can also force it with .velt-thread-card--show-actions.
velt-reaction-pin--no-reactions only when count is 0. Use it (and :has(app-reaction-pin)) to swap/position the reaction UI by count.
--resolved class: detect the unresolve button):
Wireframe-specific
Reuse one wireframe template across surfaces. Define a wireframe template once (e.g. your ownDialogWireframe component) and register it inside your single <VeltWireframe>; every place that renders that component picks it up. One source of truth for a surface’s look.
Conditional UI with velt-if. Branch on live state: empty states, role-gated bits, root-vs-reply:
velt-data. Render context/metadata Velt owns:
.Skeleton slot with your own loading cards so the loading state matches your design.
Bridge a wireframe button to your app logic (the onClick escape hatch)
You can’t use onClick inside a wireframe (R4). For a custom action, use VeltButtonWireframe + the veltButtonClick event:
VeltButtonWireframe takes id + type ("button", or "single-select" with a group). For Velt’s built-in actions (resolve, delete, submit) use the dedicated slot, not this.
Reset a VeltButtonWireframe after app logic runs
UseresetVeltButtonState() when your app handles a veltButtonClick and needs to return the button to its idle state. This is most useful for custom action buttons that show a selected, loading, or submitted state while your app logic runs.
resetVeltButtonState() for the API shape.
Custom accept/reject (or any custom action) buttons in a thread card. Same mechanism:
<VeltWireframe> root to apply globally. See wireframe scoping.
Primitives-specific
Trim, don’t hide. Turn features off with props (reactions={false}, attachments={false}) rather than display:none. Cleaner DOM, no dead behavior.
Compose sub-components for custom layout. There’s a sub-component primitive for nearly every child: rebuild a header/thread-card by arranging its sub-components, instead of accepting the default layout. (Restructure a leaf via that leaf’s wireframe.)
Wrap in your UI library. Primitives are real React components, so your library’s components (and their interactivity) work normally around them: <MuiCard><VeltCommentDialog/></MuiCard>.
Mixed (more than one layer together)
Custom dropdown: your UI library + Velt primitive items + wireframe-styled items. Build the header dropdown (status / priority / options) from your UI-library shell, Velt primitive dropdown items (which carry the set behavior), and a wireframe to style the item internals, or go fully headless withuseUpdateStatus() / useUpdatePriority(). Full worked example with the exact component names: Primitives → “custom dialog header with a custom dropdown.”
Custom sidebar filters (V2): add your own filters + restyle the filter dropdown/panel. Configure which filters show (and add type:'custom' filters with your own options) via the filters prop; pick the layout with filterPanelLayout / filterOptionLayout; and fully restructure the filter dropdown/panel via the FilterContainer / FilterDropdown / FilterButton wireframe slots: Velt keeps the filtering logic. Full details: Component config → “Sidebar filters.”
Collapsed→expanded composer (wireframe layout + CSS state class). Render a collapsed input and an expanded one in the composer slot; let Velt’s state classes switch them:
velt-composer-open / velt-comment-dialog--no-comments / velt-composer-edit-mode are the switches.)
Make the sidebar/list take available height and scroll (wireframe/primitive layout + CSS over Velt’s internals: the tricky one). Velt’s own internal container elements keep their default styles and sit between your layout and the scrollable list. If any link in that flex chain lacks min-height:0 / flex:1 / height:100%, the list grows past the panel and scrolling silently breaks. Inspect the Velt internal element and force the chain (usually !important):
min-height:0 (+ flex:1 / height:100%) must hold on every element from your wrapper down to the scroll container: including Velt’s internal ones. One missing link kills the scroll. Re-test that scrolling actually works.
Remove leftover default styling a wireframe didn’t replace (wireframe + CSS !important). Wireframing a slot doesn’t strip all of Velt’s surrounding default styles: borders, padding, backgrounds, fixed widths, popover chrome often remain. Inspect → find the velt-* class → override:
position: relative container (primitive/wireframe mount + CSS). Some Velt pieces (dialog/pin/overlays, reaction pins) render with position: absolute. If a mounted primitive or wireframe appears in the wrong place (top-left, escaping its box), give its parent position: relative so the absolute child anchors to it:

