Custom CSS & JavaScript
Drop bespoke CSS and JS into a test or variant when the visual editor isn't enough. Variant-scoped runs only for that variant; test-wide runs for everyone in the test (including the control).
Custom CSS & JavaScript
A power-user escape hatch for changes the visual editor can't express. Drop bespoke CSS or JavaScript onto a variant — or onto every visitor in the test — and the SDK injects it on the relevant page loads.
Custom CSS and JavaScript live on the same screen as your variants in the test wizard, but most teams don't need them — the visual editor handles 90% of variant authoring without custom code. Reach for these fields when the change is dynamic, computed, or easier to express as a stylesheet rule than a list of DOM mutations.
The SDK applies custom CSS and JS automatically as part of the same DOM-mutation step that powers visual variants. There's nothing to wire up on your side — paste the code, save the variant, and the SDK takes care of injection and cleanup.
Two scopes
Variant-level
Set on a single variant. Runs only for visitors assigned to that variant. This is where the actual difference between variants usually lives — most teams use variant-level custom code, not test-level.
Test-level
Set on the test itself. Runs for every visitor in the test, including the control. Useful when every variant should get the same supporting code — a CSS reset, an instrumentation snippet, a helper function. Rare in practice.
How injection works
- CSS — injected as a
<style>tag appended to<head>. Idempotent — the same ID is never injected twice. Normal cascade rules apply; more-specific page selectors win unless you bump specificity. - JavaScript — compiled to an executable function and scheduled to run in the next browser macrotask, so it runs immediately after the SDK applies DOM mutations rather than blocking the page becoming visible.
- Single execution per session — the SDK tracks an executed flag and skips re-running already-executed JS, even on SPA route changes. CSS is similarly idempotent at the DOM-ID level.
- Error-isolated — if your JS throws, the SDK logs
[Otter A/B] JS execution error (id): <error>and keeps running. Other tests on the same page are unaffected.
Power-user discipline
Default to the visual editor. Custom code is harder to review, harder to diff, and harder to debug. The visual editor produces typed, versioned changes; custom code is a blob. If you can express the change with the editor, do.
Treat custom JS like production code. Custom JS runs on real visitors in your real site. Review it the same way you'd review a script tag in your CMS. Owners, admins, and members can author it; viewers cannot.
Make your JS idempotent and event-safe. The SDK won't re-run your JS within a session, but a reload or refresh-visitor will. Code that attaches event listeners should still guard against double-binding, and code that inserts elements should check for existence first.
Prefer CSS to JS when both work. A CSS rule that achieves the same visual change is faster, smaller, and easier to roll back. JS is the tool when you need behavior, not appearance.
Don't depend on libraries you don't already load. The SDK doesn't bring in jQuery, lodash, or anything else. If your variant needs them, your site needs to load them at the top level.
Frequently asked questions
Quick answers to the questions teams ask most about this part of Otter A/B.