Whichly variant authoring
Whichly mounts a floating picker that lets a client flip between alternative
versions of a page section live on the real site. Developers wrap a section in
<Block name="..."> and put each version in a <Variant name="...">. Writing
those variants by hand is slow — this skill does it for you.
This skill does two things in one pass:
- Wrap an existing JSX section as a
<Block> with its current markup as the
main variant.
- Generate N alternative variants — each a distinct angle (different
layout + copy), or sourced from a shadcn registry block.
If the target is already wrapped in a <Block>, skip wrapping and just add
alternatives.
Whichly API — facts you MUST get right
Import from the library:
import { Block, Variant } from "@whichly/react";
<Block name="..."> requires a unique, concise name (shown in the picker;
keep it short — labels truncate ~96px). It wraps one or more <Variant>.
<Variant name="..."> requires a name unique within the block. The first
variant is the default. Names are auto-discovered at runtime from the name
prop — no registration step.
- The active variant renders with
display: contents; inactive ones with
display: none. Therefore: never add an extra layout wrapper around variant
content — each <Variant>'s children must be a drop-in replacement for the
original content, so flex/grid parents stay intact.
Block/Variant are already "use client" internally. Server Components can
import and use them directly — do NOT add a "use client" directive to the
host file just to wrap a section.
- State is URL-encoded as
?vp=Block:variant. Nothing for you to do beyond
naming.
Naming convention
- Original markup → variant named
main.
- Alternatives → short, descriptive kebab-case names that read well to a
client in the picker:
benefit-led, social-proof, minimal, urgency.
Keep them short (labels truncate ~160px). Never positional (v2, second)
unless the user asks.
- Block name → concise PascalCase reflecting the section:
Hero, Pricing,
FAQ.
Where the Block goes — wrap content, not chrome
Wrap only the part the client should toggle. Keep shared chrome — the
<section>, the centering container, the id anchor — outside the Block.
// ✅ correct: section + container stay outside; Block wraps swappable content
<section id="pricing" className="py-20">
<div className="mx-auto max-w-5xl px-6">
<Block name="Pricing">
<Variant name="main">{/* original content */}</Variant>
<Variant name="value-led">{/* alternative */}</Variant>
</Block>
</div>
</section>
Do NOT wrap the outer <section> itself, or you'll duplicate the chrome across
variants.
Workflow
1. Locate the target
Identify the section from the user's prose and/or the open/selected file. Read the
file. Figure out which JSX is the "swappable content" vs the shared chrome.
2. Wrap (skip if already a Block)
- If the content isn't inside a
<Block>, wrap it: move the current markup into
<Variant name="main"> inside a <Block name="...">, placed inside the chrome
per the rule above.
- Ensure
import { Block, Variant } from "@whichly/react" exists; add it if
missing. Do not add "use client".
- If it's already wrapped, detect the existing variants (especially
main) and
go straight to step 3 — append new variants, never touch existing ones.
3. Interview FIRST (always)
Before generating anything, propose the alternatives and get sign-off:
- Default to 2 alternatives (3 total incl.
main); honor an explicit count if
the user gave one ("give me 4").
- For each proposed alternative, state a one-line hypothesis and a proposed
kebab-case name. Make them genuinely distinct angles (e.g. benefit-led vs
social-proof vs urgency) — not reworded copies.
- Ask the user to confirm, edit the angles/names, or add direction.
4. Generate
Produce one <Variant name="kebab"> per agreed angle. Choose the source per angle:
From scratch (default): distinct layout + copy. Match the file's existing
component imports, Tailwind class conventions, spacing, and formatting. Reuse
the same building-block components/props the main variant uses where it makes
sense (e.g. local Eyebrow, shared Button/Badge).
From a shadcn registry — when the prompt names a registry handle (e.g.
"2 variants for the FAQ block using @efferd" or @tailark/@ncdai/@jalco):
- Delegate to the
shadcn skill to add the requested block(s) into the app
(it respects apps/web/components.json registries + aliases). Do not call a
shadcn MCP — none is configured; do not hand-write the registry's CLI either.
- On success, compose a
<Variant> that renders the added block, adapting it
to the section's real content/props.
- If the
shadcn skill reports the registry isn't configured (e.g. the
example @react-bits is not in components.json) or a named block can't be
resolved → STOP. Report exactly what's missing and ask how to proceed:
generate from scratch instead, add the registry to components.json, try a
different block name, or skip that variant. Never silently fall back.
Registry-sourced and from-scratch angles can be mixed in one run.
5. Apply & report
- Edit the file in place (the user reviews via
git diff).
- Report the final variant names, in order, so the user knows exactly what the
client will see in the picker stepper for that block.
Guardrails
- Keep generated JSX type-safe and Biome-clean (match the repo's formatting).
- Never wrap variant content in an extra layout box (relies on
display: contents).
- Don't invent components or props that don't exist in the file or its imports —
reuse what's there, or import real components.
- Don't add
"use client" to a server component just to use Block/Variant.
- Keep the shared section chrome outside the
<Block>.
Examples of good interview output
Pricing block — I'll keep your current layout as main and add 2 alternatives:
value-led — leads with outcome/ROI before the price; reorders so the
benefit headline sits above the tiers.
single-cta — collapses to one recommended plan with a prominent CTA to
reduce choice paralysis.
Want different angles, names, or a different count before I write them?
1---2name: whichly3description: Wrap a JSX section in Whichly <Block>/<Variant> and generate distinct alternative variants (from scratch or from a shadcn registry). Use when the user wants to "make variants", "add a variant", "mark this block", "wrap this in a Block", or mentions Whichly variant authoring.4---56# Whichly variant authoring78Whichly mounts a floating picker that lets a client flip between alternative9versions of a page section live on the real site. Developers wrap a section in10`<Block name="...">` and put each version in a `<Variant name="...">`. Writing11those variants by hand is slow — this skill does it for you.1213This skill does two things in one pass:141. **Wrap** an existing JSX section as a `<Block>` with its current markup as the15 `main` variant.162. **Generate** N alternative variants — each a *distinct angle* (different17 layout + copy), or sourced from a shadcn registry block.1819If the target is already wrapped in a `<Block>`, skip wrapping and just add20alternatives.2122## Whichly API — facts you MUST get right2324Import from the library:2526```tsx27import { Block, Variant } from "@whichly/react";28```2930- `<Block name="...">` requires a unique, concise `name` (shown in the picker;31 keep it short — labels truncate ~96px). It wraps one or more `<Variant>`.32- `<Variant name="...">` requires a `name` unique within the block. The first33 variant is the default. Names are auto-discovered at runtime from the `name`34 prop — no registration step.35- The active variant renders with `display: contents`; inactive ones with36 `display: none`. **Therefore: never add an extra layout wrapper around variant37 content** — each `<Variant>`'s children must be a drop-in replacement for the38 original content, so flex/grid parents stay intact.39- `Block`/`Variant` are already `"use client"` internally. Server Components can40 import and use them directly — **do NOT add a `"use client"` directive** to the41 host file just to wrap a section.42- State is URL-encoded as `?vp=Block:variant`. Nothing for you to do beyond43 naming.4445## Naming convention4647- Original markup → variant named **`main`**.48- Alternatives → **short, descriptive kebab-case** names that read well to a49 client in the picker: `benefit-led`, `social-proof`, `minimal`, `urgency`.50 Keep them short (labels truncate ~160px). Never positional (`v2`, `second`)51 unless the user asks.52- Block name → concise PascalCase reflecting the section: `Hero`, `Pricing`,53 `FAQ`.5455## Where the Block goes — wrap content, not chrome5657Wrap only the part the client should toggle. Keep shared chrome — the58`<section>`, the centering container, the `id` anchor — *outside* the Block.5960```tsx61// ✅ correct: section + container stay outside; Block wraps swappable content62<section id="pricing" className="py-20">63 <div className="mx-auto max-w-5xl px-6">64 <Block name="Pricing">65 <Variant name="main">{/* original content */}</Variant>66 <Variant name="value-led">{/* alternative */}</Variant>67 </Block>68 </div>69</section>70```7172Do NOT wrap the outer `<section>` itself, or you'll duplicate the chrome across73variants.7475## Workflow7677### 1. Locate the target78Identify the section from the user's prose and/or the open/selected file. Read the79file. Figure out which JSX is the "swappable content" vs the shared chrome.8081### 2. Wrap (skip if already a Block)82- If the content isn't inside a `<Block>`, wrap it: move the current markup into83 `<Variant name="main">` inside a `<Block name="...">`, placed inside the chrome84 per the rule above.85- Ensure `import { Block, Variant } from "@whichly/react"` exists; add it if86 missing. Do not add `"use client"`.87- If it's already wrapped, detect the existing variants (especially `main`) and88 go straight to step 3 — append new variants, never touch existing ones.8990### 3. Interview FIRST (always)91Before generating anything, propose the alternatives and get sign-off:92- Default to **2 alternatives** (3 total incl. `main`); honor an explicit count if93 the user gave one ("give me 4").94- For each proposed alternative, state a **one-line hypothesis** and a proposed95 **kebab-case name**. Make them genuinely distinct angles (e.g. benefit-led vs96 social-proof vs urgency) — not reworded copies.97- Ask the user to confirm, edit the angles/names, or add direction.9899### 4. Generate100Produce one `<Variant name="kebab">` per agreed angle. Choose the source per angle:101102- **From scratch** (default): distinct layout + copy. Match the file's existing103 component imports, Tailwind class conventions, spacing, and formatting. Reuse104 the same building-block components/props the `main` variant uses where it makes105 sense (e.g. local `Eyebrow`, shared `Button`/`Badge`).106107- **From a shadcn registry** — when the prompt names a registry handle (e.g.108 "2 variants for the FAQ block using @efferd" or `@tailark`/`@ncdai`/`@jalco`):109 1. **Delegate to the `shadcn` skill** to add the requested block(s) into the app110 (it respects `apps/web/components.json` registries + aliases). Do not call a111 shadcn MCP — none is configured; do not hand-write the registry's CLI either.112 2. On success, compose a `<Variant>` that renders the added block, adapting it113 to the section's real content/props.114 3. **If the `shadcn` skill reports the registry isn't configured (e.g. the115 example `@react-bits` is not in `components.json`) or a named block can't be116 resolved → STOP.** Report exactly what's missing and ask how to proceed:117 generate from scratch instead, add the registry to `components.json`, try a118 different block name, or skip that variant. Never silently fall back.119120Registry-sourced and from-scratch angles can be mixed in one run.121122### 5. Apply & report123- Edit the file in place (the user reviews via `git diff`).124- Report the final variant names, in order, so the user knows exactly what the125 client will see in the picker stepper for that block.126127## Guardrails128- Keep generated JSX type-safe and Biome-clean (match the repo's formatting).129- Never wrap variant content in an extra layout box (relies on `display:130 contents`).131- Don't invent components or props that don't exist in the file or its imports —132 reuse what's there, or import real components.133- Don't add `"use client"` to a server component just to use Block/Variant.134- Keep the shared section chrome outside the `<Block>`.135136## Examples of good interview output137138> Pricing block — I'll keep your current layout as `main` and add 2 alternatives:139> 1. **`value-led`** — leads with outcome/ROI before the price; reorders so the140> benefit headline sits above the tiers.141> 2. **`single-cta`** — collapses to one recommended plan with a prominent CTA to142> reduce choice paralysis.143>144> Want different angles, names, or a different count before I write them?