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

@founderhq/journeys

v1.0.2

Published

Config-driven interactive journey/questionnaire engine for React

Readme

@founderhq/journeys

@founderhq/journeys is a config-driven React runtime for interactive onboarding and questionnaire flows.

What It Includes

  • journey runtime with forward/back navigation
  • conditional routing between steps
  • computed variables derived from prior answers
  • block-based informational pages
  • reusable styles shipped as styles.css
  • self-hosted static Inter weights and italics as the deterministic default across browsers and native WebViews, with Lora Variable available when selected

Install

pnpm add @founderhq/journeys

Native header layout

Journey and JourneyShell accept presentation="native" for an edge-to-edge native WebView. This removes the web header's extra 32px top padding while preserving all four device safe-area insets. The progress row reserves 80px on the right for the host app's close control. Keep that control within the device safe area. The default presentation="web" retains the existing web spacing.

The hosted native renderer selects this layout automatically for Swift/SPM, CocoaPods, Android Views/Compose, React Native, and Expo. Existing native SDKs receive this renderer update when a Journey is reopened; custom self-hosted renderers must also deploy the updated base package.

Basic Usage

import { Journey } from "@founderhq/journeys";
import "@founderhq/journeys/styles.css";

export function Example({
  config,
}: {
  config: import("@founderhq/journeys").JourneyConfig;
}) {
  return (
    <Journey
      apiKey="fhq_pk_..."
      journeyId="journey_123"
      config={config}
      storageKey="example-journey"
    />
  );
}

Importing styles.css also loads the bundled fonts; Journeys does not depend on a third-party font request. Font files are split by character set and style, so the browser downloads and caches only the faces used by the rendered Journey.

apiKey and journeyId are always required. Passing config only supplies local render data; the package still validates access with FounderHQ before rendering. Omit config to fetch the published config from FounderHQ:

<Journey apiKey="fhq_pk_..." journeyId="journey_123" />

The browser package is public client code, so access enforcement happens on FounderHQ's server APIs: config fetch, validation, and capture all require a valid scoped publishable key.

Host App Identity

Pass identity when the host app already knows the respondent. This identity is used only for safe FounderHQ Contact linking; it is not written into Journey answers.

<Journey
  apiKey="fhq_pk_..."
  journeyId="journey_123"
  identity={{
    externalId: user.id,
    email: user.email,
    phone: user.phone,
  }}
/>

When any non-empty identity field is provided, it is authoritative for Contact linking and overrides answer-derived email, phone, and external ID fields. The respondent's typed answers are still stored in the response snapshot.

Injecting Runtime Data (initialAnswers)

Pass initialAnswers to seed the answer state at first render. Useful when journey config references runtime data via templates — e.g. live pricing plans from StoreKit, RevenueCat, or your own API:

<Journey
  apiKey="fhq_pk_..."
  journeyId="abc-123"
  initialAnswers={{
    pricingPlans: [
      {
        id: "pro_monthly",
        name: "Pro",
        price: {
          amount: 29,
          currency: "USD",
          period: "month",
          trial: { days: 7 },
        },
        metadata: { stripePriceId: "price_xxx" },
      },
    ],
  }}
  onEvent={(event) => {
    if (event.type === "purchase_intent") {
      // event.plan.metadata.stripePriceId → kick off Stripe checkout
    }
  }}
/>

In the journey config, reference the injected value with a whole-value template:

{ type: "pricing_plans", props: { variable: "selectedPlan", plans: "${pricingPlans}" } }

Keys declared in computedVariables are ignored in initialAnswers; computed values always come from their formulas.

Injecting Runtime Options (initialOptions)

Pass initialOptions to override option lists at render time without storing large dynamic lists in the journey config. Keys are answer keys: top-level steps use step.variable ?? step.id, and single_select / multi_select blocks use props.variable.

<Journey
  apiKey="fhq_pk_..."
  journeyId="abc-123"
  initialOptions={{
    country: countries.map((country) => ({
      id: country.code,
      label: country.name,
    })),
  }}
/>

When a key is present in initialOptions, that list is used even if it is empty. When no key is present, the inspector-configured options remain the fallback.

Event Payloads

onEvent payloads keep user-provided answers and computed values separate. step_submit and navigate events include the rendered step config, but option arrays are omitted from those event configs so large initialOptions lists are not copied into every payload.

onEvent={(event) => {
  // Raw/persisted answers only.
  event.answers;

  // Derived values from config.computedVariables.
  event.computedVariables;
}}

Pricing Plan Templates

When a user selects a plan, the block stores a flattened snapshot in answers[variable]. Templates anywhere downstream can reach into it with dotted paths:

You're subscribing to ${selectedPlan.name} — ${selectedPlan.amount} ${selectedPlan.currency}/${selectedPlan.period}.
${selectedPlan.trialDays} day free trial. Was ${selectedPlan.originalAmount}.

Available paths: id, name, amount, currency, period, originalAmount, perUnitLabel, display, trialDays, introOffer, description, badge, icon, features, metadata.

Equality conditions on the variable continue to match the plan id ({ op: "equals", value: "pro_monthly" }).

Per-Plan Templates (currentPlan)

String fields anywhere inside a plan support templates, with currentPlan exposing this card's flattened plan fields (same shape as selectedPlan):

