Skip to content

Vue Adapter

The @presage-kit/vue package provides Vue 3 composables and a plugin for building adaptive interfaces.

Terminal window
pnpm add @presage-kit/core @presage-kit/vue

Install the plugin to provide the AdaptiveClient to all components via Vue’s dependency injection.

src/main.ts
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:

OptionTypeDescription
clientAdaptiveClientThe client instance from createAdaptiveClient()

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:

ParamTypeDescription
adaptationIdstringUnique identifier for the adaptation point
config.variantsreadonly string[]Available variant IDs
config.defaultVariantstringFallback variant
config.strategyStrategyOptional. Defaults to { type: 'rules' }

Return type:

The return object uses reactive getters, so values update automatically when the user context changes.

FieldTypeDescription
selectedVariantV (getter)The selected variant string
resolutionResolvedAdaptation (getter)Full resolution details
contextUserContext (getter)Current user context
track(event: string, properties?: Record<string, unknown>) => voidScoped track function

Automatic impression tracking: Like the React hook, useAdaptive() watches for variant changes and automatically tracks presage:impression events.

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).

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.

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>
src/lib/adaptive-client.ts
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'),
},
})
src/main.ts
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')
src/App.vue
<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>
src/components/Onboarding.vue
<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>
src/components/SidebarNav.vue
<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>