PricePoint

PricePoint runs structured pricing research on your site. Use Van Westendorp or Gabor-Granger to estimate acceptable price ranges and willingness to pay.

@sensefolks/pricepoint WCAG 2.1 AA <10KB gzipped

How It Works

PricePoint supports two pricing methods. You select the method when creating the survey in your dashboard.

Van Westendorp

The Van Westendorp Price Sensitivity Meter uses four questions to map where "too cheap" meets "too expensive."

  1. Too Cheap — At what price would you question the quality?
  2. Bargain — At what price would it be a great deal?
  3. Expensive — At what price does it start to seem expensive?
  4. Too Expensive — At what price is it too expensive to consider?

Where those four curves cross tells you the optimal price point, the acceptable range, and the sensitivity thresholds. For a deeper walkthrough, see the Van Westendorp pricing guide.

Gabor-Granger

Gabor-Granger uses a ladder of candidate prices and asks whether users would buy at each point. The output forms a demand curve.

Configure the price ladder in the dashboard. The component presents each price in sequence and records willingness to pay.

Survey Phases

PricePoint moves through these phases during a session. The active phase shows up in the sfPhaseChange event and determines which UI the respondent sees:

Phase What happens
loading Component fetches the survey configuration
vw Respondent answers the four Van Westendorp price sensitivity questions
gg Respondent evaluates a series of price points on a radio scale
complete Survey submitted. The completion screen shows your completionMessage
error Something went wrong during load or submission. A retry option appears

Which methodology runs (vw or gg) depends on the survey type you set in your dashboard. Each survey uses one method per session.

When to Use PricePoint

Embed PricePoint where pricing decisions happen:

  • Pricing pages — Understand willingness to pay before launch
  • Upgrade screens — Find the conversion-optimized price
  • Trial-to-paid flows — Discover the right price for conversion
  • Cancellation flows — Learn the price sensitivity of churning users
  • New feature launches — Price premium features correctly
  • Market expansion — Test pricing in new markets

Installation

CDN (Recommended)

Add directly to your HTML:

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

<!-- Legacy browsers -->
<script nomodule src="https://unpkg.com/@sensefolks/pricepoint/dist/sf-pricepoint/sf-pricepoint.js"></script>

NPM

For projects with a build system:

bash
npm install @sensefolks/pricepoint

Framework Integration

HTML / Vanilla JS

html
<sf-pricepoint 
  survey-key="your-survey-uuid" 
  completion-message="Thank you for your feedback!">
</sf-pricepoint>

React

jsx
import { useEffect } from 'react';

function PricingSurvey({ surveyKey }) {
  useEffect(() => {
    import('@sensefolks/pricepoint');
  }, []);

  return (
    <sf-pricepoint 
      survey-key={surveyKey}
      completion-message="Thank you for helping us find the right price!">
    </sf-pricepoint>
  );
}

Vue 3

vue
<template>
  <sf-pricepoint 
    :survey-key="surveyKey"
    completion-message="Thank you!">
  </sf-pricepoint>
</template>

<script setup>
import { onMounted } from 'vue';

const props = defineProps(['surveyKey']);

onMounted(() => {
  import('@sensefolks/pricepoint');
});
</script>

For Angular, Next.js, and Svelte examples, see the Embedding Tutorial.

API Reference

Properties

Property Attribute Type Default Description
surveyKey survey-key string Required. UUID of the survey from your dashboard
completionMessage completion-message string 'Thank you for your response!' Message displayed after successful submission

Events

Event Detail Type Description
sfReady { surveyKey: string; productType: string; phase: string } Fired when the component has loaded its configuration and is ready
sfSubmit { surveyKey: string; phase: string; completionTimeSeconds: number } Fired on successful survey submission
sfError { surveyKey: string; errorType: 'load' | 'submit' | 'validation' | 'config'; errorMessage: string } Fired when an error occurs during load, validation, or submission
sfPhaseChange { phase: string } Fired when the survey phase changes (loading, vw, gg, complete, error)

Survey Configuration (Dashboard)

Set these options in your SenseFolks dashboard when creating the survey:

Option Type Description
currency string Currency code (USD, EUR, GBP, etc.)
priceType 'recurring' | 'non-recurring' One-time or subscription pricing
recurringBasis 'monthly' | 'annual' Billing frequency (if recurring)
respondentDetails array Optional fields to collect (name, email, etc.)

Response Data

Each submission automatically captures:

  • Price value entered by the respondent
  • Currency and pricing type
  • Respondent details (if configured)
  • Completion time in seconds
  • User agent, timezone, and screen resolution

CSS Custom Properties

Override these custom properties to theme the component:

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-card-bg #ffffff Card/container background
--sf-card-border #d1d5db Card/container border 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
--sf-spacing-scale 1 Spacing multiplier

Backward compatibility: Legacy aliases --sf-primary-color, --sf-text-color, --sf-background-color, and --sf-border-radius are still honored via CSS fallback chains.

