Variations

Step 2 of the experiment builder is Variations. This is where you define what each group of visitors will experience: the original page, the changed version, or multiple changed versions. You write CSS and JavaScript to describe each change, and you set how much of your traffic goes to each group.

The Variations step: traffic split sliders at the top, one card per variation below.
Info

This page covers Visual / Code variations, the default, where every variation runs on the same URL and changes the page in place. For tests that redirect visitors to a completely different page, see Split URL Experiments.

Control and variants

Every experiment starts with two variations:

  • Control: The original, unmodified experience. Visitors in the Control group see your page exactly as it is. No CSS or JS is injected. The Control is always present and cannot be deleted.
  • Variant 1: The first changed version you want to test. This is where you write the CSS and/or JS that defines the difference.

You can add up to 3 additional variants (for a total of 4 variations: Control + 3 Variants) by clicking Add Variation. Each variant is independent: it can have completely different CSS and JS from the others.

Renaming variations

Click the pencil (edit) icon next to the variation name to rename it. Use descriptive names that remind you what the variant is testing: "Green button", "Short headline", "No sidebar". Clear names make your results page much easier to read.

Traffic split

Below the variation names, you set what percentage of eligible visitors goes into each variation. The splits must add up to 100%.

By default, traffic is split equally between all variations. If you have two variations (Control + Variant 1), each gets 50%. If you have three variations, each gets 33.3%.

You can customize the split. For example, if you are testing a risky change, you might do 90% Control / 10% Variant. Or if you want results faster and are confident about the change, you could do 50/50.

When you add or remove a variation, A vs B automatically redistributes the traffic evenly. You can then adjust the individual percentages manually.

Equal splits give results fastest

A 50/50 split collects data in both groups at the same rate, which gives you statistically significant results fastest. Unequal splits (like 90/10) take longer because the smaller group collects data more slowly.

Overall traffic allocation

Below the split, the Overall Traffic Allocation slider sets what share of eligible visitors enters the experiment at all. Visitors outside this share see the normal page and are not counted.

0% reaches nobody

At 0%, no visitors will ever enter the experiment. The slider shows a warning, and the pre-flight checklist blocks publishing until the allocation is raised. See Review & Publish.

Running an A/A test

The Run as A/A test toggle at the top of the step converts the experiment into a sanity check of the bucketing pipeline: two identical, empty variations. The expected result is a null lift.

Switching A/A on replaces your entire variation setup: every variation is replaced by two identical empty ones, and any code or visual changes saved on them are deleted. Before that happens, A vs B loads the real counts and asks for confirmation, stating exactly how many variations and visual changes will be discarded. On a Split URL experiment the confirmation also names the destination URLs that will be cleared, since visitors are no longer redirected until you set one again. If the counts cannot be loaded (for example, a network problem), the confirmation says plainly that it could not verify what would be lost and treats the switch as destructive; nothing is ever deleted silently. Switching A/A off only flips the setting back and does not touch your variations.

Writing variation CSS

Each variation has a CSS editor tab. Write standard CSS here. This CSS is injected into a <style> tag in the page's <head> when a visitor in this variation loads the page.

Here is an example variation CSS that turns the primary CTA button green:

