Generate several radically different UI variations on a single route, switchable from a floating bottom bar. The user flips between variants in the browser, picks one (or steals bits from each), then throws the rest away.
在單一路由上產生數個截然不同的 UI 變體,可從浮動底欄切換。使用者在瀏覽器中來回切換變體,選一個(或從每個變體偷幾塊),然後把其他丟掉。
If the question is about logic/state rather than what something looks like, this is the wrong branch. Use LOGIC.md.
如果問題關乎邏輯 / 狀態而不是「長什麼樣子」——錯的分支。改用 LOGIC.md。
A UI prototype is much easier to judge when it's butting up against the rest of the app: real header, real sidebar, real data, real density. A throwaway route on its own is a vacuum: every variant looks fine in isolation. Default to sub-shape A whenever there's a plausible existing page to host the variants. Only reach for sub-shape B if the prototype genuinely has no nearby home.
當 UI 原型貼著應用程式其他部分時,會容易判斷得多——真正的頁首、真正的側欄、真正的資料、真正的密度。孤立的一條拋棄式路由是真空:每個變體在隔離時看起來都很好。只要有合理的既有頁面可以容納變體,就預設使用子形狀 A。只有當原型真的沒有鄰近的家可放時,才使用子形狀 B。
The route already exists. Variants are rendered on the same route, gated by a ?variant= URL search param. The existing data fetching, params, and auth all stay. Only the rendering swaps. This is the default; pick it unless there's a specific reason not to.
路由已存在。變體在同一個路由上渲染,由 ?variant= URL 搜尋參數控制。既有的資料擷取、參數與認證全部保留——只有渲染內容換掉。這是預設;除非有特定理由,否則選它。
If the prototype is for something that doesn't yet have a page but would naturally live inside one (a new section of the dashboard, a new card on the settings screen, a new step in an existing flow), it's still sub-shape A. Mount the variants inside the host page.
如果原型是為了一個還沒有頁面、但自然會住在某個頁面內的東西(儀表板的新區塊、設定畫面的新卡片、既有流程的新步驟)——那仍然是子形狀 A。把變體掛載在主機頁面內。
Only use this when the thing being prototyped genuinely has no existing page to live inside (e.g. an entirely new top-level surface, or a flow that can't be embedded anywhere sensible).
只有在被製作原型的東西真的沒有既有頁面可以棲身時才用——例如一個全新的頂層表面,或一條無法嵌進任何合理地方的流程。
Create a throwaway route following whatever routing convention the project already uses. Don't invent a new top-level structure. Name it so it's obviously a prototype (e.g. include the word prototype in the path or filename). Same ?variant= pattern.
建立一條一次性路由,遵循專案既有的路由慣例——不要發明新的頂層結構。命名要明顯看得出來是原型(例如在路徑或檔名中包含 prototype 這個詞)。使用相同的 ?variant= 模式。
Before committing to sub-shape B, sanity-check: is there really no existing page this could be embedded in? An empty route hides design problems that a populated one would expose.
在採用子形狀 B 之前先自我檢查:真的沒有既有頁面可以嵌入嗎?空路由會隱藏有內容的路由才會暴露的設計問題。
In both sub-shapes the floating bottom bar is identical.
在兩個子形狀中,浮動底欄完全相同。
Default to 3 variants. More than 5 stops being radically different and starts being noise, so cap there.
預設 3 個變體。超過 5 個就不再是「截然不同」而開始變成噪音——就此封頂。
Write down the plan in one line, in the prototype's location or a top-of-file comment:
用一行寫下計畫,放在原型所在處或檔案頂端的註解:
"Three variants of the settings page, switchable via
?variant=, on the existing/settingsroute."
「設定頁面的三種變體,在既有
/settings路由上,透過?variant=切換。」
This works whether the user is here to push back or not.
這在使用者在場想反駁或不在場時都適用。
Draft each variant. Hold each one to:
草擬每個變體。每個都要符合:
VariantA, VariantB, VariantC.VariantA、VariantB、VariantC。Variants must be structurally different: different layout, different information hierarchy, different primary affordance, not just different colours. Three slightly-tweaked card grids isn't a UI prototype, it's wallpaper. If two drafts come out too similar, redo one with explicit "do not use a card grid" guidance.
變體必須結構上不同——不同的排版、不同的資訊階層、不同的主要操作方式,而不只是不同的顏色。三個微調過的火烤網格不是 UI 原型,是壁紙。如果兩個草稿太相似,重做其中一個,並明確加上「不要用卡片網格」的指引。
Create a single switcher component on the route:
在路由上建立單一切換器元件:
// pseudo-code, adapt to the project's framework
const variant = searchParams.get('variant') ?? 'A';
return (
<>
{variant === 'A' && <VariantA {...data} />}
{variant === 'B' && <VariantB {...data} />}
{variant === 'C' && <VariantC {...data} />}
<PrototypeSwitcher variants={['A','B','C']} current={variant} />
</>
);
// pseudo-code — adapt to the project's framework
const variant = searchParams.get('variant') ?? 'A';
return (
<>
{variant === 'A' && <VariantA {...data} />}
{variant === 'B' && <VariantB {...data} />}
{variant === 'C' && <VariantC {...data} />}
<PrototypeSwitcher variants={['A','B','C']} current={variant} />
</>
);
For sub-shape A (existing page): keep all the existing data fetching above the switcher; only the rendered subtree changes per variant.
子形狀 A(既有頁面):把所有既有的資料擷取留在切換器之上;只有渲染出來的子樹會依變體改變。
For sub-shape B (new page): the throwaway route under /prototype/<name> mounts the same switcher.
子形狀 B(新頁面):/prototype/<name> 下的一次性路由掛載同一個切換器。
A small fixed-position bar at the bottom-centre of the screen with three pieces:
螢幕底部中央一個固定位置的小列,包含三部分:
B (Sidebar layout).B — Sidebar layout。Behaviour:
行為:
router.replace on Next, navigate on React Router, etc) so the variant is shareable and reload-stable.← and → arrow keys also cycle. Don't intercept arrow keys when an <input>, <textarea>, or [contenteditable] is focused.process.env.NODE_ENV !== 'production' or an equivalent check, so a stray prototype merge can't ship the bar to users.router.replace、React Router 用 navigate 等),讓變體可分享、重新整理後狀態仍在。← 和 → 方向鍵也可以循環。當 <input>、<textarea> 或 [contenteditable] 取得焦點時,不要攔截方向鍵。process.env.NODE_ENV !== 'production' 或等效檢查控制,讓不小心併入的正式原型不會把這列送給使用者。Put the switcher in a single shared component so both sub-shapes can reuse it. Locate it wherever shared UI lives in the project.
把切換器放進單一共用元件,讓兩種子形狀都能重用。放在專案中共享 UI 所在之處。
Surface the URL (and the ?variant= keys). The user will flip through whenever they get to it. The interesting feedback is usually "I want the header from B with the sidebar from C", which is the actual design they want.
公開 URL(以及 ?variant= 鍵)。使用者有空就會來回切換。有趣的回饋通常是「我想要 B 的頁首配 C 的側欄」——那才是他們真正想要的設計。
Once a variant has won, capture the answer (which variant and why), then capture the prototype the way the SKILL describes. Fold the winner into the real code and move the rest onto the throwaway branch, not into main:
一旦某個變體勝出,留存答案——哪個變體、為什麼——然後依照 SKILL 描述的方式留存原型。把勝者折疊進正式程式碼,其餘送到一次性分支,而不是 main:
The full set of variants is the primary source, so it lands on the throwaway branch, not the bin, since variant components and the switcher left in the main branch rot fast and confuse the next reader.
完整的變體集合是主要來源,所以它進入一次性分支,而不是垃圾桶——留在 main 分支的變體元件和切換器會很快腐化,並誤導下一位讀者。
<Header> is fine; a shared <Layout> defeats the point. Each variant should be free to throw out the layout.<Header> 沒問題;共享 <Layout> 就失去意義了。每個變體都應該能自由丟棄排版。