{
  id: "pro_monthly",
  name: "Pro",
  price: { amount: 29, currency: "USD", period: "month", trial: { days: 7 } },
  badge: "${currentPlan.trialDays} Days Free",
  subtext: "${currentPlan.trialDays} day free trial, then ${currentPlan.amount} ${currentPlan.currency}/${currentPlan.period}",
}

subtext is rendered as a small line under the price. Trial and intro-offer lines are no longer auto-rendered — set subtext explicitly if you want them.

Optional Discount Codes

Discount codes are opened from a normal button action, so the Journey author controls whether the UI appears and where the trigger lives:

{
  type: "button",
  props: {
    label: "Have a coupon?",
    action: {
      type: "open_discount_code",
      variable: "pricingDiscount",
      planVariable: "selectedPlan",
    },
  },
}

Pass onDiscountCodeApply to validate and reprice in the consumer app:

<Journey
  apiKey="fhq_pk_..."
  journeyId="abc-123"
  config={config}
  onDiscountCodeApply={async ({ code, plan }) => {
    const result = await validateCoupon(code, plan?.id);
    return result.valid
      ? {
          valid: true,
          code,
          planId: plan?.id,
          message: "Discount applied",
          pricing: {
            total: result.total,
            currency: result.currency,
            originalAmount: result.originalTotal,
          },
          metadata: result.metadata,
        }
      : { valid: false, reason: result.reason };
  }}
/>

Set pricing_plans.props.discountVariable to let cards replace the visible non-strike price from pricing.display, pricing.amount, or pricing.total. Set purchase.action.discountVariable to include the full applied discount answer in purchase_intent. Journeys never calculates billing amounts or provider rules; those belong in the consumer callback and metadata.

Host URL Handling

Authored link buttons use normal browser navigation by default. Applications that own routing can intercept every link, including external links:

<Journey
  apiKey="fhq_pk_..."
  journeyId="journey_123"
  onOpenURL={(url, external) => routeFromHost(url, { external })}
/>

The native SDKs use this hook internally so link actions are handed to the Expo/React Native, iOS, or Android host instead of opening another WebView.

Capture Options

Capture is enabled by default after access validation succeeds. Pass capture={false} to opt out, or pass capture options to add custom context:

<Journey
  apiKey="fhq_pk_..."
  journeyId="journey_123"
  capture={{ context: { app: { name: "web" } } }}
/>

apiKey, journeyId, and the FounderHQ API URL are inherited from the root props and are not repeated inside capture.

Host integrations may supply revisionId and clientSessionId in capture options. Queued events retain the identifiers and context from their original presentation, including after restart or revision pruning. Retryable failures retain the queue; custom capture transports may return a boolean or { ok: boolean, retryAfterMs?: number }.

Native preparation is managed by the platform SDK's persistent host. Internally, JourneyProvider active={false} renders assets and layout while suppressing experience activity until presentation. See the mobile preparation guides.

Capture events are batched and persisted locally with a TTL. The runtime flushes immediately for completion events, and browser embeds also try an exit-safe sendBeacon/keepalive flush on visibilitychange and pagehide. Beacon delivery is treated as best-effort; FounderHQ uses event IDs server-side so a later retry can safely duplicate a queued event.

Config Shape

A journey config is centered around:

  • arcs Logical step groups and navigation order.
  • steps Individual screens such as single_select, multi_select, input, slider, and info_page.
  • computedVariables Derived values evaluated from the current answer set.

Computed variables and ${...} text templates use a restricted, side-effect-free expression language. It supports property and array reads, arithmetic, comparisons, boolean/nullish operators, ternaries, template strings, common string/array helpers, and allowlisted built-ins such as Math. It does not execute arbitrary JavaScript or functions supplied through journey data.

Development

pnpm --filter @founderhq/journeys dev
pnpm --filter @founderhq/journeys build

The build emits ESM, CJS, type declarations, and dist/styles.css.

Mobile SDKs

Journeys uses the same authored block runtime on web and native platforms:

  • Expo and bare React Native: @founderhq/journeys-react-native
  • SwiftUI and UIKit: FounderHQJourneys
  • Android Views: founderhq-journeys
  • Jetpack Compose: founderhq-journeys-compose

See the README in each platform package for installation and usage. The shared architecture and security model are documented in docs/journeys-mobile-sdk-plan.md in the FounderHQ repository.

Release notes

1.0.2

  • Icons show again in the timeline, callout, card, metric, table and before-after blocks. From 0.6.0 to 1.0.1 they rendered with no stroke and were invisible.
  • A glow spot (blobs) with a percentage size stays round: the size is a share of the screen's longer side, as the default 60vmax. Before, "60%" was 60% of the width and of the height, which cut the glow off at its sides on a phone.

1.0.1

  • The package now ships its MIT licence: a LICENSE file and the license field.
  • Updated dependencies, among them motion 14 and @lottiefiles/dotlottie-react 0.21.

0.8.1

Native Android hosts can provide measured safe-area insets. The renderer keeps progress, navigation, and overlays clear of system bars on older WebViews while preserving edge-to-edge backgrounds and the existing iOS CSS inset behavior.

0.8.0

  • Journey API requests and renderer pages use https://app.getfounderhq.com. The analytics ingest host is reserved for Events.