# Variants

URL: https://docs.stageboard.design/guides/variants

Make a component swappable, add alternatives as *.variant.tsx files, switch between them on the live screen and screenshot each one.

A variant is an alternative implementation of one part of your UI, kept in its own `*.variant.tsx` file. You mark a component as swappable once; then you or an agent add variants, and you switch between them on the live screen to compare. Production always renders the original.

## Make a component swappable

Wrap the implementation with `variant(id, Component)` from `stageboard/react` and export the result under the original name:

```tsx title="src/components/hero.tsx"
import { variant } from "stageboard/react"

export type HeroProps = { title: string; subtitle: string; cta: string }

function HeroImpl({ title, subtitle, cta }: HeroProps) {
  return (
    <section className="mx-auto max-w-3xl px-6 py-24 text-center">
      <h1 className="text-5xl font-semibold">{title}</h1>
      <p className="mt-4 text-lg text-zinc-600">{subtitle}</p>
      <a href="/signup">{cta}</a>
    </section>
  )
}

export const Hero = variant("home.hero", HeroImpl)
```

For a section of inline JSX, wrap it in `<Variant id>`. A selected variant's default export replaces the children and receives the `props` you pass:

```tsx title="src/pages/home.tsx"
import { Variant } from "stageboard/react"

<Variant id="home.cta" props={{ plan: "pro" }}>
  <CallToAction plan="pro" />
</Variant>
```

Use stable, dotted ids such as `<area>.<thing>`. When `process.env.NODE_ENV` is `"production"`, `variant()` returns the original component and `<Variant>` renders its children, so nothing of stageboard runs in production. See [React API](/reference/react).

## Write a variant file

Each alternative is one file next to the original, named `<component>.<slug>.variant.tsx`. It exports `variant` metadata and a default component with the same props:

```tsx title="src/components/hero.split.variant.tsx"
import type { HeroProps } from "./hero"

export const variant = {
  of: "home.hero",
  label: "Split with panel",
  description: "Left-aligned copy with a tinted product panel on the right.",
}

export default function HeroSplit({ title, subtitle, cta }: HeroProps) {
  return (
    <section className="mx-auto grid max-w-5xl grid-cols-2 items-center gap-12 px-6 py-24">
      <div>
        <h1 className="text-5xl font-semibold">{title}</h1>
        <p className="mt-4 text-lg text-zinc-600">{subtitle}</p>
        <a href="/signup">{cta}</a>
      </div>
      <div className="aspect-[4/3] rounded-2xl bg-zinc-100" />
    </section>
  )
}
```

| Field | Type | Default | What it does |
| --- | --- | --- | --- |
| `of` | `string` | required | The id passed to `variant()` or `<Variant>`. Files without it are skipped. |
| `label` | `string` | the key | Name shown in the variant menu. |
| `description` | `string` | none | One sentence on what is different. |
| `job` | `string` | none | Set by an agent to the request it was made for. |

The variant's **key** is its file name without `.variant.tsx`: `hero.split.variant.tsx` has the key `hero.split`. stageboard reads the `variant` object from the source text, so keep its values as plain string literals.

## The registry file

stageboard finds variant files with the `variantGlobs` option (default `**/*.variant.{tsx,jsx,ts,js}`) and writes a registry module that imports them all. It is `stageboard.variants.ts` next to your config, or the path in the `variantsFile` option (see [Configuration](/configuration)).

```ts title="stageboard.variants.ts"
// Generated by stageboard. Do not edit; it is rewritten whenever *.variant.tsx files change.
import { registerVariants } from "stageboard/react"
import * as v0 from "./src/components/hero.split.variant"

registerVariants([
  { file: "src/components/hero.split.variant.tsx", module: v0 },
])
```

`npx stageboard dev` rewrites it whenever a variant file is added, changed or removed. To regenerate it by hand, run:

```bash
npx stageboard variants
```

Commit the file: stageboard's own frames load it automatically, and your app imports it so the [in-app devtools](/guides/devtools) can switch variants too.

## Switch variants on the live screen

Open a screen on its own and choose Variants in the toolbar; the count shows how many variants the screen has. Every swappable part is outlined in yellow, labelled with its id and the variant it shows. Click an outline to open its menu: Original is what's in the code today, followed by every variant with its description. Pick one and the screen re-renders with it. The flow viewer has the same Variants button.

Variants that an agent is still writing are counted in the menu and appear as soon as their files exist. Ask for more variants in the menu opens a note to request more alternatives of that part. You can also ask for variants from any [annotation](/guides/annotate) by choosing Variants instead of Edit.

Your choice is kept by the dev server and in the browser, and applies to every screen that renders that id. It never changes production. To keep a variant for good, pick it and press Adopt as original in the menu (or in the in-app inspector). An agent ports the variant into the original component, keeps the `variant()` boundary and its id, and deletes the variant file; its pin shows Adopting, then Adopted. To drop a variant, delete its file.

To pin a variant in one story, set `parameters.variants`:

```tsx title="src/components/hero.stories.tsx"
export const Split: Story<typeof Hero> = { parameters: { variants: { "home.hero": "hero.split" } } }
```

## Screenshot a variant

`shot` takes `--variant <id>=<key>`, once per boundary:

```bash
npx stageboard shot marketing-hero--default --variant home.hero=hero.split --width 1440 --out /tmp/hero-split.png
```

Agents compare all variants of one boundary with the MCP tool `compare_variants`, list them with `list_variants`, and switch the one you see with `select_variant`. See [CLI](/reference/cli) and [MCP tools](/reference/mcp).
