Feature & Variant System Instructions
Paths below are relative to this app's directory.
What Is the Feature System?
CoreAI Sandbox has a built-in feature/variant system for tracking prototyped features and presenting multiple UI alternatives on the same page. It uses a neutral floating selector with small, elevated tab links and a brand-colored Prototype InteractionTag menu trigger to distinguish itself from the designs being prototyped.
A feature represents a prototyped change from main. A feature may:
- Have variants — multiple design alternatives that can be switched between (e.g., different layouts)
- Be variant-less — a single prototyped change with no alternatives (just a link to the location of the change)
Multiple features can be active simultaneously on the same page, each with their own independent variant selection.
You MUST use this system whenever a user asks to:
- Try a different layout, design, or approach for a page or section
- Create an "option B" or "version 2" of something
- Compare two or more designs side by side
- Add an A/B test or design alternative
- Explore an alternate version of a feature
Do NOT build a custom toggle, tab switcher, or parallel component tree. The feature system already handles all of this.
For a guided walk-through tailored to designer-style requests, invoke the new-variant skill.
How It Works (Summary)
- Features are defined in
app/features.json. Each feature may have variant options. The registry is currently empty; add definitions to its top-level features array.
- Active variants are encoded in the URL as repeated
?variant=feature-id--variant-id params — every combination is a shareable deep link.
- When a user navigates to a page with applicable features, the system automatically populates selections from valid URL values, then remembered client-navigation selections, then the first configured variant.
- A draggable floating switcher strip appears bottom-center by default showing each feature's variants as small, elevated router-backed tab links, when applicable variants exist and Show feature selector is enabled in the Prototype menu.
- Route components use the
useFeature(featureId) hook to conditionally render based on the active variant.
- Multiple features can be active on the same page simultaneously with independent variant selections.
Architecture — Key Files
| File | Purpose |
|---|
app/features.json | App configuration: all features, routes, and variant labels |
app/features/featureConfig.ts | Feature schema, configuration validation, and route/URL helpers |
app/features/FeatureContext.ts | Feature context and public context types |
app/features/FeatureProvider.tsx | URL state, route matching, defaults, and floating selector composition |
app/features/FeatureContext.ts (useFeature) | Public hook for reading the active variant |
app/features/FeatureFloatingUI.tsx | Floating variant selector |
app/features/FeatureMenu.tsx | Prototype feature menu used by the app shell |
URL Structure
Variants are encoded as repeated variant query params:
# Single feature with one variant active
?variant=agent-playground-layout--accordion
# Two features active simultaneously
?variant=agent-playground-layout--accordion&variant=code-tab--visible
# Variant-less features have no variant query param (they apply only on matching routes)
Each variant param is a feature-id--variant-id pair using kebab-case string identifiers.
Step-by-Step: Adding a New Variant to an Existing Feature
If a feature already exists in app/features.json and has variants, add a new variant:
1. Update app/features.json
Add a new entry to the variants array of the existing feature:
{
"id": "your-new-variant",
"label": "Your New Design Name"
}
Variant id values are kebab-case strings. Choose a descriptive, URL-friendly name.
2. Update the Route Component
In the route component that uses this feature, add a conditional branch for the new variant:
import { useFeature } from '~/features/FeatureContext';
function MyPage() {
const variant = useFeature('agent-playground-layout');
if (variant === 'accordion') return <AccordionLayout />;
if (variant === 'your-new-variant') return <YourNewDesign />; // ← new
return <DefaultLayout />; // first variant or null
}
That's it. The floating UI automatically shows the new tab link when the selector is visible on an applicable route.
Step-by-Step: Creating a New Feature
Feature WITH Variants
When the feature explores multiple design alternatives:
1. Add the feature to app/features.json
Add this object to the registry's top-level features array:
{
"id": "agent-playground-layout",
"label": "Agent Playground Layout",
"notes": "What design question this feature explores.",
"defaultRoute": "/build/agents/:agent/playground",
"defaultParams": { "agent": "agent-001" },
"routes": ["/build/agents/:agent/playground"],
"variants": [
{ "id": "side-panel", "label": "Side Panel (Current)" },
{ "id": "accordion", "label": "Accordion Layout" }
]
}
2. Add useFeature() to the Route Component
import { useFeature } from '~/features/FeatureContext';
function MyPage() {
const variant = useFeature('agent-playground-layout');
if (variant === 'accordion') return <AccordionLayout />;
return <DefaultLayout />; // 'side-panel' or null
}
Feature WITHOUT Variants
When the feature is a single prototyped change with no alternatives:
1. Add the feature to app/features.json
Add this object to the registry's top-level features array:
{
"id": "dark-mode-revamp",
"label": "Dark Mode Revamp",
"notes": "Updated dark mode colors across settings page.",
"defaultRoute": "/settings/appearance",
"routes": ["/settings/appearance"]
}
No variants array needed. The feature appears in the Prototype InteractionTag menu as a navigation link. No useFeature() call is needed in the route component.
Field reference:
| Field | Required | Description |
|---|
id | Yes | Kebab-case string identifier (used in URL params) |
label | Yes | Human-readable name shown in the floating UI |
notes | Yes | Design context — what question does this feature explore? |
defaultRoute | Yes | The primary route to navigate to from the Prototype menu; must be in routes |
defaultParams | No | Fallback values for :param segments (e.g., { "agent": "agent-001" }); required when defaultRoute has required parameters |
routes | Yes | Route patterns this feature applies to. Supports :param wildcards |
variants | No | Array of { id: string, label: string }. Omit for variant-less features |
Route pattern matching: A pattern like /build/agents/:agent/playground matches any URL of that shape. The :param segments are wildcards matched segment-by-segment.
useFeature() Return Values
const result = useFeature('feature-id');
// Returns:
// string — active variant id (e.g., 'accordion') if feature has variants
// true — if feature is variant-less and applies to current route
// null — if feature doesn't apply to current route
Multi-Feature Pages
A single page can have multiple features active simultaneously. Each component on the page calls useFeature() with its own feature ID independently.
const layout = useFeature('agent-playground-layout');
const codeTab = useFeature('code-tab-visibility');
// Both return their own variant independently
// URL: ?variant=agent-playground-layout--accordion&variant=code-tab-visibility--visible
The floating strip shows variant tab links for all applicable features with variants, separated by dividers.
Variant Scope — What Can Change?
A variant can change anything within its route component:
- Full page replacement: Return entirely different component trees per variant
- Section swap: Replace just one panel, card, or section
- Prop changes: Same component with different props
- Style changes: Different CSS classes per variant
- Data presentation: Same data shown in different formats
- Conditional features: Show/hide features like buttons, tabs, or panels
Example — Small Change (text variant)
const variant = useFeature('hero-text');
return (
<Text preset="heading2" foreground={1}>
{variant === 'version-b'
? 'Start Building with AI'
: variant === 'version-c'
? 'Your AI Development Platform'
: 'Welcome to CoreAI Sandbox'}
</Text>
);
Example — Full Layout Swap
const variant = useFeature('playground-layout');
if (variant === 'accordion') return <AccordionLayout />;
if (variant === 'dialog') return <DialogLayout />;
return <SidePanelLayout />;
Variant-Exclusive Routes
Some variants introduce routes that only exist for that variant (e.g., a "Code" tab route that only exists when go-to-code--tab is active). If a user lands on a variant-exclusive route with the wrong variant selected, the route component should redirect:
const variant = useFeature('go-to-code');
// This route only exists for the "tab" variant — redirect otherwise
if (variant !== 'tab') return <Navigate to="../playground" replace />;
Keep this guard in the route component itself, colocated with the route that knows it's conditional. Do not add redirect logic to FeatureProvider.tsx.
Anti-Patterns — Do NOT Do These
| ❌ Don't | ✅ Do Instead |
|---|
| Build a custom toggle/switch for design alternatives | Use useFeature() + app/features.json |
| Store variant state in localStorage or React state | URL query params (?variant=...) handle everything |
| Create separate routes for each design alternative | One route, conditional rendering via useFeature() |
Import feature implementation internals from app/features/featureConfig.ts or app/features/FeatureProvider.tsx in app components | Use useFeature() from ~/features/FeatureContext |
| Hard-code variant IDs as numbers | Feature and variant IDs are kebab-case strings |
| Add a new variant switching UI element into the product design | The floating tab-link UI is the switcher — it's meta-UI |
| Modify app-local feature-system internals for an app feature | Only modify app/features.json and the consuming component |
Troubleshooting
Floating strip doesn't appear on my page:
- Check that the route pattern in
app/features.json matches your page's URL exactly (segment-by-segment)
:param segments match any value, but the number of segments must match
- Ensure the applicable feature has variants and Show feature selector is enabled in the Prototype menu
- Check browser console for errors
useFeature() returns null:
- Verify you're passing the feature ID (string), not the variant ID
- Verify the feature's
routes array includes a pattern matching the current page
Variant tab links show but clicking doesn't change anything:
- Ensure your component reads
useFeature() and conditionally renders based on it
- Make sure you're checking the correct variant IDs (kebab-case strings)
URL params get cleared when navigating:
- This is expected for off-route
variant params: they are removed. When returning during client navigation, valid URL selections take priority, then remembered selections, then the first configured variant. A reload uses the URL or the first variant, not remembered client-navigation state. Unrelated query params are preserved.