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

v5.1.1

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, Newton–Miller–Smith, and direct price testing.

Features

  • 💰 Van Westendorp price sensitivity plus Newton–Miller–Smith purchase intent
  • 🔄 Server-assigned VW + NMS or independent GG-derived direct price test
  • 💱 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

Required respondent fields are indicated and keep submission disabled until valid without showing required-field errors. Email format feedback starts after any non-empty input and the respondent pauses typing for 1.5 seconds.

Research flow and deployment

Deploy compatible embed-request-api and embed-response-api services before loading version 5.1.0. Configuration loading is followed by GET /v1/embed/pricePoint/:surveyKey/study; a missing or invalid study assignment shows a retry action instead of silently changing the research method. Existing clients can continue using the legacy ladder endpoint during rollout. Legacy surveys with currencyMode: "auto" receive the explicit PRICEPOINT_FIXED_CURRENCY_REQUIRED response and retain VW-only submission; adaptive research requires a new survey with a fixed currency. No other study error enables fallback.

Discovery respondents answer four VW questions followed by two NMS questions at their own bargain and expensive prices. Later respondents are assigned by the server to either the same VW + NMS flow or a GG-only group. The GG-only group evaluates one randomly assigned price without earlier VW or NMS questions. This is a monadic first-price test derived from GG, not a sequential GG ladder or a respondent-level willingness-to-pay estimate. Candidate grids and batch/validation policy are controlled by the server.

VW inputs start blank and accept decimal amounts of at least 1, including amounts above 100. Legacy priceRange metadata does not constrain them. Non-finite values and amounts beyond JavaScript's safe numeric magnitude are rejected as technical validation; there is no outlier detection, flagging, or filtering. The four answers must be strictly increasing. Currency and billing terms come from the saved survey configuration.

NMS and GG reuse the same five-point purchase-intent scale. Responses represent stated intent rather than observed purchases. The component does not display the product description; keep the offering and purchase context visible on the host page consistently for both groups.

Pricing answers are sent only on final submission. Assignment and answers exist only in the component's memory, and reload begins a blank response with a new assignment. Failed final submissions retry using the same signed assignment; the response service deduplicates that assignment. No partial-answer save or restore is performed. Existing answer-free attempt telemetry is unchanged. Respondent fields and optional bot verification appear only on the final NMS or GG step.

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 | {} | Every schema field is required when a schema exists; schema-less surveys accept safe flat string, finite number, and boolean values |

For schema-backed surveys, assign the complete sessionData object before setting surveyKey. Reassign a new object whenever Session Data values change; the component refetches after a meaningful reassignment.

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 → nms → complete, or loading → gg → complete) |

Event Payload Shapes

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

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

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

interface SfPhaseChangeEventDetail {
  phase: string; // 'loading' | 'vw' | 'nms' | '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
  }
}

The study response contains { protocol: 'adaptive-v1', assignment, assignmentToken }. The assignment records its ID, study version, phase, cohort, batch version, candidate grid, assigned price, and allocation probabilities. The final responseData.study carries the signed assignment unchanged. A VW + NMS response includes vanWestendorp and nms: { bargain: { price, purchaseIntent }, expensive: { price, purchaseIntent } }. A GG-only response includes one gaborGranger.responses entry and priceLadderVersion equal to the assignment's batch version, with no VW or NMS answers.

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 | Compatibility hook for primary body text | | progress | Top progress header wrapper | | progress-indicator | Compatibility alias for the progress header | | progress-label | "Step 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 | | price-input | Blank decimal price input; text-input compatibility alias | | text-input | Compatibility part on the open price input | | price-preview | Currency display or minimum-price guidance | | validation-message | Price input validation feedback | | 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 | | survey-field-error-message | Inline email feedback shown after a 1.5-second idle delay | | troubleshooting-link | Troubleshooting link shown with integration errors | | loading-message | Loading state text | | completion-step | Completion/thank-you screen wrapper | | completion-heading | Compatibility hook for the completion message | | success-message | Green completion status message | | 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 | Shared NMS/GG purchase-intent 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 introductory text | | 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 | Error message text color | | --sf-error-bg | #fef2f2 | Error state background | | --sf-error-border | #fecaca | Error state border color | | --sf-success-text | #166534 | Success message text color | | --sf-success-bg | #f0fdf4 | Success state background | | --sf-success-border | #bbf7d0 | Success state 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)
  • Associated labels, decimal keyboard hints, and aria-invalid / aria-describedby feedback on price inputs
  • ARIA role="radiogroup" on NMS and GG purchase intent scales
  • 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-success-text",
    "--sf-success-bg",
    "--sf-success-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",
    "price-input",
    "text-input",
    "price-preview",
    "validation-message",
    "validation-errors",
    "validation-error",
    "button-container",
    "submit-button",
    "error-container",
    "error-message",
    "survey-field-error-message",
    "troubleshooting-link",
    "loading-message",
    "completion-step",
    "completion-heading",
    "success-message",
    "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 | 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