CSS
/* Change the primary CTA button */.btn-primary {  background-color: #22c55e;  border-color: #16a34a;  color: #ffffff;}.btn-primary:hover {  background-color: #16a34a;}
CSS10 lines
Tip

Keep variation CSS focused on the specific elements you are testing. Changing too many things at once makes it impossible to know what caused the difference in results.

Writing variation JavaScript

Each variation also has a JavaScript editor tab. Your code must be wrapped in a function named initVariation that receives an options argument. The editor blocks saving if the wrapper is missing.

The snippet calls initVariation(options) after the variation's CSS is applied and the DOM is ready, while the page is still hidden by anti-flicker. Because the function runs only after the experiment has already activated, DOM mutations go directly in the function body. Use options.onRemove to reverse any changes when the variation is torn down (for example, on SPA navigation).

This example changes the hero headline and subtitle, and reverts both when the variation is removed:

JavaScript
/** @type {(options: AvsbVariationOptions) => void} */function initVariation(options) {  // The function body IS the activation moment: apply changes directly  // (variation CSS is already applied and the DOM is ready here)  const heroTitle = document.querySelector('.hero h1');  if (heroTitle instanceof HTMLElement) {    heroTitle.dataset.originalText = heroTitle.textContent ?? '';    heroTitle.textContent = 'The fastest way to grow your business';  }  const heroSub = document.querySelector('.hero .subtitle');  if (heroSub instanceof HTMLElement) {    heroSub.dataset.originalText = heroSub.textContent ?? '';    heroSub.textContent = 'Join 10,000+ teams who trust A vs B.';  }  // Revert DOM changes when the variation is removed (e.g. on SPA navigation)  options.onRemove(() => {    const title = document.querySelector('.hero h1');    if (title instanceof HTMLElement && title.dataset.originalText) {      title.textContent = title.dataset.originalText;    }    const sub = document.querySelector('.hero .subtitle');    if (sub instanceof HTMLElement && sub.dataset.originalText) {      sub.textContent = sub.dataset.originalText;    }  });}
JavaScript29 lines
Always check that elements exist

If you call methods on a null reference (because the element was not found), it will throw an error. Always check that elements exist before modifying them.

If the element you need may not be in the DOM yet (for example, it is rendered by a JavaScript framework), use options.utils.waitForElement to wait for it: it resolves with the element. This is automatically cancelled if the variation is removed before the element appears.

JavaScript
/** @type {(options: AvsbVariationOptions) => void} */function initVariation(options) {  // Give up after 5 seconds instead of the default 10  options.utils.waitForElement('.product-price', { timeout: 5000 }).then((el) => {    if (!(el instanceof HTMLElement)) return;    el.dataset.originalText = el.textContent ?? '';    el.textContent = el.textContent + ' (save 20%)';    options.onRemove(() => {      if (el.dataset.originalText) {        el.textContent = el.dataset.originalText;      }    });  });}
JavaScript16 lines

For the full reference on initVariation, the options object, and all self-cleaning helpers, see Variation Code.

Duplicating a variation

To create a new variation that starts with the same CSS and JS as an existing one, click the duplicate (copy) icon on the variation's card, next to the pencil icon. The duplicate gets the same code but a new name. This is useful when you want to test multiple small tweaks to the same base change.

Inside the Code Editor, the same action lives one click deeper: open the three-dot menu next to a variation's name in the file sidebar, then choose Duplicate.

Adding a trigger

By default, a variation is applied immediately when the visitor is bucketed. If you need more control (for example, waiting for a specific DOM element to appear or activating only on scroll), you can add a trigger function. Click the Trigger tab in the variation editor to write trigger code. Trigger code must be wrapped in an initTrigger(options, activate, deactivate) function. See Triggers for the full trigger API reference.

Editor linting

The code editor has a linting control in its toolbar with three levels. Each experiment carries its own setting (it defaults to On), independent of other experiments and of the project's Project JS.

  • Off: no checking and no underlines; a distraction-free editor.
  • On (default): underlines type and syntax problems as you type. Advisory: you can still save.
  • Strict: turns on stricter TypeScript checks (unused variables, possible null access, missing types) and blocks saving until the reported type errors are fixed. The errors appear in the editor's problem panel, and you can click each one to jump straight to it.

Linting checks apply to TypeScript experiments. JavaScript experiments get basic syntax validation but no type checking.

A syntax error is not a lint opinion, so the lint level does not change how it is treated. If a file cannot be parsed at all (a missing bracket, a mistyped keyword), the Done button turns into Fix errors at every lint level, including Off, and saving is blocked until the file parses. Click Fix errors to see which file it is. Your work is never thrown away while you fix it: it stays in the editor and is kept on your device.

Saving your work

The editor saves when you take a deliberate action: clicking Done (or the back arrow), or pressing Cmd/Ctrl + S to save without closing. It compiles, saves to the server, waits for confirmation, and only then closes. If Strict linting is on and there are errors, saving is blocked and the editor stays open so you can fix them first.

The status indicator reflects where your work actually stands: Unsaved changes while you're editing, Saving… during a save, and Saved only once the server has confirmed it.

Your work is saved even if you leave another way

If you leave the editor by a sidebar link, the browser's back button, a reload, or by closing the tab, A vs B saves your work in the background on the way out. Nothing is silently lost. In the rare case a save doesn't reach the server, the next time you open the editor you'll see a banner offering to restore your unsaved changes.

Shared types

A TypeScript experiment has several files: a trigger plus one per variation. Rather than redeclaring a type in each, define it once in the shared.d.ts file (in the editor's file list) and it becomes automatically available in the trigger and every variation file, with no import needed.

TypeScript
interface Product {  sku: string;  price: number;}
TypeScript4 lines
Types only, nothing ships

The shared types file holds type declarations only and produces no compiled output, so it has zero impact on what runs in your visitors' browsers. A project-wide shared types file is also available in Project JS for types you reuse across every experiment.

Was this helpful?