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

@sensefolks/pricepoint

v3.0.3

Published

Find the right price that your users are willing to pay

Readme

sf-pricepoint

npm version npm downloads License: MIT

Price sensitivity survey web component for Van Westendorp and Gabor-Granger pricing research.

Features

  • 💰 Van Westendorp Price Sensitivity Meter + Gabor-Granger demand analysis
  • 🔄 Automatic two-phase pipeline (VW → GG) based on response volume
  • 💱 Multi-currency support with auto-detection from respondent locale
  • ♿ Accessibility-oriented keyboard, screen reader, and reduced motion support
  • 🎨 Customizable via CSS Parts and Custom Properties
  • 📱 Responsive design
  • 🌐 Works with React + Next.js, Vue + Nuxt, Angular, Svelte, Astro, and vanilla HTML

Installation

NPM

npm install @sensefolks/pricepoint

Yarn

yarn add @sensefolks/pricepoint

CDN (Script Tag)

<!-- Modern browsers (ES modules) -->
<script type="module" src="https://unpkg.com/@sensefolks/[email protected]/dist/sf-pricepoint/sf-pricepoint.esm.js"></script>

Or via jsDelivr:

<script type="module" src="https://cdn.jsdelivr.net/npm/@sensefolks/[email protected]/dist/sf-pricepoint/sf-pricepoint.esm.js"></script>

Quick Start

<sf-pricepoint survey-key="your-survey-uuid"></sf-pricepoint>

React

import '@sensefolks/pricepoint';

function App() {
  return <sf-pricepoint survey-key="your-survey-uuid"></sf-pricepoint>;
}

Vue

<template>
  <sf-pricepoint survey-key="your-survey-uuid"></sf-pricepoint>
</template>

<script>
import '@sensefolks/pricepoint';
export default { name: 'App' };
</script>

Angular

// app.module.ts
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import '@sensefolks/pricepoint';

