Vue Adapter
The @presage-kit/vue package provides Vue 3 composables and a plugin for building adaptive interfaces.
Installation
Section intitulée « Installation »pnpm add @presage-kit/core @presage-kit/vueAdaptivePlugin
Section intitulée « AdaptivePlugin »Install the plugin to provide the AdaptiveClient to all components via Vue’s dependency injection.
import { createApp } from 'vue'import { AdaptivePlugin } from '@presage-kit/vue'import { createAdaptiveClient, createLocalStorageDriver } from '@presage-kit/core'import App from './App.vue'
const client = createAdaptiveClient({ rules: [/* ... */], persistence: { driver: createLocalStorageDriver('my-app') },})
const app = createApp(App)app.use(AdaptivePlugin, { client })app.mount('#app')Options:
| Option | Type | Description |
|---|---|---|
client | AdaptiveClient | The client instance from createAdaptiveClient() |
useAdaptive() Composable
Section intitulée « useAdaptive() Composable »The primary composable for resolving adaptation points. Returns reactive getters for the selected variant, resolution, and context.
<script setup lang="ts">import { useAdaptive } from '@presage-kit/vue'
const { selectedVariant, resolution, context, track } = useAdaptive('onboarding', { variants: ['guided-tour', 'standard', 'minimal'] as const, defaultVariant: 'standard',})
function handleComplete() { track('onboarding_completed')}</script>
<template> <div v-if="selectedVariant === 'guided-tour'"> <h2>Welcome! Let us show you around.</h2> <button @click="handleComplete">Complete tour</button> </div> <div v-else-if="selectedVariant === 'standard'"> <h2>Welcome</h2> </div> <div v-else> <p>Good to see you again.</p> </div></template>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:
The return object uses reactive getters, so values update automatically when the user context changes.
| Field | Type | Description |
|---|---|---|
selectedVariant | V (getter) | The selected variant string |
resolution | ResolvedAdaptation (getter) | Full resolution details |
context | UserContext (getter) | Current user context |
track | (event: string, properties?: Record<string, unknown>) => void | Scoped track function |
Automatic impression tracking: Like the React hook, useAdaptive() watches for variant changes and automatically tracks presage:impression events.
useVariant() Composable
Section intitulée « useVariant() Composable »A lighter composable that returns only the selected variant string.
<script setup lang="ts">import { useVariant } from '@presage-kit/vue'
const variant = useVariant('cta-style', { variants: ['primary', 'secondary', 'subtle'] as const, defaultVariant: 'primary',})</script>
<template> <button :class="`cta cta--${variant}`">Get Started</button></template>Parameters: Same as useAdaptive().
Return type: V — The selected variant string (reactive getter).
useAdaptiveClient() Composable
Section intitulée « useAdaptiveClient() Composable »Returns the AdaptiveClient instance from the plugin injection. Use for imperative operations.
<script setup lang="ts">import { useAdaptiveClient } from '@presage-kit/vue'
const client = useAdaptiveClient()
function handleExport() { client.track('feature_used', { featureId: 'export' })}</script>
<template> <button @click="handleExport">Export Data</button></template>Throws an error if the AdaptivePlugin has not been installed.
<Adaptive> and <Variant> Components
Section intitulée « <Adaptive> and <Variant> Components »Declarative components for rendering variant-based adaptations. Works the same as the React adapter.
<script setup lang="ts">import { Adaptive, Variant } from '@presage-kit/vue'</script>
<template> <Adaptive id="dashboard-layout" default-variant="standard"> <Variant id="compact"> <CompactDashboard /> </Variant> <Variant id="standard"> <StandardDashboard /> </Variant> <Variant id="advanced"> <AdvancedDashboard /> </Variant> </Adaptive></template><Adaptive> props:
| Prop | Type | Description |
|---|---|---|
id | string | Unique adaptation point identifier |
default-variant | string | Variant to show when no rule matches |
<Variant> props:
| Prop | Type | Description |
|---|---|---|
id | string | Variant identifier (referenced in rule actions) |
The <Adaptive> component internally uses useAdaptive() and automatically tracks impressions when the selected variant changes.
<AdaptiveOrder> Component
Section intitulée « <AdaptiveOrder> Component »A generic ordering component for any list of items. Reorders, hides, or preserves the original order based on adaptation rules. Uses Vue scoped slots for rendering each item. Use this when you need adaptive ordering for non-navigation content such as feature cards, pricing tiers, or tab panels.
<script setup lang="ts">import { AdaptiveOrder } from '@presage-kit/vue'
const features = [ { id: 'analytics', title: 'Analytics', description: 'Track user behavior' }, { id: 'automation', title: 'Automation', description: 'Automate repetitive tasks' }, { id: 'integrations', title: 'Integrations', description: 'Connect your tools' },]</script>
<template> <div class="feature-grid"> <AdaptiveOrder id="feature-highlights" :items="features" v-slot="{ item, index }"> <div class="feature-card"> <h3>{{ item.title }}</h3> <p>{{ item.description }}</p> </div> </AdaptiveOrder> </div></template>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'] },}Props:
| Prop | Type | Description |
|---|---|---|
id | string | Adaptation point identifier |
items | T[] (extends Orderable) | Array of items, each with at least an id field |
Scoped slot:
| Field | Type | Description |
|---|---|---|
item | T | The current item |
index | number | The current item index in the ordered list |
<AdaptiveNav> Component
Section intitulée « <AdaptiveNav> Component »A thin wrapper around <AdaptiveOrder>, specialized for navigation lists. Internally delegates all logic to <AdaptiveOrder>.
Renders a list of navigation items that can be reordered or hidden by adaptation rules. Uses Vue scoped slots for rendering each item.
<script setup lang="ts">import { AdaptiveNav } from '@presage-kit/vue'
const items = [ { id: 'home', label: 'Home', href: '/' }, { id: 'analytics', label: 'Analytics', href: '/analytics' }, { id: 'settings', label: 'Settings', href: '/settings' },]</script>
<template> <nav> <AdaptiveNav id="sidebar-nav" :items="items" v-slot="{ item }"> <a :href="item.href">{{ item.label }}</a> </AdaptiveNav> </nav></template>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'] },}Props:
| Prop | Type | Description |
|---|---|---|
id | string | Adaptation point identifier |
items | T[] (extends NavItem) | Array of items, each with at least an id field |
Scoped slot:
| Field | Type | Description |
|---|---|---|
item | T | The current navigation item |
Alternative: Manual AdaptiveNav with evaluateAction()
Section intitulée « Alternative: Manual AdaptiveNav with evaluateAction() »You can also use evaluateAction() directly for full control over reorder and hide logic.
<script setup lang="ts">import { computed } from 'vue'import { useAdaptiveClient } from '@presage-kit/vue'
interface NavItem { id: string label: string href: string}
const client = useAdaptiveClient()
const baseItems: NavItem[] = [ { id: 'home', label: 'Home', href: '/' }, { id: 'analytics', label: 'Analytics', href: '/analytics' }, { id: 'settings', label: 'Settings', href: '/settings' },]
const navItems = computed(() => { const action = client.evaluateAction('sidebar-nav')
if (action?.type === 'hide') return []
if (action?.type === 'reorder') { return reorder(baseItems, action.order) }
return baseItems})
function reorder(items: NavItem[], order: string[]): NavItem[] { const map = new Map(items.map((item) => [item.id, item])) const ordered: NavItem[] = []
for (const id of order) { const item = map.get(id) if (item) { ordered.push(item) map.delete(id) } }
// Append items not in the order list for (const item of map.values()) { ordered.push(item) }
return ordered}</script>
<template> <nav> <a v-for="item in navItems" :key="item.id" :href="item.href"> {{ item.label }} </a> </nav></template>Full Example: SaaS Dashboard
Section intitulée « Full Example: SaaS Dashboard »import { createAdaptiveClient, createLocalStorageDriver } from '@presage-kit/core'
export const client = createAdaptiveClient({ rules: [ { id: 'power-user-sidebar', adaptationId: 'sidebar-nav', priority: 10, conditions: { all: [{ field: 'maturity', operator: 'eq', value: 'power' }], }, action: { type: 'reorder', order: ['analytics', 'dashboards', 'settings', 'home'] }, }, { id: 'new-user-tour', adaptationId: 'onboarding', priority: 10, conditions: { all: [{ field: 'maturity', operator: 'eq', value: 'new' }], }, action: { type: 'show', variantId: 'guided-tour' }, }, { id: 'experienced-skip', adaptationId: 'onboarding', priority: 5, conditions: { any: [ { field: 'maturity', operator: 'eq', value: 'active' }, { field: 'maturity', operator: 'eq', value: 'power' }, ], }, action: { type: 'show', variantId: 'minimal' }, }, ], persistence: { driver: createLocalStorageDriver('vue-demo'), },})import { createApp } from 'vue'import { AdaptivePlugin } from '@presage-kit/vue'import { client } from './lib/adaptive-client'import App from './App.vue'
const app = createApp(App)app.use(AdaptivePlugin, { client })app.mount('#app')<script setup lang="ts">import SidebarNav from './components/SidebarNav.vue'import Onboarding from './components/Onboarding.vue'</script>
<template> <div class="layout"> <SidebarNav /> <main> <Onboarding /> <router-view /> </main> </div></template><script setup lang="ts">import { Adaptive, Variant } from '@presage-kit/vue'</script>
<template> <Adaptive id="onboarding" default-variant="standard"> <Variant id="guided-tour"> <div class="onboarding"> <h2>Welcome! Let us show you around.</h2> <button>Complete Tour</button> </div> </Variant> <Variant id="standard"> <div class="onboarding"> <h2>What is new</h2> <p>Check out the latest features.</p> </div> </Variant> <Variant id="minimal"> <div class="onboarding"> <p>Welcome back!</p> </div> </Variant> </Adaptive></template><script setup lang="ts">import { AdaptiveNav } from '@presage-kit/vue'
const items = [ { id: 'home', label: 'Home', href: '/' }, { id: 'dashboards', label: 'Dashboards', href: '/dashboards' }, { id: 'analytics', label: 'Analytics', href: '/analytics' }, { id: 'settings', label: 'Settings', href: '/settings' },]</script>
<template> <nav class="sidebar"> <AdaptiveNav id="sidebar-nav" :items="items" v-slot="{ item }"> <a :href="item.href">{{ item.label }}</a> </AdaptiveNav> </nav></template>