npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@twenty1tools/whatif

v0.2.0

Published

What If? SDK for collecting contextual user insights

Readme

WhatIf Widget SDK

A lightweight, customizable SDK for collecting contextual user feedback through beautiful widgets.

Installation

npm install @twenty1tools/whatif

Basic Usage

import { WhatIf } from '@twenty1tools/whatif';

// Initialize the SDK
const whatif = new WhatIf('your-project-id');

// Show a question
await whatif.ask('question-id-123');

Theming & Customization

The SDK supports extensive theming options to match your website's design. All theme properties are optional and have sensible defaults.

Theme Configuration

import { WhatIf, type ThemeConfig } from '@twenty1tools/whatif';

const theme: ThemeConfig = {
  // Dark mode configuration
  darkMode: 'auto', // 'auto' | 'light' | 'dark' | 'disabled'
  
  // Widget position
  position: 'bottom-right', // 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'
  
  // Colors
  colors: {
    primary: '#f97316',
    primaryHover: '#ea580c',
    background: '#ffffff',
    textPrimary: '#111827',
    textSecondary: '#6b7280',
    border: '#e5e7eb',
    borderHover: '#f97316',
    hoverBackground: '#f9fafb',
    closeButton: '#6b7280',
    closeButtonHover: '#111827',
    ratingFilled: '#fbbf24',
    ratingEmpty: '#d1d5db',
  },
  
  // Typography
  typography: {
    fontFamily: 'Inter, system-ui, sans-serif',
    questionSize: '18px',
    questionWeight: '600',
    inputSize: '14px',
    buttonSize: '14px',
    buttonWeight: '500',
  },
  
  // Spacing
  spacing: {
    padding: '24px',
    gap: '16px',
    offset: '20px', // Distance from screen edge
  },
  
  // Borders
  border: {
    radius: '16px',
    buttonRadius: '8px',
    inputRadius: '6px',
    width: '1px',
  },
  
  // Shadow
  shadow: {
    enabled: true,
    container: '0 10px 40px rgba(0,0,0,0.15)',
  },
  
  // Animations
  animation: {
    enabled: true,
    duration: 400, // milliseconds
    easing: 'cubic-bezier(0.16, 1, 0.3, 1)',
  },
  
  // Custom CSS (injected into shadow DOM)
  customCss: `
    .widget-container {
      border: 2px solid #f97316;
    }
  `,
};

const whatif = new WhatIf('your-project-id', '/api', theme);

Dark Mode

The SDK supports three dark mode strategies:

Auto Mode (Recommended)

Automatically detects and respects the user's system preference:

const whatif = new WhatIf('your-project-id', '/api', {
  darkMode: 'auto',
});

Force Light Mode

const whatif = new WhatIf('your-project-id', '/api', {
  darkMode: 'light',
});

Force Dark Mode

const whatif = new WhatIf('your-project-id', '/api', {
  darkMode: 'dark',
  colors: {
    // Dark mode colors are automatically applied
    // but you can override them
    primary: '#fb923c',
    background: '#1f2937',
    textPrimary: '#f9fafb',
  },
});

Disabled (Default)

const whatif = new WhatIf('your-project-id', '/api', {
  darkMode: 'disabled', // or omit entirely
});

Common Use Cases

Match Your Brand Colors

const whatif = new WhatIf('your-project-id', '/api', {
  colors: {
    primary: '#3b82f6', // Blue
    primaryHover: '#2563eb',
  },
});

Minimal Design

const whatif = new WhatIf('your-project-id', '/api', {
  shadow: {
    enabled: false,
  },
  border: {
    radius: '4px',
    buttonRadius: '4px',
  },
  colors: {
    primary: '#000000',
    primaryHover: '#333333',
  },
});

Playful Design

const whatif = new WhatIf('your-project-id', '/api', {
  border: {
    radius: '24px',
    buttonRadius: '24px',
  },
  colors: {
    primary: '#ec4899', // Pink
    primaryHover: '#db2777',
    ratingFilled: '#f472b6',
  },
  animation: {
    duration: 600,
    easing: 'cubic-bezier(0.68, -0.55, 0.265, 1.55)', // Bouncy
  },
});

Corporate/Professional

const whatif = new WhatIf('your-project-id', '/api', {
  colors: {
    primary: '#1e40af', // Navy blue
    primaryHover: '#1e3a8a',
    background: '#ffffff',
  },
  typography: {
    fontFamily: '"Helvetica Neue", Arial, sans-serif',
  },
  border: {
    radius: '8px',
  },
  shadow: {
    container: '0 4px 12px rgba(0,0,0,0.08)',
  },
});

