React Adapter
The @presage-kit/react package provides React components and hooks for building adaptive interfaces.
Installation
Sección titulada «Installation»pnpm add @presage-kit/core @presage-kit/reactAdaptiveProvider
Sección titulada «AdaptiveProvider»Provides the AdaptiveClient to the entire component tree via React context.
import { AdaptiveProvider } from '@presage-kit/react'import { createAdaptiveClient, createLocalStorageDriver } from '@presage-kit/core'
const client = createAdaptiveClient({ rules: [/* ... */], persistence: { driver: createLocalStorageDriver('my-app') },})
function App() { return ( <AdaptiveProvider client={client}> <YourApp /> </AdaptiveProvider> )}Props:
| Prop | Type | Description |
|---|---|---|
client | AdaptiveClient | The client instance from createAdaptiveClient() |
children | ReactNode | Your application tree |
<Adaptive> Component
Sección titulada «<Adaptive> Component»Declares an adaptation point and renders the matching variant.
import { Adaptive, Variant } from '@presage-kit/react'
function Dashboard() { return ( <Adaptive id="dashboard-layout" defaultVariant="standard"> <Variant id="compact"> <CompactDashboard /> </Variant> <Variant id="standard"> <StandardDashboard /> </Variant> <Variant id="advanced"> <AdvancedDashboard /> </Variant> </Adaptive> )}<Adaptive> props:
| Prop | Type | Description |
|---|---|---|
id | string | Unique adaptation point identifier |
defaultVariant | string | Variant to show when no rule matches |
children | ReactNode | Must contain <Variant> components |
<Variant> props:
| Prop | Type | Description |
|---|---|---|
id | string | Variant identifier (referenced in rule actions) |
children | ReactNode | Content to render when this variant is selected |
The <Adaptive> component internally uses useAdaptive() and automatically tracks impressions when the selected variant changes.
<AdaptiveOrder> Component
Sección titulada «<AdaptiveOrder> Component»A generic ordering component for any list of items. Reorders, hides, or preserves the original order based on adaptation rules. Use this when you need adaptive ordering for non-navigation content such as feature cards, pricing tiers, or tab panels.
import { AdaptiveOrder, type Orderable } from '@presage-kit/react'
interface FeatureCard extends Orderable { title: string description: string}
const features: FeatureCard[] = [ { id: 'analytics', title: 'Analytics', description: 'Track user behavior' }, { id: 'automation', title: 'Automation', description: 'Automate repetitive tasks' }, { id: 'integrations', title: 'Integrations', description: 'Connect your tools' },]
function FeatureGrid() { return ( <AdaptiveOrder id="feature-highlights" items={features} renderItem={(item, index) => ( <div key={item.id} className="feature-card"> <h3>{item.title}</h3> <p>{item.description}</p> </div> )} /> )}With a reorder rule, items are rearranged based on user context:
{ id: 'power-user-features', adaptationId: 'feature-highlights', priority: 10, conditions: { all: [{ field: 'maturity', operator: 'eq', value: 'power' }], }, action: { type: 'reorder', order: ['automation', 'integrations', 'analytics'] },}With a hide rule, the entire list is hidden:
{ id: 'hide-features-for-new-users', adaptationId: 'feature-highlights', priority: 20, conditions: { all: [{ field: 'maturity', operator: 'eq', value: 'new' }], }, action: { type: 'hide' },}Props:
| Prop | Type | Description |
|---|---|---|
id | string | Adaptation point identifier |
items | T[] (extends Orderable) | Array of items, each with at least an id field |
renderItem | (item: T, index: number) => ReactNode | Render function for each item |
Orderable interface:
interface Orderable { id: string [key: string]: unknown}<AdaptiveNav> Component
Sección titulada «<AdaptiveNav> Component»A thin wrapper around <AdaptiveOrder> that provides the same API, specialized for navigation lists. Internally delegates to <AdaptiveOrder>.
Renders a list of navigation items that can be reordered by adaptation rules.
import { AdaptiveNav, type NavItem } from '@presage-kit/react'
interface MenuItem extends NavItem { label: string href: string icon: string}
const menuItems: MenuItem[] = [ { id: 'home', label: 'Home', href: '/', icon: 'home' }, { id: 'analytics', label: 'Analytics', href: '/analytics', icon: 'chart' }, { id: 'settings', label: 'Settings', href: '/settings', icon: 'gear' },]
function Sidebar() { return ( <AdaptiveNav id="sidebar-nav" items={menuItems} renderItem={(item, index) => ( <a key={item.id} href={item.href}> {item.icon} {item.label} </a> )} /> )}With a reorder rule, the navigation order changes based on user context:
{ id: 'power-user-nav-order', adaptationId: 'sidebar-nav', priority: 10, conditions: { all: [{ field: 'maturity', operator: 'eq', value: 'power' }], }, action: { type: 'reorder', order: ['analytics', 'home', 'settings'] },}With a hide rule, the entire navigation is hidden:
{ id: 'hide-nav-for-new-users', adaptationId: 'sidebar-nav', priority: 20, conditions: { all: [{ field: 'maturity', operator: 'eq', value: 'new' }], }, action: { type: 'hide' },}Props:
| Prop | Type | Description |
|---|---|---|
id | string | Adaptation point identifier |
items | T[] (extends NavItem) | Array of items, each with at least an id field |
renderItem | (item: T, index: number) => ReactNode | Render function for each item |
NavItem interface:
interface NavItem { id: string [key: string]: unknown}useAdaptive() Hook
Sección titulada «useAdaptive() Hook»The primary hook for resolving adaptation points. Returns the selected variant, full resolution details, context, and a scoped track function.
import { useAdaptive } from '@presage-kit/react'
function FeatureDiscovery() { const { selectedVariant, resolution, context, track } = useAdaptive('feature-discovery', { variants: ['tooltip', 'banner', 'modal'] as const, defaultVariant: 'tooltip', })
// Track a conversion event scoped to this adaptation point function handleDismiss() { track('feature_discovery_dismissed') }
switch (selectedVariant) { case 'tooltip': return <Tooltip onDismiss={handleDismiss} /> case 'banner': return <Banner onDismiss={handleDismiss} /> case 'modal': return <Modal onDismiss={handleDismiss} /> }}Parameters:
| Param | Type | Description |
|---|---|---|
adaptationId | string | Unique identifier for the adaptation point |
config.variants | readonly string[] | Available variant IDs |
config.defaultVariant | string | Fallback variant |
config.strategy | Strategy | Optional. Defaults to { type: 'rules' } |
Return type:
| Field | Type | Description |
|---|---|---|
selectedVariant | V | The selected variant (type-narrowed to your variant union) |
resolution | ResolvedAdaptation | Full resolution details (strategy, reason, timestamp) |
context | UserContext | Current user context |
track | (event: string, properties?: Record<string, unknown>) => void | Track function scoped to this adaptation point |
TypeScript generics: The hook infers the variant type from variants:
// selectedVariant is typed as 'tooltip' | 'banner' | 'modal'const { selectedVariant } = useAdaptive('feature-discovery', { variants: ['tooltip', 'banner', 'modal'] as const, defaultVariant: 'tooltip',})Automatic impression tracking: When the selected variant changes, useAdaptive() automatically tracks an presage:impression event with the adaptation ID, variant, and strategy.
useVariant() Hook
Sección titulada «useVariant() Hook»A lighter hook that returns only the selected variant string. Use this when you don’t need the full resolution details.
import { useVariant } from '@presage-kit/react'
function CTAButton() { const variant = useVariant('cta-style', { variants: ['primary', 'secondary', 'subtle'] as const, defaultVariant: 'primary', })
return <button className={`cta cta--${variant}`}>Get Started</button>}Parameters: Same as useAdaptive().
Return type: V — The selected variant string.
useAdaptiveClient() Hook
Sección titulada «useAdaptiveClient() Hook»Returns the AdaptiveClient instance from context. Useful for imperative operations like tracking and identifying.
import { useAdaptiveClient } from '@presage-kit/react'
function TrackingButton() { const client = useAdaptiveClient()
function handleClick() { client.track('feature_used', { featureId: 'export' }) }
return <button onClick={handleClick}>Export</button>}Return type: AdaptiveClient
Throws an error if called outside of <AdaptiveProvider>.
Full Example: SaaS Dashboard
Sección titulada «Full Example: SaaS Dashboard»This example shows an adaptive sidebar and onboarding flow for a SaaS dashboard.
import { createAdaptiveClient, createLocalStorageDriver } from '@presage-kit/core'
export const client = createAdaptiveClient({ rules: [ // Sidebar: prioritize analytics for power users { id: 'power-user-sidebar', adaptationId: 'sidebar', priority: 10, conditions: { all: [{ field: 'maturity', operator: 'eq', value: 'power' }], }, action: { type: 'reorder', order: ['analytics', 'dashboards', 'settings', 'home'] }, }, // Onboarding: guided tour for new users { id: 'new-user-tour', adaptationId: 'onboarding', priority: 10, conditions: { all: [{ field: 'maturity', operator: 'eq', value: 'new' }], }, action: { type: 'show', variantId: 'guided-tour' }, }, // Onboarding: hide for experienced users { id: 'experienced-no-onboarding', adaptationId: 'onboarding', priority: 5, conditions: { any: [ { field: 'maturity', operator: 'eq', value: 'active' }, { field: 'maturity', operator: 'eq', value: 'power' }, ], }, action: { type: 'hide' }, }, ], persistence: { driver: createLocalStorageDriver('saas-demo'), },})import { AdaptiveProvider } from '@presage-kit/react'import { client } from './lib/adaptive-client'import { Sidebar } from './components/Sidebar'import { Onboarding } from './components/Onboarding'import { Dashboard } from './components/Dashboard'
export function App() { return ( <AdaptiveProvider client={client}> <div style={{ display: 'flex' }}> <Sidebar /> <main> <Onboarding /> <Dashboard /> </main> </div> </AdaptiveProvider> )}import { AdaptiveNav, type NavItem } from '@presage-kit/react'
interface SidebarItem extends NavItem { label: string href: string}
const items: SidebarItem[] = [ { id: 'home', label: 'Home', href: '/' }, { id: 'dashboards', label: 'Dashboards', href: '/dashboards' }, { id: 'analytics', label: 'Analytics', href: '/analytics' }, { id: 'settings', label: 'Settings', href: '/settings' },]
export function Sidebar() { return ( <nav> <AdaptiveNav id="sidebar" items={items} renderItem={(item) => ( <a key={item.id} href={item.href}> {item.label} </a> )} /> </nav> )}import { Adaptive, Variant } from '@presage-kit/react'
export function Onboarding() { return ( <Adaptive id="onboarding" defaultVariant="standard"> <Variant id="guided-tour"> <div> <h2>Welcome! Let us show you around.</h2> <p>Follow the steps to get set up.</p> </div> </Variant> <Variant id="standard"> <div> <h2>What is new</h2> <p>Check out the latest features.</p> </div> </Variant> </Adaptive> )}