Vanilla JavaScript
Use with any framework - Vue, Angular, Svelte, or plain HTML ⚡
The vanilla JavaScript SDK works everywhere - HTML, Vue, Angular, Svelte, or any JavaScript framework.
Installation
npm install @twenty1tools/whatifThen import:
import { WhatIf } from '@twenty1tools/whatif';
const whatif = new WhatIf('your-app-id');<script type="module">
import { WhatIf } from 'https://unpkg.com/@twenty1tools/whatif';
const whatif = new WhatIf('your-app-id');
document.getElementById('btn').addEventListener('click', () => {
whatif.ask('question-id');
});
</script><script type="module" src="main.js"></script>import { WhatIf } from 'https://unpkg.com/@twenty1tools/whatif';
const whatif = new WhatIf('your-app-id');
window.whatif = whatif; // Make available globallyBasic Usage
Initialize SDK
import { WhatIf } from '@twenty1tools/whatif';
const whatif = new WhatIf('your-app-id', '/api', {
// Optional theme configuration
darkMode: 'auto',
colors: {
primary: '#3b82f6',
}
});Trigger on Button Click
<button id="feedback-btn">Give Feedback</button>
<script type="module">
import { WhatIf } from '@twenty1tools/whatif';
const whatif = new WhatIf('your-app-id');
document.getElementById('feedback-btn').addEventListener('click', () => {
whatif.ask('question-id');
});
</script>With Context
whatif.ask('question-id', {
user: {
id: 'user-123', // Required for smart filtering
name: 'Sarah Chen', // Optional - for dashboard display
email: 'sarah@example.com', // Optional - for dashboard display
avatar: 'https://example.com/avatar.jpg' // Optional - for dashboard display
},
org: {
id: 'org_acme_123', // Optional - for organization tracking
name: 'Acme Corp', // Optional - for dashboard display
logo: 'https://example.com/logo.png' // Optional - for dashboard display
},
context: {
// Custom context only (no user/org data)
page: 'checkout',
plan: 'pro'
},
userFingerprint: 'anonymous-user-id' // Fallback for anonymous users
});Automatic Data: WhatIf automatically captures timestamp, browser, device, page info, and previous URL with every response - no code needed! See the Context Guide for details.
Complete Example
<!DOCTYPE html>
<html>
<head>
<title>Feedback Widget</title>
</head>
<body>
<h1>My Website</h1>
<button id="feedback-btn">Give Feedback</button>
<script type="module">
import { WhatIf } from 'https://unpkg.com/@twenty1tools/whatif';
// Initialize with theme
const whatif = new WhatIf('your-app-id', '/api', {
darkMode: 'auto',
position: 'bottom-right',
colors: {
primary: '#3b82f6',
},
typography: {
fontFamily: 'Inter, sans-serif',
}
});
// Trigger on button click
document.getElementById('feedback-btn').addEventListener('click', () => {
whatif.ask('general-feedback', {
context: {
page: window.location.pathname,
timestamp: Date.now()
}
});
});
// Exit intent
document.addEventListener('mouseleave', (e) => {
if (e.clientY <= 0) {
whatif.ask('exit-survey');
}
});
// After 30 seconds
setTimeout(() => {
whatif.ask('time-based-feedback');
}, 30000);
</script>
</body>
</html>Framework Integration
Vue 3
<template>
<button @click="showFeedback">Give Feedback</button>
</template>
<script setup>
import { WhatIf } from '@twenty1tools/whatif';
import { onMounted, onUnmounted } from 'vue';
let whatif;
onMounted(() => {
whatif = new WhatIf('your-app-id', '/api', {
darkMode: 'auto',
colors: { primary: '#42b883' } // Vue green
});
});
onUnmounted(() => {
whatif?.destroy();
});
function showFeedback() {
whatif.ask('question-id');
}
</script>Angular
// feedback.service.ts
import { Injectable, OnDestroy } from '@angular/core';
import { WhatIf } from '@twenty1tools/whatif';
@Injectable({ providedIn: 'root' })
export class FeedbackService implements OnDestroy {
private whatif: WhatIf;
constructor() {
this.whatif = new WhatIf('your-app-id', '/api', {
darkMode: 'auto',
colors: { primary: '#dd0031' } // Angular red
});
}
ask(questionId: string, context?: Record<string, unknown>) {
return this.whatif.ask(questionId, { context });
}
ngOnDestroy() {
this.whatif.destroy();
}
}// component.ts
import { Component } from '@angular/core';
import { FeedbackService } from './feedback.service';
@Component({
selector: 'app-root',
template: '<button (click)="showFeedback()">Feedback</button>'
})
export class AppComponent {
constructor(private feedback: FeedbackService) {}
showFeedback() {
this.feedback.ask('question-id');
}
}Svelte
<script>
import { WhatIf } from '@twenty1tools/whatif';
import { onMount, onDestroy } from 'svelte';
let whatif;
onMount(() => {
whatif = new WhatIf('your-app-id', '/api', {
darkMode: 'auto',
colors: { primary: '#ff3e00' } // Svelte orange
});
});
onDestroy(() => {
whatif?.destroy();
});
function showFeedback() {
whatif.ask('question-id');
}
</script>
<button on:click={showFeedback}>Give Feedback</button>API Methods
Constructor
new WhatIf(projectId: string, baseUrl?: string, theme?: ThemeConfig)Parameters:
projectId(required): Your project ID from dashboardbaseUrl(optional): API endpoint (default:"/api")theme(optional): Theme configuration object
ask()
Show a question widget.
await whatif.ask(questionId: string, options?: AskOptions)Parameters:
questionId(required): Question ID from dashboardoptions(optional):context: Additional data objectuserFingerprint: Anonymous user identifier
Returns: Promise<void>
setTheme()
Update theme at runtime.
whatif.setTheme(theme: ThemeConfig)Example:
whatif.setTheme({
darkMode: 'dark',
colors: { primary: '#10b981' }
});remove()
Manually close the current widget.
whatif.remove()destroy()
Clean up all resources (event listeners, etc).
whatif.destroy()Always call destroy() when removing the SDK to prevent memory leaks.
Common Patterns
Exit Intent
const whatif = new WhatIf('app-id');
let hasShown = false;
document.addEventListener('mouseleave', (e) => {
if (e.clientY <= 0 && !hasShown) {
hasShown = true;
whatif.ask('exit-survey');
}
});Scroll Depth
const whatif = new WhatIf('app-id');
let triggered = false;
window.addEventListener('scroll', () => {
const scrollPercent = (window.scrollY / document.body.scrollHeight) * 100;
if (scrollPercent > 75 && !triggered) {
triggered = true;
whatif.ask('deep-engagement', {
context: { scrollDepth: scrollPercent }
});
}
});Time-Based
const whatif = new WhatIf('app-id');
const startTime = Date.now();
setTimeout(() => {
whatif.ask('engagement-survey', {
context: {
timeOnPage: Date.now() - startTime
}
});
}, 30000); // After 30 secondsPage Visibility
const whatif = new WhatIf('app-id');
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
// User switched tabs
whatif.ask('abandonment-survey');
}
});Theming
Full theme customization:
const whatif = new WhatIf('app-id', '/api', {
// Dark mode
darkMode: 'auto',
// Position
position: 'bottom-right',
// Colors
colors: {
primary: '#3b82f6',
primaryHover: '#2563eb',
background: '#ffffff',
textPrimary: '#1f2937',
},
// Typography
typography: {
fontFamily: 'Inter, sans-serif',
questionSize: '20px',
},
// Spacing
spacing: {
padding: '28px',
gap: '20px',
},
// Borders
border: {
radius: '16px',
buttonRadius: '8px',
},
// Effects
shadow: {
enabled: true,
container: '0 20px 50px rgba(0,0,0,0.15)',
},
// Animation
animation: {
enabled: true,
duration: 400,
}
});TypeScript Support
Full TypeScript types available:
import { WhatIf, type ThemeConfig, type AskOptions } from '@twenty1tools/whatif';
const theme: ThemeConfig = {
darkMode: 'auto',
colors: { primary: '#3b82f6' }
};
const whatif = new WhatIf('app-id', '/api', theme);
const options: AskOptions = {
user: {
id: 'user_123',
name: 'John Doe'
},
context: {
page: '/dashboard'
},
userFingerprint: 'anon-user' // Fallback for anonymous users
};
await whatif.ask('question-id', options);Browser Support
- ✅ Chrome/Edge (latest 2 versions)
- ✅ Firefox (latest 2 versions)
- ✅ Safari (latest 2 versions)
- ✅ Mobile browsers (iOS Safari, Chrome Android)
Requires:
- ES6 support
- Shadow DOM support
- Modern JavaScript features
Best Practices
✅ Do
- Clean up with
destroy()when done - Use context for better insights
- Rate limit questions
- Test in different browsers
❌ Don't
- Don't create multiple instances unnecessarily
- Don't forget to remove event listeners
- Don't spam users with questions
- Don't ignore errors