css
/* Override theme tokens on the component */
sf-pricepoint {
  --sf-primary: #7c3aed;
  --sf-primary-hover: #6d28d9;
  --sf-card-radius: 12px;
  --sf-button-radius: 8px;
  --sf-font-family: 'Inter', system-ui, sans-serif;
  --sf-spacing-scale: 1.2;
}

CSS Parts

Style individual elements inside the shadow DOM using ::part():

Part Description
survey-containerOuter wrapper for the entire survey
headingPrimary heading elements
progress-indicatorPhase progress text
vw-containerVan Westendorp phase wrapper
question-containerWrapper for each question
question-textQuestion text paragraph
validation-errorsValidation error container
validation-errorIndividual validation error message
button-containerWrapper for action buttons
submit-buttonThe submit/next action button
error-containerWrapper for error state display
error-messageError message text
retry-buttonRetry button shown on error
loading-messageLoading state text
completion-stepCompletion/thank-you screen wrapper
hcaptcha-containerWrapper for the hCaptcha widget (when enabled)
gg-containerGabor-Granger phase wrapper
price-displayPrice value display span
radio-groupWrapper for radio button group
radio-optionA single radio option row
radio-option-selectedApplied to the selected radio option (additive with radio-option)
radio-labelRadio option label text
announcementsARIA live region for screen reader announcements
css
/* Container & Layout */
sf-pricepoint::part(survey-container) { }
sf-pricepoint::part(vw-container) { }
sf-pricepoint::part(gg-container) { }
sf-pricepoint::part(completion-step) { }
sf-pricepoint::part(hcaptcha-container) { }
sf-pricepoint::part(error-container) { }

/* Headings & Progress */
sf-pricepoint::part(heading) { }
sf-pricepoint::part(progress-indicator) { }

/* Questions */
sf-pricepoint::part(question-container) { }
sf-pricepoint::part(question-text) { }

/* Validation */
sf-pricepoint::part(validation-errors) { }
sf-pricepoint::part(validation-error) { }

/* Price Display */
sf-pricepoint::part(price-display) { }

/* Radio Options (GG phase) */
sf-pricepoint::part(radio-group) { }
sf-pricepoint::part(radio-option) { }
sf-pricepoint::part(radio-option-selected) { }
sf-pricepoint::part(radio-label) { }

/* Buttons */
sf-pricepoint::part(button-container) { }
sf-pricepoint::part(submit-button) { }
sf-pricepoint::part(retry-button) { }

/* Messages & States */
sf-pricepoint::part(error-message) { }
sf-pricepoint::part(loading-message) { }

/* Accessibility */
sf-pricepoint::part(announcements) { }

Styling Example

Combine custom properties and ::part() for full control:

css
/* Theme with CSS custom properties */
sf-pricepoint {
  --sf-primary: #7c3aed;
  --sf-primary-hover: #6d28d9;
  --sf-card-radius: 12px;
  --sf-button-radius: 8px;
}

/* Fine-tune individual elements with ::part() */
sf-pricepoint::part(survey-container) {
  font-family: 'Inter', system-ui, sans-serif;
  max-width: 480px;
}

sf-pricepoint::part(vw-container) {
  background: #f8fafc;
  border: 2px solid #e2e8f0;
  border-radius: 12px;
  padding: 16px;
}

sf-pricepoint::part(question-text) {
  font-size: 1.1rem;
  font-weight: 600;
  margin-bottom: 12px;
}

sf-pricepoint::part(price-display) {
  margin-top: 12px;
  padding: 12px;
  background: #f0fdf4;
  border-radius: 8px;
  color: #166534;
  font-weight: 500;
}

sf-pricepoint::part(submit-button) {
  background: linear-gradient(135deg, #6366f1, #8b5cf6);
  color: white;
  border: none;
  padding: 14px 28px;
  font-weight: 600;
  cursor: pointer;
}

/* Style selected radio option additively */
sf-pricepoint::part(radio-option-selected) {
  border-color: #7c3aed;
  background: #f5f3ff;
}

Accessibility

PricePoint is built with accessibility as a core feature:

  • Keyboard Navigation — Tab through fields, Enter to submit
  • Screen Readers — ARIA labels, live regions for price preview
  • Input Validation — Accessible error messages with aria-invalid
  • Focus Management — Visible focus indicators, logical tab order
  • High Contrast — Works with Windows High Contrast Mode
  • Reduced Motion — Respects prefers-reduced-motion

Troubleshooting

Price input not accepting values

  • The input accepts numeric values only (decimals allowed)
  • Negative values are not permitted
  • Check browser console for validation errors

Currency symbol not showing

  • Make sure the survey is configured with a valid currency in the dashboard
  • The currency is fetched from the server. Check network requests if it's missing

Recurring indicator not visible

  • Set priceType to 'recurring' in dashboard settings
  • Configure recurringBasis as 'monthly' or 'annual'

Browser Support

Browser Version Notes
Chrome88+Full support
Firefox85+Full support
Safari14+Full support
Edge88+Full support
IE11 SupportedES5 build with polyfills