PricePoint

PricePoint is an embeddable pricing survey for Van Westendorp and Gabor-Granger research.

@sensefolks/pricepointWCAG 2.1 AA<10KB gzipped

Choose a Pricing Method

The pricing methods answer different questions:

Van Westendorp

Van Westendorp uses four price questions to estimate perceived price boundaries.

  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?

Curve intersections estimate an acceptable price range. Compare it with costs, positioning, and actual purchases. See the Van Westendorp guide.

Gabor-Granger

Gabor-Granger asks whether respondents would buy at candidate prices to estimate stated demand.

A price ladder defines the candidate prices tested.

Refreshing the page clears unfinished answers.

Survey Phases

The sfPhaseChange event reports the active survey phase:

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

The survey configuration determines which research phases run.

When should I use PricePoint?

Use PricePoint to research pricing for a defined product or service.

  • 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

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

NPM

bash
npm install @sensefolks/pricepoint

How do I embed PricePoint?

HTML

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>
  );
}

See the embedding tutorial for Next.js, Vue, Nuxt, Angular, Svelte, Astro, and TypeScript setup.

API Reference

Properties

PropertyAttributeTypeDefaultDescription
surveyKeysurvey-keystring—Required. UUID of the survey from your dashboard
completionMessagecompletion-messagestring'Thank you for your response!'Message displayed after successful submission
thankYouMessagethank-you-messagestring—Overrides the saved survey thank-you message
sessionData—Record<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)

Pricing configuration includes:

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

Submissions include:

  • 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

Set theme variables on 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

Legacy aliases --sf-primary-color, --sf-text-color, --sf-background-color, and --sf-border-radius remain supported.

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

Target exposed elements with ::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 theme variables and CSS Parts:

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;
}

Completion and validation styles

CSS PartElement
success-messageSubmission confirmation
survey-field-error-messageRespondent-field validation feedback
completion-headingCompletion message heading
price-inputTyped price field
price-previewFormatted price preview
text-inputText input
validation-messagePrice validation feedback

Use --sf-success-bg, --sf-success-border, and --sf-success-text for the confirmation background, border, and text.

Accessibility

PricePoint includes:

  • 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