Skip to main content
Version: 5.0

Custom CSS

OBS Utils supports custom CSS at four scopes. Rules are injected into managed <style> tags that mount only while an overlay renderer is alive — they don't pollute Foundry's UI when the editor is closed.

Watch the Stream Overlay tutorial video for a visual guide on the older API (pre-V5; the four-scope model below replaces it).

DOM structure

The renderer produces this structure inside each overlay container (.overlay-renderer):

<div class="obs-utils overlay">
<div class="actor" id="actor{actorID}" data-actor-name="{Display Name}">
<div class="actor-layer" data-actor-name="...">
<div class="overlay{index}" data-overlay-id="{stable-id}">
<div data-component-id="{stable-id}">
<div class="component {component-type}" id="component{index}">
<!-- per-type contents -->
</div>
</div>
...
</div>
</div>
</div>
</div>

The IDs in data-overlay-id and data-component-id are stable UUIDs that survive reorder. Use those for scoped CSS — the array-index id="overlay0" etc. are convenient but shift when you reorder.

The four scopes

ScopeSelector wrapEdit whereSurvives reorder
Global.overlay-renderer { ... } (auto-applied)Composer footer → Global CSSn/a
Per actor#actor{id} { ... }Manage Actors → CSS button on the rowyes (actor ID)
Per overlay[data-overlay-id="..."] { ... }Style drawer → Advanced when an overlay is selectedyes (stable UUID)
Per component[data-component-id="..."] { ... }Style drawer → Advanced when a component is selectedyes (stable UUID)

The Global scope is auto-wrapped in .overlay-renderer so a careless rule like .app { color: red } can't bleed into Foundry's chrome or another module's DOM. The other three are scoped by construction.

Opening the Style editor

The Style editor is a slide-in drawer opened from a button at the bottom of the component section in the Properties panel (Layout workspace). When no component is selected, the button styles the overlay itself; when a component is selected, it styles that component.

Simple vs Advanced

Each customCSS field has a Simple mode (form fields) and an Advanced mode (raw CSS textarea). Both edit the same underlying data:

  • Simple parses the CSS into known fields grouped under Typography / Color / Layout & Spacing / Border / Image. The Image group only appears on image-rendering component types (img, bavimg, mimgav).
  • Anything Simple can't represent — nested rules, pseudo-elements, unknown declarations — is preserved verbatim and shown in a read-only "Preserved CSS" summary at the bottom of Simple mode.
  • Advanced shows the same CSS string raw. Edits round-trip into Simple's fields on switch.

Style drawer in Simple mode

Nested rules and pseudo-elements

For Inline overlay components, the data-component-id wrapper uses display: contents, so layout properties (padding, background, width) won't apply to it directly. To style the inner element, use a nested selector:

i { color: gold; font-size: 22px; }
img { border-radius: 50%; }
progress { width: 240px; height: 14px; }
progress::-webkit-progress-value { background: linear-gradient(90deg, #f55, #ff5); }

Inheritable properties like color and font-* still propagate through display: contents, so they work at the top level.

Canvas component wrappers are real positioning elements — top-level layout properties work directly.

Lifecycle

The <style> tags (obs-utils-css-global, obs-utils-css-actors, obs-utils-css-overlays, obs-utils-css-components) are mounted when an overlay renderer activates and removed when the last renderer unmounts. Active renderers are:

  • OverlayRenderer on /stream
  • Composer (the overlay editor's canvas, active while the Composer window is open)
  • The Preview workspace mode tab inside the Composer

You won't find these style tags in the document on /game unless one of those is open. The OBS browser source keeps them mounted as long as /stream is loaded.