# Variations

A variation is one version of your page in an experiment. The **control** (letter A) shows your page unchanged. Every other variation describes how it differs: a list of declarative **changes**, optional **CSS**, optional **JavaScript**, or, in a split URL test, a **redirect**. Prefer changes: they are safe, easy to review, and revert cleanly when a visitor leaves the page.

Each variation has a `key` (`control`, `b`, `c` or your own, lowercase letters, digits, `-` and `_`), a `name` ("Free shipping") and a `weight_bp`, its share of the experiment's traffic in basis points.

## Changes

A change edits the elements that match a CSS selector:

```json
{"op": "set_text", "selector": "#hero h1", "value": "Free shipping on every order"}
```

| `op` | Effect | Fields |
|---|---|---|
| `set_text` | Replace the element's text. | `value` |
| `set_html` | Replace the element's inner HTML. | `value` |
| `set_attribute` | Set an attribute (`href`, `src`, `alt`, `aria-label`, ...). `value: null` removes it. | `name`, `value` |
| `add_class`, `remove_class` | Add or remove space-separated classes. | `value` |
| `set_style` | Set one CSS property inline, `!important` by default. | `name` (kebab-case, `font-size`), `value`, `important` |
| `hide` | Hide the element (`display: none`). | |
| `remove` | Take the element out of the page. | |
| `insert_before`, `insert_after`, `prepend`, `append` | Insert HTML before, after, at the start or at the end of the element. | `value` |
| `move` | Move the element before, after, to the start or to the end of another element. | `target` (selector), `position` (`before`, `after`, `prepend`, `append`) |

Every change also takes:

- `all` (default `true`): apply to every match; `false` applies to the first match only.
- `persist` (default `true`): also apply to matching elements that appear later (lazy-loaded content, client rendering), and apply again when your page's own code undoes the change. With `false` the change applies to elements present at activation plus the first match that appears within 10 seconds.
- `id`: an optional stable id (the visual editor sets one).

Changes apply in order, before the page is shown for elements in the initial HTML. Each change records what it replaced, so when a visitor of a single-page app navigates away, or a preview ends, the page returns to its original state. A change whose selector matches nothing does nothing; [debugging](qa-and-preview.md#debugging) shows it.

### Safety rules

Every change is checked when it is saved, from the app, the API, an agent or the visual editor:

- Selectors must be valid CSS, at most 500 characters.
- Attributes that hold URLs (`href`, `src`, `action`, `srcset` and others) cannot start with `javascript:`, `vbscript:` or `data:` (images in `src` and `srcset` may use `data:image/`). Event handler attributes (`onclick` and other `on*`), `srcdoc` and `style` (use `set_style`) cannot be set.
- HTML values cannot contain `<script>`, `<iframe>`, `<object>`, `<embed>`, `<frame>`, `<base>` or `<meta>` elements, event handler attributes or `javascript:` URLs (`unsafe_html`). Executable code belongs in the variation's JavaScript, which is checked and recorded in the activity log.
- In workspaces without an account, HTML is additionally filtered through an allowlist of tags and attributes.

Limits: 200 changes per variation, 20 KB per value, 100 KB for all changes of a variation.

## CSS

`css` adds a style sheet while the variation is shown and removes it on revert. `common_css` on the experiment applies to every visitor in the experiment, including the control (useful for a shared fix that must not bias the test). Up to 50 KB each.

```css
.btn-buy { background: #0f7a3d; font-size: 1.1rem; }
.trust-row { display: none; }
```

## JavaScript

`js` is the body of a function that runs when the variation applies, `common_js` runs for every visitor in the experiment including the control, and the project's **global script** runs on every page before any experiment. Up to 50 KB each.

```js
var price = document.querySelector("#price");
if (price) price.insertAdjacentHTML("afterend", "<p class='note'>Free shipping on every order</p>");
ctx.cleanup(function () { var n = document.querySelector("#price + .note"); if (n) n.remove(); });
```

The function receives `tf` (the [JavaScript API](../javascript-api.md#code-in-variations)) and `ctx` (`cleanup`, `onElement`, `when`, the experiment and variation). Register a cleanup for anything you add, so single-page apps and previews can revert it.

Code is checked when you save it: a syntax error answers `validation_failed` with the field (`variations[1].js`), the line and the column, through the app, the API and agents alike, and nothing is published. Code never runs on tryfast.eu, only on your site.

In a workspace without an account, custom JavaScript (variation, experiment-wide and global code, and JavaScript conditions and triggers) runs in previews only; save the workspace to an account to launch it.

## Redirects

In a split URL test (`"type": "split_url"`) every variation except the control has a redirect:

```json
{"type": "url", "url": "https://acme-shop.de/pricing-b", "keep_query": true, "keep_hash": true}
{"type": "params", "params": {"layout": "b"}, "keep_query": true, "keep_hash": true}
```

- `url` sends the visitor to another page: an absolute `https:` URL or a path starting with `/`. It must be on the project's domains and on the same site (registrable domain, for example `acme-shop.de`) as the pages it leaves, so the visitor id carries over and the visitor keeps their variation. Other targets are rejected when saved.
- `params` keeps the page and only changes query parameters.
- `keep_query` and `keep_hash` (default `true`) carry the original query string and fragment over.

The redirect happens before the page is shown, adds no browser history entry, counts the visitor on the original page, and never loops: the destination recognises the visitor and does not redirect again, even when it matches the experiment's page rules.

## Limits

8 variations per experiment, 200 changes per variation, 50 KB of CSS and of JavaScript each. A project's script, with all running experiments, can be at most 200 KB uncompressed (`bundle_too_large`); the launch checklist warns above 30 KB compressed, because every visitor downloads it.
