@vibeflow-tools/prototyping
v0.2.0
Published
In-app variant switching for React — page-level and component-level prototyping with URL persistence
Maintainers
Readme
@vibeflow-tools/prototyping
In-app variant switching for React — page-level and component-level prototyping with URL persistence and zero runtime dependencies.
npm install @vibeflow-tools/prototypingBuilt for Coding Agents
ui-prototyping was built to enable coding agents to propose UI variants directly in your running app — without screenshots, back-and-forth prompts, or external design tools.
An agent can define multiple named variants (layouts, colour schemes, component densities) and register them with a few lines of code. You switch between them with a click, see the result immediately in your real app, and tell the agent which one to keep. No Figma. No mockups. No rebuild cycle.
This makes ui-prototyping one of the most efficient ways for developers to give and receive UI feedback when working with AI coding assistants.
Why It Matters
Designing and reviewing UI variations is slow when you have to change code, rebuild, and refresh every time you want to see a different state. ui-prototyping brings the switch directly into your running app:
- Click to switch — numbered dot on each component, floating toolbar for all scopes at once
- URL-persisted — share a URL and your reviewer sees the exact variant you're showing them
- TypeScript-first — variant keys are narrowed to their literal types, config objects are fully typed
- Zero runtime deps — peer-deps only (
react,react-dom); nothing extra in your bundle - Vibeflow overlay integration — when the Vibeflow overlay is detected, the toolbar is accessible via the right-click context menu ("Prototyping" option)
Quick Start
import { VariantProvider, useVariant, PageVariantSwitcher } from '@vibeflow-tools/prototyping'
// 1. Define your variants
const heroVariants = {
default: {},
compact: { spacing: 'tight', titleSize: 'md' },
expanded: { spacing: 'loose', titleSize: 'xl' },
}
// 2. Read the active variant
function HeroSection() {
const v = useVariant('Hero', heroVariants)
return (
<section className={v.spacing === 'tight' ? 'py-4' : 'py-12'}>
<PageVariantSwitcher name="Hero" variants={heroVariants} />
<h1 className={v.titleSize === 'xl' ? 'text-4xl' : 'text-2xl'}>Welcome</h1>
</section>
)
}
// 3. Wrap your app
function App() {
return (
<VariantProvider>
<HeroSection />
</VariantProvider>
)
}Installation
npm install @vibeflow-tools/prototyping
# or
pnpm add @vibeflow-tools/prototyping
# or
yarn add @vibeflow-tools/prototypingRequirements: React 18+, Node.js 18+.
Core API
<VariantProvider>
Wrap your app (or a subtree) with VariantProvider to enable the variant switching system. All hooks and switcher components below it share state via this provider.
<VariantProvider>
<App />
</VariantProvider>Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| mode | "dev" \| "always" | "dev" | "dev" hides switcher UI in production (NODE_ENV === "production"). "always" always shows it — useful for A/B testing demos. |
| defaultVisible | boolean | true | Initial UI visibility. Persisted in localStorage so users can hide/show across sessions. |
| shortcuts | KeyboardShortcut[] \| false | default shortcuts | Custom keyboard shortcuts for toggling the variant UI. Pass false to disable. |
useVariant(name, variants)
Core hook. Registers the scope, resolves the active variant from URL → localStorage → default, and returns the matching config object.
const variants = {
default: {},
compact: { compact: true, density: 'low' },
detailed: { showMeta: true, showComments: true },
}
function TaskCard() {
const v = useVariant('TaskCard', variants)
// v is typed as typeof variants[keyof typeof variants]
return <div className={v.compact ? 'p-2' : 'p-4'}>…</div>
}The first key is always the default variant (applied when no URL param or storage entry is present).
<VariantSwitcher>
A subtle indicator dot positioned on the edge of the parent element. Clicking it expands a numbered-dots picker.
function TaskCard() {
const v = useVariant('TaskCard', variants)
return (
<div style={{ position: 'relative' }}>
<VariantSwitcher name="TaskCard" variants={variants} />
{v.compact ? <CompactView /> : <FullView />}
</div>
)
}The parent must have position: relative for correct placement.
Deduplicates per scope — only the first VariantSwitcher for a given scope renders, even if the component is used multiple times on the page.
Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| name | string | required | Scope name — must match the useVariant call |
| variants | Record<string, object> | required | Same variant definitions passed to useVariant |
| position | "right" \| "left" | "right" | Which side the dot appears on |
<PageVariantSwitcher>
A dark segmented control fixed to the top-left of the viewport — ideal for page-level layout or theme switching.
function DashboardPage() {
const layout = useVariant('DashboardLayout', layoutVariants)
return (
<>
<PageVariantSwitcher name="DashboardLayout" variants={layoutVariants} />
<main className={layout.sidebar ? 'with-sidebar' : ''}>…</main>
</>
)
}Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| name | string | required | Scope name — must match the useVariant call |
| variants | Record<string, object> | required | Same variant definitions passed to useVariant |
<VariantDevToolbar>
An optional floating toolbar that shows all registered variant scopes at once in a single panel. Use this when you have many scopes and want a central control panel.
function App() {
return (
<VariantProvider>
<VariantDevToolbar /> {/* ← add once near the root */}
<MainContent />
</VariantProvider>
)
}The toolbar button (vibeflow icon) appears bottom-right of the page. When the Vibeflow overlay is detected, the button is hidden — access the toolbar instead via the overlay's right-click context menu.
Keyboard shortcuts:
Alt+H— toggle all switchers on/offCtrl+Shift+V— toggle all switchers on/offEscape— close the toolbar panel
registerVariant(name, variants)
Module-level registration. Use this to centralise all variant definitions in one file instead of re-passing them to every component.
// variants.ts
import { registerVariant } from '@vibeflow-tools/prototyping'
registerVariant('TaskCard', {
default: {},
minimal: { compact: true },
detailed: { showMeta: true, showComments: true },
})
registerVariant('KanbanBoard', {
default: {},
dense: { rowHeight: 'sm' },
})// TaskCard.tsx
function TaskCard() {
// Pass an empty object — registered variants are merged automatically
const v = useVariant('TaskCard', {})
return <div className={v.compact ? 'p-2' : 'p-4'}>…</div>
}Inline variants always win over registered ones when both are provided.
Persistence
Active variants are persisted in two places:
| Layer | Storage | Scope |
|-------|---------|-------|
| URL param | ?vf-<name>=<variant> | Shareable — copy the URL to share a specific variant |
| localStorage | vf-<name> | Session — survives page reloads in the same browser |
URL takes precedence over localStorage.
Keyboard Shortcuts
| Shortcut | Action |
|----------|--------|
| Alt+H | Toggle all variant switchers on/off |
| Ctrl+Shift+V | Toggle all variant switchers on/off |
Both shortcuts call toggleUiVisible() on the VariantProvider. To open the VariantDevToolbar panel, click the vibeflow icon button in the bottom-right corner.
Customise or disable shortcuts via the shortcuts prop on VariantProvider:
// Custom shortcuts
<VariantProvider shortcuts={[{ key: 'v', alt: true }]}>
<App />
</VariantProvider>
// Disable all shortcuts
<VariantProvider shortcuts={false}>
<App />
</VariantProvider>Vibeflow Overlay Integration
When the Vibeflow overlay is injected into the page, VariantDevToolbar automatically hides its standalone vibeflow icon button and registers itself with the overlay. The toolbar is then accessible via the overlay's right-click context menu under "Prototyping" — both on the bottom-right trigger and when right-clicking any element on the page.
No configuration needed — detection is automatic.
Tip for AI coding agents: Combine
ui-prototypingwith the Vibeflow overlay so variants are instantly accessible via right-click anywhere on the page. The agent defines the variants; you switch and approve. See the ui-prototyping webpage for a live demo.
Advanced: Power User Utilities
The following URL/localStorage utilities are exported for cases where you need manual control:
import {
readVariantFromUrl,
writeVariantToUrl,
removeVariantFromUrl,
readVariantFromStorage,
writeVariantToStorage,
removeVariantFromStorage,
resolveActiveVariant,
readUiVisibleFromStorage,
writeUiVisibleToStorage,
} from '@vibeflow-tools/prototyping'TypeScript
All exports are fully typed. The useVariant hook preserves key-literal types so your IDE autocompletes variant config properties:
const variants = {
default: {},
compact: { compact: true as const, rows: 3 as const },
}
function MyComponent() {
const v = useVariant('MyComponent', variants)
// v.compact is typed as `true | undefined`
// v.rows is typed as `3 | undefined`
}Contributing
pnpm install
pnpm --filter @vibeflow-tools/prototyping run build
pnpm --filter @vibeflow-tools/prototyping run test
pnpm --filter @vibeflow-tools/prototyping run test:coverageLicense
Apache-2.0 — see NOTICE for third-party attributions.
