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.
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.
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.
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}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).
1function initVariation(options) {2 // The function body IS the activation moment — apply changes directly3 // (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}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.
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
nullaccess, 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.
1interface Product {2 sku: string;3 price: number;4}