/Docs

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.

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 variation name (for example “Variant 1”) 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.

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.

Example variation CSS — green button
css
1/* Change the primary CTA button */
2.btn-primary {
3 background-color: #22c55e;
4 border-color: #16a34a;
5 color: #ffffff;
6}
7
8.btn-primary:hover {
9 background-color: #16a34a;
10}
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).

Example variation JS — changed headline with cleanup
javascript
1function initVariation(options) {
2 // The function body IS the activation moment — apply changes directly
3 // (variation CSS is already applied and the DOM is ready here)
4 const heroTitle = document.querySelector('.hero h1');
5 if (heroTitle) {
6 heroTitle.dataset.originalText = heroTitle.textContent;
7 heroTitle.textContent = 'The fastest way to grow your business';
8 }
9
10 const heroSub = document.querySelector('.hero .subtitle');
11 if (heroSub) {
12 heroSub.dataset.originalText = heroSub.textContent;
13 heroSub.textContent = 'Join 10,000+ teams who trust A vs B.';
14 }
15
16 // Revert DOM changes when the variation is removed (e.g. on SPA navigation)
17 options.onRemove(() => {
18 const heroTitle = document.querySelector('.hero h1');
19 if (heroTitle && heroTitle.dataset.originalText) {
20 heroTitle.textContent = heroTitle.dataset.originalText;
21 }
22
23 const heroSub = document.querySelector('.hero .subtitle');
24 if (heroSub && heroSub.dataset.originalText) {
25 heroSub.textContent = heroSub.dataset.originalText;
26 }
27 });
28}
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.waitUntil to wait for it. This is automatically cancelled if the variation is removed before the element appears.

Waiting for a dynamically rendered element
javascript
1function initVariation(options) {
2 options.waitUntil(
3 () => document.querySelector('.product-price'),
4 (priceEl) => {
5 priceEl.dataset.originalText = priceEl.textContent;
6 priceEl.textContent = priceEl.textContent + ' (save 20%)';
7
8 options.onRemove(() => {
9 if (priceEl.dataset.originalText) {
10 priceEl.textContent = priceEl.dataset.originalText;
11 }
12 });
13 },
14 { timeout: 5000 }
15 );
16}

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 Duplicateoption in the variation's menu (three dots 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.

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

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.tsfile (in the editor’s file list) and it becomes automatically available in the trigger and every variation file, with no import needed.

shared.d.ts — shared across this experiment's files
typescript
1interface Product {
2 sku: string;
3 price: number;
4}
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.