Variation Code
Each variation in an experiment can have its own CSS and JavaScript. Understanding when and how this code runs — and how the initVariation wrapper gives you lifecycle control — helps you write variations that are reliable, fast, and easy to clean up.
CSS injection
When a visitor is assigned to a variation, the variation's CSS is injected first. A <style>tag containing all the variation's CSS rules is appended to the document's <head> element. This happens synchronously during the experiment evaluation phase, while the page is still hidden by the anti-flicker mechanism.
Because the CSS is in the <head>, it applies to the entire page immediately. Any elements that match your CSS selectors will be styled according to the variation before the visitor ever sees the page.
1/* This CSS is injected into <head> for visitors in this variation */2.hero-title {3 font-size: 3rem;4 color: #1d4ed8;5}6
7.cta-button {8 background-color: #22c55e;9 border-radius: 9999px;10 padding: 1rem 2rem;11}!important and you cannot override them, you may need to add !important to your variation CSS as well.JavaScript injection — the initVariation wrapper
After CSS, the variation's JavaScript runs. Your code must be wrapped in a function named initVariation that receives a single options argument:
1function initVariation(options) {2 // Your variation code here3}The snippet calls initVariation(options) automatically. The editor blocks saving if the wrapper function is missing or renamed.
When initVariation runs:
- The variation's CSS is already applied (CSS is injected first).
- The DOM is fully built (the page's HTML has been parsed).
- The
window.avsbglobal API is available. - The page is still hidden by the anti-flicker mechanism.
The options object
The options argument provides experiment context, lifecycle hooks, self-cleaning helpers, and tracking utilities — everything your variation needs without reaching for global state.
Context properties
options.experimentIdstringOptionalThe unique identifier for the experiment.
options.experimentNamestringOptionalThe display name of the experiment.
options.variationIdstringOptionalThe unique identifier for the variation assigned to this visitor.
options.variationNamestringOptionalThe display name of the variation (e.g. "Control", "Variant 1").
options.variationType'control' | 'variant'OptionalWhether this visitor was assigned the control or a variant.
Lifecycle hooks
options.onRemove(callback: () => void) => voidOptionalRegister a callback to run when the variation is removed — on SPA navigation, experiment teardown, or when the visitor calls avsb.disable(). Use this to undo DOM changes that the self-cleaning helpers cannot handle automatically.
Self-cleaning helpers
These helpers work exactly like their native browser equivalents, but anything you start through them is automatically torn down when the variation is removed. You never need to cancel them manually.
options.waitUntil(conditionFn, callback, opts?) => handleOptionalPolls a condition function and calls the callback with the truthy result when the condition is met. Uses a MutationObserver + 100ms polling internally. Automatically cancelled on variation removal.
options.setTimeout(fn: () => void, ms?: number) => handleOptionalLike window.setTimeout, but the timer is automatically cleared when the variation is removed.
options.setInterval(fn: () => void, ms?: number) => handleOptionalLike window.setInterval, but the interval is automatically cleared when the variation is removed.
options.addEventListener(target, type, handler, opts?) => voidOptionalLike target.addEventListener, but the listener is automatically removed when the variation is removed.
Tracking utilities
options.track.event(shortId: number, properties?: { revenue?: number }) => voidOptionalManually fire a custom metric event for this visitor. The shortId must match a metric configured in your project.
options.track.segment(segmentKey: string, segmentValue: string) => voidOptionalSend a segment value for this visitor.
options.getVisitorId() => stringOptionalReturns the current visitor's unique identifier.
options.getVariation(experimentId: string) => string | nullOptionalReturns the variation ID assigned to this visitor for a given experiment, or null if not assigned.
options.getActiveExperiments() => Array<{ experimentId: string; variationId: string }>OptionalReturns all experiments the current visitor is currently active in.
options.forceVariation(experimentId: string, variationId: string) => booleanOptionalForces the current visitor into a specific variation of another experiment. Returns true if the variation was applied successfully.
Basic example
A simple variation that changes a heading and reverts it when the variation is removed (for example, on SPA navigation). Because initVariation runs only after the experiment has already activated, DOM mutations go directly in the function body — there is no need for an additional activation wrapper:
1function initVariation(options) {2 // The function body IS the activation moment — apply changes directly3 const heading = document.querySelector('.hero h1');4 if (heading) {5 heading.dataset.originalText = heading.textContent;6 heading.textContent = 'The faster way to grow your business';7 }8
9 // Revert DOM changes when the variation is removed (e.g. on SPA navigation)10 options.onRemove(() => {11 const heading = document.querySelector('.hero h1');12 if (heading && heading.dataset.originalText) {13 heading.textContent = heading.dataset.originalText;14 }15 });16}Waiting for dynamic content
On single-page applications, some elements are rendered by the JavaScript framework after the initial DOM parse. Use options.waitUntil to delay your DOM work until the target element is ready. Because it runs through options, it is automatically cancelled if the variation is torn down while waiting.
1function initVariation(options) {2 options.waitUntil(3 // Condition: both the global and the DOM node must exist4 () => window.Intercom && document.querySelector('#intercom-container'),5 (container) => {6 // Safe to modify — the widget is ready7 container.classList.add('variant-position');8
9 // Revert on removal10 options.onRemove(() => {11 const el = document.querySelector('#intercom-container');12 if (el) el.classList.remove('variant-position');13 });14 },15 { timeout: 5000 } // Give up after 5 seconds16 );17}Accessing the DOM
Because variation JavaScript runs after the DOM is fully parsed, you can use standard DOM APIs like document.querySelector, document.getElementById, and so on directly in the initVariation function body without waiting for a DOMContentLoaded event.
On single-page applications, elements rendered by the framework after the initial page load may not be present yet. Use triggers with options.waitUntil to delay activation until the element exists, or use options.waitUntil directly inside initVariation.
Error handling
Errors thrown inside initVariation are not caught by A vs B. If your variation JS throws an exception, it will propagate normally in the browser. Always write defensive code:
1function initVariation(options) {2 // Good — check that the element exists before modifying it3 const button = document.querySelector('.cta-button');4 if (button) {5 button.textContent = 'Start free trial';6 }7
8 // Bad — throws an error if .cta-button doesn't exist9 // document.querySelector('.cta-button').textContent = 'Start free trial';10}Cleanup on SPA navigation
On single-page applications, when the visitor navigates away from a page where an experiment is running, A vs B automatically:
- Removes the
<style>tag containing the variation's CSS. - Cancels all timers, intervals, listeners, and
waitUntilobservers started via theoptionshelpers. - Runs any callbacks registered with
options.onRemove.
Note that automatic teardown does not undo DOM text or attribute changes your code made — only the resources started through the options helpers are auto-cleaned. If your variation mutated the DOM directly, register an options.onRemove callback to reverse those changes.