Vue Adapter
The @presage-kit/vue package provides Vue 3 composables and a plugin for building adaptive interfaces.
Installation
Section titled “Installation”pnpm add @presage-kit/core @presage-kit/vueAdaptivePlugin
Section titled “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 titled “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 titled “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 titled “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.
Manual AdaptiveNav with evaluateAction()
Section titled “Manual AdaptiveNav with evaluateAction()”Unlike the React adapter, the Vue package does not ship a dedicated AdaptiveNav component. Instead, use evaluateAction() directly to reorder or hide navigation items.
<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 titled “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 { useAdaptive } from '@presage-kit/vue'
const { selectedVariant, 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'" class="onboarding"> <h2>Welcome! Let us show you around.</h2> <button @click="handleComplete">Complete Tour</button> </div> <div v-else-if="selectedVariant === 'standard'" class="onboarding"> <h2>What is new</h2> <p>Check out the latest features.</p> </div> <div v-else class="onboarding"> <p>Welcome back!</p> </div></template><script setup lang="ts">import { computed } from 'vue'import { useAdaptiveClient } from '@presage-kit/vue'
const client = useAdaptiveClient()
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' },]
const orderedItems = computed(() => { const action = client.evaluateAction('sidebar-nav') if (action?.type === 'hide') return [] if (action?.type === 'reorder') { const map = new Map(items.map((i) => [i.id, i])) const result = action.order .filter((id) => map.has(id)) .map((id) => map.get(id)!) const remaining = items.filter((i) => !action.order.includes(i.id)) return [...result, ...remaining] } return items})</script>
<template> <nav class="sidebar"> <a v-for="item in orderedItems" :key="item.id" :href="item.href"> {{ item.label }} </a> </nav></template>