Dynamic Theming

You can update the theme at runtime:

const whatif = new WhatIf('your-project-id');

// Later, update theme
whatif.setTheme({
  darkMode: 'dark',
  colors: {
    primary: '#10b981', // Green
  },
});

Widget Positioning

Control where the widget appears on screen:

// Bottom right (default)
const whatif = new WhatIf('your-project-id', '/api', {
  position: 'bottom-right',
});

// Bottom left
const whatif = new WhatIf('your-project-id', '/api', {
  position: 'bottom-left',
});

// Top right
const whatif = new WhatIf('your-project-id', '/api', {
  position: 'top-right',
});

// Top left
const whatif = new WhatIf('your-project-id', '/api', {
  position: 'top-left',
});

Advanced: Custom CSS

For complete control, you can inject custom CSS into the shadow DOM:

const whatif = new WhatIf('your-project-id', '/api', {
  customCss: `
    /* Target widget container */
    .widget-container {
      backdrop-filter: blur(10px);
      background: rgba(255, 255, 255, 0.9) !important;
    }
    
    /* Style buttons */
    button[type="submit"] {
      text-transform: uppercase;
      letter-spacing: 1px;
    }
    
    /* Custom animations */
    @keyframes customPulse {
      0%, 100% { opacity: 1; }
      50% { opacity: 0.5; }
    }
    
    .star-svg {
      animation: customPulse 2s infinite;
    }
  `,
});

TypeScript Support

The SDK is fully typed with TypeScript. All theme options include JSDoc comments for IntelliSense:

import { WhatIf, type ThemeConfig, type DarkMode, type ColorPalette } from '@twenty1tools/whatif';

const colors: ColorPalette = {
  primary: '#3b82f6',
  // TypeScript will autocomplete and validate
};

const darkMode: DarkMode = 'auto'; // Only accepts valid values

API Reference

Constructor

new WhatIf(projectId: string, baseUrl?: string, theme?: ThemeConfig)
  • projectId: Your project ID
  • baseUrl: API base URL (default: /api)
  • theme: Theme configuration object

Methods

ask(questionId: string, options?: AskOptions): Promise<void>

Display a question widget.

await whatif.ask('question-123', {
  context: { page: 'checkout' },
  userFingerprint: 'user-abc',
});

setTheme(theme: ThemeConfig): void

Update the theme configuration.

whatif.setTheme({ darkMode: 'dark' });

remove(): void

Manually close the current widget.

whatif.remove();

destroy(): void

Clean up all resources (useful when unmounting).

whatif.destroy();

Examples

React Hook

import { useEffect, useRef } from 'react';
import { WhatIf } from '@twenty1tools/whatif';

function useWhatIf(projectId: string, theme?: ThemeConfig) {
  const whatifRef = useRef<WhatIf>();

  useEffect(() => {
    whatifRef.current = new WhatIf(projectId, '/api', theme);
    
    return () => {
      whatifRef.current?.destroy();
    };
  }, [projectId]);

  return whatifRef.current;
}

// Usage
function MyComponent() {
  const whatif = useWhatIf('your-project-id', {
    darkMode: 'auto',
    colors: { primary: '#3b82f6' },
  });

  const handleFeedback = () => {
    whatif?.ask('question-123');
  };

  return <button onClick={handleFeedback}>Give Feedback</button>;
}

Next.js App Router

'use client';

import { useEffect } from 'react';
import { WhatIf } from '@twenty1tools/whatif';

export default function WhatIfProvider() {
  useEffect(() => {
    const whatif = new WhatIf('your-project-id', '/api', {
      darkMode: 'auto',
    });

    // Trigger on specific events
    window.addEventListener('whatif:show', (e: CustomEvent) => {
      whatif.ask(e.detail.questionId);
    });

    return () => whatif.destroy();
  }, []);

  return null;
}

Vanilla JavaScript

<!DOCTYPE html>
<html>
<head>
  <script type="module">
    import { WhatIf } from 'https://unpkg.com/@twenty1tools/whatif';

    const whatif = new WhatIf('your-project-id', '/api', {
      darkMode: 'auto',
      colors: {
        primary: '#10b981',
      },
    });

    // Show widget on button click
    document.getElementById('feedback-btn').addEventListener('click', () => {
      whatif.ask('question-123');
    });
  </script>
</head>
<body>
  <button id="feedback-btn">Give Feedback</button>
</body>
</html>

Browser Support

  • Chrome/Edge: Latest 2 versions
  • Firefox: Latest 2 versions
  • Safari: Latest 2 versions
  • Mobile browsers: iOS Safari, Chrome Android

License

MIT