PricePoint

PricePoint runs one of two pricing methods inside your product. Use Van Westendorp to research perceived price boundaries or Gabor-Granger to test stated purchase intent across a price ladder.

@sensefolks/pricepointWCAG 2.1 AA<10KB gzipped

Choose a Pricing Method

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?

The intersections of the four curves estimate acceptable-price boundaries and reference points. Treat them as research evidence alongside costs, positioning, and observed buying behaviour. For a deeper walkthrough, see the Van Westendorp pricing guide.

Gabor-Granger

Gabor-Granger uses a ladder of candidate prices and asks whether people would buy at each point. The answers estimate a stated-demand curve.

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

Answers and phase progress stay only in the mounted component. Reloading the page starts a new blank response.

Survey Phases

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

PhaseWhat happens
loadingComponent fetches the survey configuration
vwRespondent answers the four Van Westendorp price sensitivity questions
ggRespondent evaluates a series of price points on a radio scale
completeSurvey submitted. The completion screen shows your completionMessage
errorSomething went wrong during load or submission. The error message is displayed

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

When PricePoint Is the Right Survey

Use PricePoint when you have a defined offer and need structured evidence for a pricing decision.

  • Pricing pages — Understand willingness to pay before launch
  • Upgrade research — Compare price sensitivity near an upgrade decision
  • Trial research — Study stated purchase intent before setting a paid offer
  • Cancellation flows — Learn the price sensitivity of churning users
  • New feature launches — Research the acceptable range for a premium feature
  • Market expansion — Test pricing in new markets

Installation

CDN (Recommended)

Add directly to your HTML:

html
<script type="module" src="https://unpkg.com/@sensefolks/[email protected]/dist/sf-pricepoint/sf-pricepoint.esm.js"></script>

NPM

For projects with a build system:

bash
npm install @sensefolks/pricepoint

Embed PricePoint

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, React + Next.js, Vue + Nuxt, Svelte, and Astro examples, see theEmbedding Tutorial.

API Reference

Properties

PropertyAttributeTypeDefaultDescription
surveyKeysurvey-keystringRequired. UUID of the survey from your dashboard
completionMessagecompletion-messagestring'Thank you for your response!'Message displayed after successful submission
thankYouMessagethank-you-messagestringOverrides the saved survey thank-you message
sessionDataRecord<string, string | number | boolean>{}Complete typed Session Data matching every field in the schema defined during survey creation

Events

EventDetail TypeDescription
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:

OptionTypeDescription
currencystringCurrency code (USD, EUR, GBP, etc.)
priceType'recurring' | 'non-recurring'One-time or subscription pricing
recurringBasis'monthly' | 'annual'Billing frequency (if recurring)
respondentDetailsarrayOptional 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:

PropertyDefaultDescription
--sf-primary#005fccPrimary accent color
--sf-primary-hover#0047a3Primary color hover state
--sf-text-primary#111827Primary text color
--sf-text-secondary#6b7280Secondary/muted text color
--sf-error-color#dc2626Error state color
--sf-card-bg#ffffffCard/container background
--sf-card-border#d1d5dbCard/container border color
--sf-card-radius8pxCard/container border radius
--sf-button-radius6pxButton border radius
--sf-transition150ms easeDefault transition timing
--sf-progress-bg#e5e7ebProgress track color
--sf-progress-fill#005fccProgress fill color
--sf-font-familysystem-ui, sans-serifFont family
--sf-spacing-scale1Spacing 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-button-text: #ffffff;
  --sf-error-text: #991b1b;
  --sf-error-bg: #fef2f2;
  --sf-error-border: #fecaca;
  --sf-card-radius: 12px;
  --sf-button-radius: 8px;
  --sf-progress-bg: #ede9fe;
  --sf-progress-fill: #7c3aed;
  --sf-font-family: 'Inter', system-ui, sans-serif;
  --sf-spacing-scale: 1.2;
}

CSS Parts

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

PartDescription
survey-containerOuter wrapper for the entire survey
headingPrimary heading elements
progress-indicatorPhase progress text
progress-labelProgress label text
progress-textCompatibility alias for progress label text
progress-trackContinuous progress bar track
progress-barCompatibility alias for the progress bar track
progress-fillThe filled portion of the progress bar
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
loading-messageLoading state text
completion-stepCompletion/thank-you screen wrapper
hcaptcha-containerWrapper for the hCaptcha widget (when enabled)
brandingSenseFolks branding wrapper
branding-linkSenseFolks branding link
branding-logoSenseFolks branding logo
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-inputSurvey Field radio input
radio-labelRadio option label text
survey-fields-containerSurvey Fields section wrapper
form-containerForm container compatibility alias
survey-fields-headingSurvey Fields section heading
form-fieldIndividual Survey Field wrapper
form-labelSurvey Field label
required-indicatorRequired field marker
survey-field-controlSurvey Field input wrapper
form-inputText, email, or number Survey Field input
form-selectDropdown Survey Field input
field-errorSurvey Field validation error
checkbox-groupCheckbox Survey Field group
checkbox-optionCheckbox Survey Field option wrapper
checkbox-inputCheckbox Survey Field input
checkbox-labelCheckbox Survey Field label
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) { }
sf-pricepoint::part(troubleshooting-link) { }

/* Headings & Progress */
sf-pricepoint::part(heading) { }
sf-pricepoint::part(progress-indicator) { }
sf-pricepoint::part(progress-label) { }
sf-pricepoint::part(progress-text) { }
sf-pricepoint::part(progress-track) { }
sf-pricepoint::part(progress-bar) { }
sf-pricepoint::part(progress-fill) { }

/* 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-input) { }
sf-pricepoint::part(radio-label) { }

/* Survey Fields */
sf-pricepoint::part(survey-fields-container) { }
sf-pricepoint::part(form-container) { }
sf-pricepoint::part(survey-fields-heading) { }
sf-pricepoint::part(form-field) { }
sf-pricepoint::part(form-label) { }
sf-pricepoint::part(required-indicator) { }
sf-pricepoint::part(survey-field-control) { }
sf-pricepoint::part(form-input) { }
sf-pricepoint::part(form-select) { }
sf-pricepoint::part(field-error) { }
sf-pricepoint::part(checkbox-group) { }
sf-pricepoint::part(checkbox-option) { }
sf-pricepoint::part(checkbox-input) { }
sf-pricepoint::part(checkbox-label) { }

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

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

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

/* Branding */
sf-pricepoint::part(branding) { }
sf-pricepoint::part(branding-link) { }
sf-pricepoint::part(branding-logo) { }

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: 1px solid var(--color__grey--450);
  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: 400;
  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

BrowserVersionNotes
Chrome88+Full support
Firefox85+Full support
Safari14+Full support
Edge88+Full support
IE11Not supportedRequires modern Web Component APIs, fetch, and AbortController