Skip to main content

features-variants

Definitive guide to the app-local prototype feature/variant system: configuration, `useFeature()`, URL params, and anti-patterns. USE WHEN: editing `apps/prototype/app/features.json`, using `useFeature()` in routes, creating design alternatives, A/B comparisons, layout options, alternate versions, or switching between design proposals. Trigger keywords: feature, variant, version, option, alternate, prototype, design option, A/B, comparison, switch between, try a different, another way, layout option, explore, side by side.

Source facts

Repository
microsoft/coreai-design-system
Last source activity
October 14, 2026 at 20:38
Detected SKILL.md language
English
Stars
2
Forks
1

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
features-variants
description
Definitive guide to the app-local prototype feature/variant system: configuration, `useFeature()`, URL params, and anti-patterns. USE WHEN: editing `apps/prototype/app/features.json`, using `useFeature()` in routes, creating design alternatives, A/B comparisons, layout options, alternate versions, or switching between design proposals. Trigger keywords: feature, variant, version, option, alternate, prototype, design option, A/B, comparison, switch between, try a different, another way, layout option, explore, side by side.
# Feature & Variant System Instructions Paths below are relative to `apps/prototype/`. ## 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 `prototype-app-new-variant` skill. --- ## How It Works (Summary) 1. **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. 2. Active variants are encoded in the URL as repeated `?variant=feature-id--variant-id` params — every combination is a shareable deep link. 3. 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. 4. 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. 5. Route components use the `useFeature(featureId)` hook to conditionally render based on the active variant. 6. **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: ```json { "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: ```tsx 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: ```json { "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 ```tsx 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: ```json { "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 ```tsx 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. ```tsx 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) ```tsx 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 ```tsx 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: ```tsx 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:**
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub