Skip to main content
Change Velt’s look (colors, spacing, fonts, radius, shadows) without touching its structure or behavior. It’s the lowest-effort approach, and it layers on top of every other one.
Two shortcuts before you write any CSS: build a theme visually in the Theme Playground, or have the UI Customization Plugin extract the values from your Figma design and write the overrides for you.
className and style props on Velt components do nothing. Style Velt with the variables and classes below, or add classes to your own markup inside a wireframe.

Make your CSS reach Velt

Velt can render inside a shadow DOM, which blocks your stylesheets.
  • CSS variables (--velt-*) always cross the boundary. Theming with variables alone needs nothing here.
  • Class and element selectors don’t. Pick one of these:
Option A: turn shadow DOM off.
Option B: keep it on and inject your CSS into the shadow root. Use type: "styles" for a CSS string, type: "link" for a stylesheet URL.
Wireframes change this. Registering a component’s root wireframe (e.g. VeltCommentDialogWireframe) removes that component’s shadow DOM automatically, so your class CSS reaches it. A nested-only wireframe doesn’t: set shadowDom={false} yourself. Inline style="" always works either way.Not every component takes shadowDom: the per-component prop lists are in Props.

Theme with variables

Put all Velt CSS in one stylesheet and override the tokens you need:
Every token is listed in CSS variables; don’t invent names. Older surfaces read a few --legacy-velt-* tokens, which are listed there too.
Rather than hand-picking values, build your theme in the Theme Playground: adjust colors, radius, spacing, and typography against a live preview, then copy the generated variables straight into this stylesheet.

Dark mode

Velt sets data-velt-theme="dark" on the document root when dark mode is on. You supply the values:
Turn it on with the darkMode prop (also dialogDarkMode, pinDarkMode, … for components Velt injects for you), with setDarkMode() app-wide, or by wiring your own prefers-color-scheme listener to it:

Fonts

One global token sets the font across every Velt surface:
Font sizes use the --velt-font-size-* scale. Line-height and weight are per-component.

Override classes

For anything variables don’t cover, target Velt’s classes. Velt’s own styles are high-specificity, so your overrides need !important. That’s the supported way to do class-based Velt CSS, not a hack.
  1. Run with shadowDom={false} and inspect the element.
  2. Prefer its velt-* BEM class over the short legacy twin: velt-comment-dialog--selected, not selected.
  3. Write the rule with !important:
CSS classes lists every structural and stateful class (unread, resolved, selected, hover, filter-applied, …). Class names can shift between versions, so prefer a --velt-* variable where one exists and re-check overrides on upgrade.

Unstyled mode

Restyling most of the UI anyway? Strip Velt’s visual styling with setUnstyledMode() (v6.0.0-beta.10+) and bring your own CSS. Layout and positioning styles are kept so components stay functional. It covers styles in the page head and inside shadow roots, and is reversible.
To also drop Velt’s global styles (the ones outside its own components), set globalStyles: false in your config:

Recipes

Specific selector tricks for things variables don’t reach. All of these assume shadowDom={false} and use !important for the reason above.
Define your brand once as your own variables, then point the --velt-* tokens at them. One place to change your brand, and Velt stays in sync with your app.
Hide the kebab, options, and reaction icons until hover. Velt already wires this on .velt-thread-card--container:hover, and you can force it with .velt-thread-card--show-actions.
There is no hover on touch devices. Velt force-shows per-comment actions at mobile widths, so if you build your own hover-reveal add an always-visible fallback under a mobile media query.
The pin carries velt-reaction-pin--no-reactions only when the count is 0. Use it, and :has(app-reaction-pin), to position or swap the reaction UI.
There is no --resolved class. Detect the unresolve button instead.
Wireframing a slot doesn’t strip all of Velt’s surrounding defaults: borders, padding, backgrounds, fixed widths, and popover chrome often remain. Inspect, find the velt-* class, override it.
Treat “inspect, find class, override” as part of every wireframe pass, not a failure.
Render a collapsed input and an expanded one in the composer slot, and let Velt’s state classes switch between them.
The switches are velt-composer-open, velt-comment-dialog--no-comments, and velt-composer-edit-mode.
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, or height:100%, the list grows past the panel and scrolling silently breaks. Inspect the Velt internal element and force the chain.
min-height:0 (plus flex:1 or 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, so re-test that scrolling actually works.
Some Velt pieces (dialogs, pins, overlays, reaction pins) render with position: absolute. If a mounted primitive or wireframe appears top-left or escaping its box, give its parent position: relative so the absolute child anchors to it.
The most common “why is my dialog floating in the wrong spot?” fix.

What CSS can and can’t do

Writing display:none to remove parts, or wishing you could move a button? You’ve hit CSS’s ceiling: escalate to wireframes to restructure, or primitives to toggle features.

Troubleshooting

Common CSS symptoms, in Debugging:

Checklist

  • shadowDom={false} on components you style with classes.
  • All Velt CSS in one stylesheet.
  • Only token names that exist in CSS variables.
  • Dark values under :root[data-velt-theme="dark"].
  • No display:none to remove features: toggle them with a prop.