> ## Documentation Index
> Fetch the complete documentation index at: https://velt.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# CSS

> Theme Velt UI with CSS variables, dark mode tokens, and supported class overrides.

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.

> [!NOTE]
>
> Two shortcuts before you write any CSS: build a theme visually in the [Theme Playground](https://playground.velt.dev/themes), or have the [UI Customization Plugin](https://velt.dev/docs/get-started/ui-customization-plugin.md) extract the values from your Figma design and write the overrides for you.

> [!WARNING]
>
> `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](https://velt.dev/docs/ui-customization/layout.md).

## 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.**

> [tab] React / Next.js
>
> ```tsx theme={null}
> <VeltComments shadowDom={false} />
> <VeltCommentsSidebar shadowDom={false} />
> ```

> [tab] Other Frameworks
>
> ```html theme={null}
> <velt-comments shadow-dom="false"></velt-comments>
> <velt-comments-sidebar shadow-dom="false"></velt-comments-sidebar>
> ```

**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.

> [tab] React / Next.js
>
> ```tsx theme={null}
> const { client } = useVeltClient();
> client.injectCustomCss({ type: "styles", value: `
>   .velt-comment-dialog-composer { border-radius: 10px !important; }
> ` });
> ```

> [tab] Other Frameworks
>
> ```js theme={null}
> Velt.injectCustomCss({ type: "styles", value: `
>   .velt-comment-dialog-composer { border-radius: 10px !important; }
> ` });
> ```

> [!NOTE]
>
> **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`](https://velt.dev/docs/ui-customization/reference/props).

## Theme with variables

Put all Velt CSS in one stylesheet and override the tokens you need:

```css theme={null}
/* velt.css */
:root {
  --velt-light-mode-accent: #FF6B35;
  --velt-light-mode-accent-hover: #E55A29;
  --velt-light-mode-background-0: #FFFFFF;
  --velt-light-mode-text-0: #0A0A0A;

  --velt-border-radius-md: 10px;
  --velt-spacing-md: 14px;
  --velt-font-size-sm: 13px;
}
```

Every token is listed in [`CSS variables`](https://velt.dev/docs/ui-customization/reference/css-variables); don't invent names. Older surfaces read a few `--legacy-velt-*` tokens, which are listed there too.

> [!TIP]
>
> Rather than hand-picking values, build your theme in the [Theme Playground](https://playground.velt.dev/themes): 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:

```css theme={null}
:root[data-velt-theme="dark"] {
  --velt-dark-mode-accent: #FF8A5C;
  --velt-dark-mode-background-0: #0F0F0F;
  --velt-dark-mode-text-0: #FFFFFF;
}
```

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:

> [tab] React / Next.js
>
> ```tsx theme={null}
> // Global
> const { client } = useVeltClient();
> client.setDarkMode(true);
>
> // Per component
> <VeltComments darkMode={true} dialogDarkMode={true} pinDarkMode={true} />
> ```

> [tab] Other Frameworks
>
> ```html theme={null}
> <script>
>   Velt.setDarkMode(true);
> </script>
>
> <velt-comments dark-mode="true" dialog-dark-mode="true" pin-dark-mode="true"></velt-comments>
> ```

## Fonts

One global token sets the font across every Velt surface:

```css theme={null}
:root {
  --velt-default-font-family: "Inter", "Helvetica Neue", Arial, sans-serif;
}
```

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 theme={null}
.velt-composer--submit-button {
  background: #3d5afe !important;
  border-radius: 6px !important;
}
```

> [!TIP]
>
> [`CSS classes`](https://velt.dev/docs/ui-customization/reference/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()`](https://velt.dev/docs/api-reference/sdk/api/api-methods#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.

> [tab] React / Next.js
>
> ```tsx theme={null}
> const { client } = useVeltClient();
>
> client.setUnstyledMode(true);                                    // keep layout styles
> client.setUnstyledMode(true, { keepFunctionalStyles: false });   // strip everything
> client.setUnstyledMode(false);                                   // restore
> ```

> [tab] Other Frameworks
>
> ```js theme={null}
> Velt.setUnstyledMode(true);
> Velt.setUnstyledMode(true, { keepFunctionalStyles: false });
> Velt.setUnstyledMode(false);
> ```

To also drop Velt's global styles (the ones outside its own components), set `globalStyles: false` in your config:

> [tab] React / Next.js
>
> ```jsx theme={null}
> <VeltProvider apiKey='API_KEY' config={{ globalStyles: false }}>
>     {/* Your app content */}
> </VeltProvider>
> ```

> [tab] Other Frameworks
>
> ```js theme={null}
> Velt.initConfig('API_KEY', { globalStyles: false });
> ```

## Recipes

Specific selector tricks for things variables don't reach. All of these assume `shadowDom={false}` and use `!important` for the reason above.

> [accordion] Map your brand tokens onto Velt's
>
> 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.
>
> ```css theme={null}
> :root {
>   --brand-accent: #FF6B35;
>   --velt-light-mode-accent: var(--brand-accent);
>   --velt-light-mode-accent-hover: color-mix(in srgb, var(--brand-accent) 85%, black);
> }
> ```

> [accordion] Reveal actions only on hover
>
> 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`.
>
> ```css theme={null}
> .hw-comment-actions { opacity: 0; transition: opacity .12s ease; }
> .hw-comment:hover .hw-comment-actions,
> .hw-comment-actions:focus-within { opacity: 1; }
> ```
>
> 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.

> [accordion] Swap the reaction tool and reaction panel by count
>
> 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.
>
> ```css theme={null}
> .velt-thread-card--reactions:not(:has(app-reaction-pin)) { display: none !important; }
> ```

> [accordion] Style the unread dot
>
> ```css theme={null}
> .velt-thread-card--name--unread {
>   width:6px !important; height:6px !important; border-radius:50% !important;
>   background: var(--brand) !important; margin-left:0 !important;
> }
> ```

> [accordion] Style resolved threads
>
> There is no `--resolved` class. Detect the unresolve button instead.
>
> ```css theme={null}
> .velt-comment-dialog--sidebar-mode:has(velt-comment-dialog-unresolve-button-internal .icon)
>   .velt-thread-card--message { color: var(--muted) !important; }
> ```

> [accordion] Hide a container when its data slot is empty
>
> ```css theme={null}
> .context-row:has(app-data:empty) { display:none !important; }
> app-if:empty { display:none !important; }   /* unwrap empty conditionals */
> ```

> [accordion] Resize the default avatar
>
> ```css theme={null}
> snippyly-user-avatar {
>   --legacy-velt-user-avatar-height:20px !important;
>   --legacy-velt-user-avatar-width:20px !important;
> }
> ```

> [accordion] Indent threaded replies
>
> ```css theme={null}
> app-comment-dialog-threads > :not(:first-child) { padding-left: 26px; }
> ```

> [accordion] Neutralize an SDK popover so your menu takes over
>
> ```css theme={null}
> velt-comment-dialog-options-dropdown-content-internal,
> div:has(> velt-comment-dialog-options-dropdown-content-internal) {
>   background:transparent !important; box-shadow:none !important;
>   border:none !important; padding:0 !important; width:max-content !important;
> }
> ```

> [accordion] Remove default styling a wireframe didn't replace
>
> 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.
>
> ```css theme={null}
> .velt-status-dropdown--content {
>   border:none !important; background:transparent !important;
>   box-shadow:none !important; padding:0 !important;
> }
> ```
>
> Treat "inspect, find class, override" as part of every wireframe pass, not a failure.

> [accordion] Switch a collapsed composer to expanded
>
> Render a collapsed input and an expanded one in the composer slot, and let Velt's state classes switch between them.
>
> ```css theme={null}
> .velt-composer-open .composer-collapsed { display:none !important; }
> .velt-composer-open .composer-expanded  { display:flex !important; }
> ```
>
> The switches are `velt-composer-open`, `velt-comment-dialog--no-comments`, and `velt-composer-edit-mode`.

> [accordion] Make a sidebar or list take the available height and scroll
>
> 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.
>
> ```css theme={null}
> .my-panel { display:flex; flex-direction:column; height:100%; min-height:0; }
> /* Velt's internal panel element: the hidden link you find by inspecting */
> .my-panel > app-comment-sidebar-panel { display:flex; flex-direction:column; flex:1 1 auto; min-height:0; }
> .my-list-body { flex:1; min-height:0; overflow-y:auto; }
> /* sometimes Velt's own list viewport needs forcing too: */
> velt-comments-sidebar /* …inspect for the exact internal el… */ { height:100% !important; min-height:0 !important; overflow-y:auto !important; }
> ```
>
> `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.

> [accordion] Fix a dialog or pin rendering in the wrong place
>
> 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.
>
> ```css theme={null}
> .my-comment-dialog-host { position: relative; }
> ```
>
> The most common "why is my dialog floating in the wrong spot?" fix.

## What CSS can and can't do

| ✅ CSS can | ❌ CSS cannot |
| - | - |
| Recolor everything (light + dark) | Reorder or restructure the UI |
| Change spacing, radius, fonts, shadows | Add or remove UI parts (use a prop or wireframe) |
| Theme to match a brand | Insert your own markup between Velt's pieces |
| Adjust z-index layering | Change behavior |

Writing `display:none` to remove parts, or wishing you could move a button? You've hit CSS's ceiling: escalate to [wireframes](https://velt.dev/docs/ui-customization/layout.md) to restructure, or [primitives](https://velt.dev/docs/ui-customization/primitives.md) to toggle features.

## Troubleshooting

Common CSS symptoms, in [`Debugging`](https://velt.dev/docs/ui-customization/debugging):

* ["My CSS does nothing"](https://velt.dev/docs/ui-customization/debugging.md#my-css-does-nothing)
* ["Dark mode colors are wrong"](https://velt.dev/docs/ui-customization/debugging.md#dark-mode-colors-are-wrong)
* ["Default styling is still there even though I wireframed it"](https://velt.dev/docs/ui-customization/debugging.md#default-styling-is-still-there-even-though-i-wireframed-it)
* ["My sidebar or list won't scroll"](https://velt.dev/docs/ui-customization/debugging.md#my-sidebar-or-list-wont-scroll-or-wont-take-the-available-height)
* ["My dialog, pin, or primitive renders in the wrong place"](https://velt.dev/docs/ui-customization/debugging.md#my-dialog-pin-or-primitive-renders-in-the-wrong-place-top-left-escaping-its-box)

## Checklist

* [ ] `shadowDom={false}` on components you style with classes.
* [ ] All Velt CSS in **one** stylesheet.
* [ ] Only token names that exist in [`CSS variables`](https://velt.dev/docs/ui-customization/reference/css-variables).
* [ ] Dark values under `:root[data-velt-theme="dark"]`.
* [ ] No `display:none` to remove features: toggle them with a prop.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.