Documentation

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/whatif

Then 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>
main.js
import { WhatIf } from 'https://unpkg.com/@twenty1tools/whatif';

const whatif = new WhatIf('your-app-id');
window.whatif = whatif; // Make available globally

Basic 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 dashboard
  • baseUrl (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 dashboard
  • options (optional):
    • context: Additional data object
    • userFingerprint: 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 seconds

Page 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

Next Steps