PricePoint
PricePoint runs structured pricing research on your site. Use Van Westendorp or Gabor-Granger to estimate acceptable price ranges and willingness to pay.
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."
- Too Cheap — At what price would you question the quality?
- Bargain — At what price would it be a great deal?
- Expensive — At what price does it start to seem expensive?
- 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:
<!-- Modern browsers (ES modules) -->
<script type="module" src="https://unpkg.com/@sensefolks/[email protected]/dist/sf-pricepoint/sf-pricepoint.esm.js"></script>
<!-- Legacy browsers -->
<script nomodule src="https://unpkg.com/@sensefolks/[email protected]/dist/sf-pricepoint/sf-pricepoint.js"></script> NPM
For projects with a build system:
npm install @sensefolks/pricepoint Framework Integration
HTML / Vanilla JS
<sf-pricepoint
survey-key="your-survey-uuid"
completion-message="Thank you for your feedback!">
</sf-pricepoint> React
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
<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 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 |
thankYouMessage | thank-you-message | string | — | Overrides the saved survey thank-you message |
sessionData | — | Record<string, string | number | boolean> | {} | Typed Session Data matching the schema declared in Response Context |
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-progress-bg | #e5e7eb | Progress track color |
--sf-progress-fill | #005fcc | Progress fill color |
--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.
/* Override theme tokens on the component */
sf-pricepoint {
--sf-primary: #7c3aed;
--sf-primary-hover: #6d28d9;
--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():
| Part | Description |
|---|---|
survey-container | Outer wrapper for the entire survey |
heading | Primary heading elements |
progress-indicator | Phase progress text |
progress-label | Progress label text |
progress-text | Compatibility alias for progress label text |
progress-track | Continuous progress bar track |
progress-bar | Compatibility alias for the progress bar track |
progress-fill | The 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 |
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, or 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 |
/* 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) { }
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) { }
sf-pricepoint::part(retry-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:
/* 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
priceTypeto 'recurring' in dashboard settings -
Configure
recurringBasisas 'monthly' or 'annual'
Browser Support
| Browser | Version | Notes |
|---|---|---|
| Chrome | 88+ | Full support |
| Firefox | 85+ | Full support |
| Safari | 14+ | Full support |
| Edge | 88+ | Full support |
| IE11 | Supported | ES5 build with polyfills |