@NgModule({
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
<!-- component.html -->
<sf-pricepoint survey-key="your-survey-uuid"></sf-pricepoint>

Framework Notes

  • React + Next.js: use the React snippet above. In Next.js, render surveys inside a 'use client' component.
  • Vue + Nuxt: use the Vue snippet above. In Nuxt pages that SSR, wrap the survey with <ClientOnly>.
  • Svelte: load the package inside onMount and render the same sf-* tag.
  • Astro: keep the sf-* tag in markup and load the package with a client-side module import.
  • Vanilla JS: use the HTML/CDN snippet or a module import in a <script type="module"> block.

For complete copy-paste examples, see: https://sensefolks.com/docs/tutorials/embedding-a-survey/

API Reference

Properties

| Property | Attribute | Type | Default | Description | | ------------------- | -------------------- | -------- | -------------------------------- | -------------------------------------------------- | | surveyKey | survey-key | string | undefined | Public key (UUID) of the survey to load (required) | | completionMessage | completion-message | string | 'Thank you for your response!' | Fallback message shown after submission when the survey has no saved thank-you message | | thankYouMessage | thank-you-message | string | undefined | Overrides the saved survey thank-you message | | sessionData | — | object | {} | Session data passed from the host app; must match the declared Session Data schema |

Events

| Event | Detail Type | Description | | --------------- | -------------------------- | ------------------------------------------------- | | sfReady | SfReadyEventDetail | Survey config loaded and component ready | | sfSubmit | SfSubmitEventDetail | Response successfully submitted | | sfError | SfErrorEventDetail | Error occurred (load, submit, validation, config) | | sfPhaseChange | SfPhaseChangeEventDetail | Phase transition (loading → vw → gg → complete) |

Event Payload Shapes

interface SfReadyEventDetail {
  surveyKey: string;
  productType: string;
  phase: string;
}

interface SfSubmitEventDetail {
  surveyKey: string;
  phase: string;
  completionTimeSeconds: number;
}

interface SfErrorEventDetail {
  surveyKey: string;
  errorType: 'load' | 'submit' | 'validation' | 'config';
  errorMessage: string;
}

interface SfPhaseChangeEventDetail {
  phase: string; // 'loading' | 'vw' | 'gg' | 'complete' | 'error'
}

Configuration Schema

The survey is configured server-side. The component fetches config via the surveyKey.

{
  "productContext": {
    "productName": "My SaaS Product",
    "productCategory": "Software",
    "industry": "Technology"
  },
  "targetMarkets": ["US", "GB"],
  "productType": "subscription",
  "billingInterval": "monthly",
  "priceRange": { "min": 10, "max": 200 },
  "currencyMode": "USD",
  "costAnalysis": {
    "annualCostPerCustomer": 50,
    "annualFixedExpenses": 100000,
    "expectedAnnualCustomers": 5000,
    "desiredProfitMargin": 30
  }
}

CSS Shadow Parts

The component uses shadow DOM and exposes the following CSS parts for styling:

| Part | Description | | ----------------------- | ----------------------------------------------------------------- | | survey-container | Outer wrapper for the entire survey | | heading | Primary heading elements | | progress | Top progress header wrapper | | progress-indicator | Compatibility alias for the progress header | | progress-label | "Task X of Y" label | | progress-text | Compatibility alias for the progress label | | progress-track | Continuous progress bar track | | progress-bar | Compatibility alias for the progress bar track | | progress-fill | Filled portion of the progress bar | | vw-container | Van Westendorp phase wrapper | | question-container | Wrapper for each question | | question-text | Question text paragraph | | validation-errors | Validation error container | | validation-error | Individual validation error message | | button-container | Wrapper for action buttons | | submit-button | The submit/next action button | | error-container | Wrapper for error state display | | error-message | Error message text | | troubleshooting-link | Troubleshooting link shown with integration errors | | retry-button | Retry button shown on error | | loading-message | Loading state text | | completion-step | Completion/thank-you screen wrapper | | hcaptcha-container | Wrapper for the hCaptcha widget (when enabled) | | branding | SenseFolks branding wrapper | | branding-link | SenseFolks branding link | | branding-logo | SenseFolks branding logo | | gg-container | Gabor-Granger phase wrapper | | price-display | Price value display span | | radio-group | Wrapper for radio button group | | radio-option | A single radio option row | | radio-option-selected | Applied to the selected radio option (additive with radio-option) | | radio-input | Survey Field radio input | | radio-label | Radio option label text | | survey-fields-container | Survey Fields section wrapper | | form-container | Form container compatibility alias | | survey-fields-heading | Survey Fields section heading | | form-field | Individual Survey Field wrapper | | form-label | Survey Field label | | required-indicator | Required field marker | | survey-field-control | Survey Field input wrapper | | form-input | Text/email/number Survey Field input | | form-select | Dropdown Survey Field input | | field-error | Survey Field validation error | | checkbox-group | Checkbox Survey Field group | | checkbox-option | Checkbox Survey Field option wrapper | | checkbox-input | Checkbox Survey Field input | | checkbox-label | Checkbox Survey Field label | | announcements | ARIA live region for screen reader announcements |

CSS Custom Properties

| Property | Default | Description | | --------------------- | ----------------------- | -------------------------------- | | --sf-primary | #005fcc | Primary accent color | | --sf-primary-hover | #0047a3 | Primary color hover state | | --sf-text-primary | #111827 | Primary text color | | --sf-text-secondary | #6b7280 | Secondary/muted text color | | --sf-error-color | #dc2626 | Error state color | | --sf-error-text | #991b1b | Integration error text color | | --sf-error-bg | #fef2f2 | Integration error background | | --sf-error-border | #fecaca | Integration error border color | | --sf-card-bg | #ffffff | Card/container background | | --sf-card-border | #d1d5db | Card/container border color | | --sf-button-text | #ffffff | Button text color | | --sf-card-radius | 8px | Card/container border radius | | --sf-button-radius | 6px | Button border radius | | --sf-transition | 150ms ease | Default transition timing | | --sf-font-family | system-ui, sans-serif | Font family (backward-compat) | | --sf-spacing-scale | 1 | Spacing multiplier — 1 = default | | --sf-progress-bg | #e5e7eb | Progress track color | | --sf-progress-fill | primary | Progress fill color |

Legacy aliases --sf-primary-color, --sf-text-color, --sf-background-color, and --sf-border-radius are still honoured via CSS fallback chains for backward compatibility.

Styling example

/* Override primary color and card radius */
sf-pricepoint {
  --sf-primary: #7c3aed;
  --sf-primary-hover: #6d28d9;
  --sf-card-radius: 12px;
  --sf-button-radius: 8px;
}

/* Style the submit button */
sf-pricepoint::part(submit-button) {
  font-weight: 600;
  letter-spacing: 0.025em;
  text-transform: uppercase;
}

/* Highlight the selected radio option */
sf-pricepoint::part(radio-option-selected) {
  box-shadow: 0 0 0 2px var(--sf-primary);
}

/* Style the price display in the Gabor-Granger phase */
sf-pricepoint::part(price-display) {
  font-size: 1.25rem;
  font-weight: 700;
}

Privacy And Network Requirements

sf-pricepoint loads survey configuration from the SenseFolks embed request endpoint and submits responses to the SenseFolks embed response endpoint. The survey-key value is validated as a UUID before requests are made and is URL encoded in request paths.

Submitted payloads can include the respondent's answers, configured survey fields, prepared session data values supplied by the host page, completion timing, and metadata including user-agent string, platform, language, cookie-enabled status, online status, screen resolution, color depth, timezone, and timestamp. This component does not use cookies, localStorage, sessionStorage, or IndexedDB. In-progress answers live only in the current component instance; reloading the page or creating a new instance always starts a blank response.

If hCaptcha is enabled by the survey config, allow hCaptcha in your CSP: script-src https://js.hcaptcha.com, frame-src https://hcaptcha.com https://*.hcaptcha.com, and a connect-src that includes your SenseFolks API origin plus the hCaptcha endpoints.

Accessibility

  • Full keyboard navigation (Tab, Enter, Space, Arrow keys)
  • ARIA role="slider" with aria-valuemin, aria-valuemax, aria-valuenow, aria-label on hybrid slider inputs
  • ARIA role="radiogroup" on Gabor-Granger purchase intent scale
  • aria-live="polite" region for screen reader announcements on phase/question transitions
  • Visible focus indicators on all interactive elements
  • Respects prefers-reduced-motion: reduce
  • High contrast mode support via @media (prefers-contrast: high)

Browser Support

  • Chrome 88+
  • Firefox 85+
  • Safari 14+
  • Edge 88+

Internet Explorer 11 is not supported. The host browser must provide modern Web Component APIs, fetch, and AbortController.

Agent Integration

This section is designed for AI code generation agents integrating the sf-pricepoint component.

Machine-Readable Config Example

{
  "component": "sf-pricepoint",
  "package": "@sensefolks/pricepoint",
  "attributes": [
    {
      "name": "survey-key",
      "type": "string",
      "required": true,
      "description": "Public key UUID of the survey"
    },
    {
      "name": "completion-message",
      "type": "string",
      "required": false,
      "description": "Fallback message shown after submission when the survey has no saved thank-you message. Defaults to \"Thank you for your response!\""
    }
  ],
  "events": [
    { "name": "sfReady", "detail": "SfReadyEventDetail", "description": "Component loaded and ready" },
    { "name": "sfSubmit", "detail": "SfSubmitEventDetail", "description": "Response submitted" },
    { "name": "sfError", "detail": "SfErrorEventDetail", "description": "Error occurred" },
    { "name": "sfPhaseChange", "detail": "SfPhaseChangeEventDetail", "description": "Phase changed" }
  ],
  "cssCustomProperties": [
    "--sf-primary",
    "--sf-primary-hover",
    "--sf-text-primary",
    "--sf-text-secondary",
    "--sf-error-color",
    "--sf-error-text",
    "--sf-error-bg",
    "--sf-error-border",
    "--sf-card-bg",
    "--sf-card-border",
    "--sf-button-text",
    "--sf-card-radius",
    "--sf-button-radius",
    "--sf-transition",
    "--sf-font-family",
    "--sf-spacing-scale",
    "--sf-progress-bg",
    "--sf-progress-fill"
  ],
  "cssParts": [
    "survey-container",
    "heading",
    "progress",
    "progress-indicator",
    "progress-label",
    "progress-text",
    "progress-track",
    "progress-bar",
    "progress-fill",
    "vw-container",
    "question-container",
    "question-text",
    "validation-errors",
    "validation-error",
    "button-container",
    "submit-button",
    "error-container",
    "error-message",
    "troubleshooting-link",
    "retry-button",
    "loading-message",
    "completion-step",
    "hcaptcha-container",
    "branding",
    "branding-link",
    "branding-logo",
    "gg-container",
    "price-display",
    "radio-group",
    "radio-option",
    "radio-option-selected",
    "radio-label",
    "announcements"
  ]
}

Emitted Events with Payload Shapes

| Event | Payload Fields | When Emitted | | --------------- | ----------------------------------------------------------------- | --------------------------- | | sfReady | surveyKey: string, productType: string, phase: string | After config fetch and init | | sfSubmit | surveyKey: string, phase: string, completionTimeSeconds: number | After successful POST | | sfError | surveyKey: string, errorType: string, errorMessage: string | On any error | | sfPhaseChange | phase: string | On phase transition |

Integration Checklist

  1. Install the package: npm install @sensefolks/pricepoint
  2. Import the component: import '@sensefolks/pricepoint';
  3. Add the element: <sf-pricepoint survey-key="YOUR_KEY"></sf-pricepoint>
  4. Listen for events: el.addEventListener('sfReady', (e) => { ... })
  5. Apply custom styles via CSS custom properties or ::part() selectors
  6. Ensure the survey is created and configured via the API before embedding

TypeScript Types

All types are exported from the package entry point:

import type {
  PricepointSurveyConfig,
  PriceLadderResponse,
  CurrencyResolution,
  VWAnswers,
  SfReadyEventDetail,
  SfSubmitEventDetail,
  SfErrorEventDetail,
  SfPhaseChangeEventDetail,
} from '@sensefolks/pricepoint';

Changelog

See CHANGELOG.md.

License

MIT © SenseFolks