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

@sentientui/react

v0.15.0

Published

React SDK for [SentientUI](https://sentient-ui.com) — the adaptive ladder. Declare bounded variations (styles, content, arrangement order); a persona-keyed optimizer on the hosted API learns which one converts best for each visitor type. Visit 1 learns; V

Readme

@sentientui/react

React SDK for SentientUI — the adaptive ladder. Declare bounded variations (styles, content, arrangement order); a persona-keyed optimizer on the hosted API learns which one converts best for each visitor type. Visit 1 learns; Visit 2 converts.

Installation

npm install @sentientui/react     # or: npx @sentientui/cli init (installs + writes .env.local + an example component)

2-minute start (no account)

npx @sentientui/cli init
# then follow its printed instructions: wrap your app with <AdaptiveRoot> (or
# <AdaptiveProvider>) and mount the generated components/adaptive-example.tsx
npm run dev
# open http://localhost:3000?sentient_persona=buyer — the example adapts

The CLI installs the package, writes .env.local, and generates an example component — it does not edit your layout, so nothing adapts until you do the wrap-and-mount step it prints.

With no API key the SDK runs in keyless local mode: deterministic simulated decisions, zero network. Add a pk_… key from sentient-ui.com to learn from real traffic:

# .env.local
NEXT_PUBLIC_SENTIENT_API_KEY=pk_your_key

Setup

Wrap your root layout (Next App Router — server component):

// app/layout.tsx
import { AdaptiveRoot } from '@sentientui/react/next';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <AdaptiveRoot
          apiKey={process.env.NEXT_PUBLIC_SENTIENT_API_KEY!}
          appOrigin={process.env.NEXT_PUBLIC_APP_URL!}
          context="saas"
        >
          {children}
        </AdaptiveRoot>
      </body>
    </html>
  );
}

suppressHydrationWarning on <html> is required: an inline script (rendered by AdaptiveRoot as its first child) sets the persona attributes before first paint, exactly like the next-themes pattern. Other React apps (Vite, CRA, Remix, Pages Router) use <AdaptiveProvider> from @sentientui/react with the same props — plus <SentientPersonaScript /> rendered in your document <head> if you want the Rung-1a persona attributes: only that inline script (which AdaptiveRoot includes for you) writes data-sentient-persona / data-sentient-confidence in hosted mode. Without it, an AdaptiveProvider app gets everything except the persona CSS hooks. (Keyless local mode writes the attributes itself, so the demo works either way — don't let that mask a missing script when you add a real key.)

The adaptive ladder

Rung 0 — Observe

Install is the integration. The dashboard immediately shows who is arriving: persona mix, device and traffic-source segments, engagement signals. The "Suggested next step" card tells you which rung to climb next, with a copy-pasteable snippet.

Rung 1 — Style (CSS only)

1a. Persona attributes — zero declaration. The SDK sets on <html>:

data-sentient-persona   = buyer | researcher | deal_seeker | browser | unknown
data-sentient-confidence = low | medium | high

Write plain CSS against them (the canonical block — safe defaults for every persona):

/* Show each visitor type what it cares about. Confidence-gate bold treatments. */
html[data-sentient-persona='buyer'] .cta-primary { font-weight: 700; }
html[data-sentient-persona='researcher'] .spec-details { display: block; }
html[data-sentient-persona='deal_seeker'] .discount-banner { display: block; }
html[data-sentient-persona='browser'] .newsletter-nudge { display: block; }
html[data-sentient-confidence='low'] .discount-banner,
html[data-sentient-confidence='low'] .newsletter-nudge { display: none; }

Force any persona in dev: ?sentient_persona=deal_seeker.

1b. Adaptive tokens — learned. Declare a bounded design space; the optimizer picks per visitor type; values arrive as element-scoped data-* props (SSR-serialized — zero flicker):

import { useAdaptiveTokens } from '@sentientui/react';

function Hero() {
  const t = useAdaptiveTokens('hero', {
    tone:   ['calm', 'urgent'],   // first value = baseline (what you show today)
    motion: ['none', 'pulse'],
  });
  return <section {...t.props} className="hero">…</section>;
  // t.props  → { 'data-tone': 'urgent', 'data-motion': 'pulse' }
  // t.tokens → { tone: 'urgent', motion: 'pulse' }
}
.hero[data-tone='urgent'] .cta { font-weight: 700; }

/* Animation values are enum values whose CSS you own — always respect reduced motion: */
.hero[data-motion='pulse'] .cta { animation: pulse 2s infinite; }
@media (prefers-reduced-motion: reduce) {
  .hero[data-motion='pulse'] .cta { animation: none; }
}

Constraints: 1–4 dims, 2–6 values each, combinations ≤ 64. Enum values only — no arbitrary CSS. Optional { goal: 'buy_click' } third argument credits conversions to this element directly.

Rung 2 — Swap (alternate content)

import { useAdaptive } from '@sentientui/react';

function BuyBox() {
  const { variant, value, bind, fireGoal } = useAdaptive('buy-box', {
    variants: { calm: <CalmBuyBox />, urgent: <UrgentBuyBox /> },  // first key = baseline
    goal: 'buy_click',                                              // required
  });
  return <div {...bind}>{value}</div>;
}

bind (ref + data attributes) wires exposure tracking, goal listeners, and engagement signals — attach it or the slot cannot learn (dev mode warns loudly if you don't). <Adaptive> is the wrapper form of the same rung; <AdaptiveText> swaps dashboard-managed text.

Rung 3 — Reorder (structure)

Region-scope — declared arrangements of keyed children only, never free permutation:

import { AdaptiveGroup } from '@sentientui/react';

<AdaptiveGroup
  id="pricing-area"
  arrangements={{
    standard:     ['plans', 'faq', 'social'],   // first key = baseline
    social_first: ['social', 'plans', 'faq'],
  }}
  goal="plan_selected"
>
  <PlanGrid key="plans" />
  <Faq key="faq" />
  <Testimonials key="social" />
</AdaptiveGroup>

Page-scope — declare sections on AdaptiveRoot and read the order with useLayoutOrder() (see the API reference below). Use AdaptiveGroup for a region, sections for the page.

Every decision is locked for the session — visitors never see the page reshuffle under them.

API

<AdaptiveRoot> (Next.js App Router — server component)

Imported from @sentientui/react/next.

| Prop | Type | Description | |------|------|-------------| | components | Array<{ id: string; variantIds: string[] }> | Components to preload server-side. id must match <Adaptive id="…">. | | sections | string[] (optional) | Page section IDs in default order. When provided, a single POST /v1/decide returns both layout order and assignments; useLayoutOrder() becomes available. | | apiKey | string | pk_… key — used by both the browser SDK and server-side SSR requests. | | appOrigin | string (default http://localhost:3001) | Your app origin (e.g. https://yourapp.com). Must be on the project's allowed-origins list. Always set in production. | | context | 'landing' \| 'ecommerce' \| 'saas' \| 'marketplace' | Type of product. Used for segment weighting and analytics grouping. | | consent | boolean (default true) | Set false to skip SDK init (no cookies, no events). Flip to true after the visitor accepts. | | respectDoNotTrack | boolean (default true) | Honor the browser's Do Not Track signal. When on and DNT is enabled, the SDK sets no cookies and sends no tracking data (overriding consent: true), and grantConsent() won't re-enable it. Set false to make your own consent gate authoritative. | | ssrFallback | 'first' \| 'none' (default 'first') | What to render in SSR HTML for components not in components. 'first' is safe for SEO. | | timeoutMs | number (default 1000) | Server-side fetch timeout before falling back to the first variant. Typical decide is well under 150 ms; the full budget is only reached on a cold start or an API distant from your SSR host. | | debug | boolean | Log assignment and event activity to the console. |

<AdaptiveProvider> (any React app)

Accepts the same apiKey, context, consent, ssrFallback, debug props as <AdaptiveRoot>, plus onAssignment (not available on <AdaptiveRoot> — function props can't cross the RSC boundary) and:

| Prop | Type | Description | |------|------|-------------| | initialAssignments | Record<string, string> | SSR-preloaded assignments (e.g. from loadAdaptiveAssignments in getServerSideProps). | | sessionSegment | string | Segment from SSR (device:source). Must match the value used in loadAdaptiveAssignments. | | initialLayoutOrder | string[] \| null | Preloaded section order from loadAdaptiveDecision (for Pages-Router-style SSR with sections). |

<Adaptive>

| Prop | Type | Description | |------|------|-------------| | id | string | Unique component identifier within your project. | | variants | Record<string, ReactNode> | Map of variant ID → content. Any two or more keys; the bandit explores them all. | | goal | string \| GoalConfig | Conversion goal. A string is a click-goal label; an object is an explicit GoalConfig. | | agentDataByVariant | Record<string, unknown> (optional) | Structured data keyed by variant ID that AI agents can consume via GET /v1/agent/layout. Only the assigned variant's entry is sent to the server. Preferred over agentData. | | agentData | unknown (optional, deprecated) | Deprecated in favour of agentDataByVariant. Single value stored once regardless of which variant is shown; kept for backward compatibility. | | clientOnly | boolean | Render nothing on the server; resolve on the client only. Use for cookie-dependent slots. |

Goal types

// Any click inside the variant (button, a, role=button). The string is the analytics label.
goal="signup_click"

// Click only on elements matching a CSS selector inside the variant.
goal={{ type: 'click', selector: 'button.cta' }}

// 80% of the component visible in the viewport (IntersectionObserver, 0–1 scale).
goal={{ type: 'scroll_depth', threshold: 0.8 }}

// A <form> inside the variant fires submit.
goal={{ type: 'form_submit' }}

// Composite — all sub-goals must fire (in any order) before the reward is recorded.
goal={{
  type: 'composite',
  all: [
    { type: 'scroll_depth', threshold: 0.8 },
    { type: 'click' },
  ],
}}

// Weighted composite — each step fires immediately as it completes, with a fractional reward.
// Use for multi-step funnels where partial completion still signals quality.
// Steps are independent: step 2 can fire before step 1.
goal={{
  type: 'weighted_composite',
  steps: [
    { goal: { type: 'scroll_depth', threshold: 0.5 }, name: 'viewed_pricing', weight: 0.2 },
    { goal: { type: 'click' },                         name: 'clicked_cta',   weight: 0.4 },
    { goal: { type: 'form_submit' },                   name: 'signed_up',     weight: 1.0 },
  ],
}}

Each goal fires at most once per variant mount. WeightedCompositeGoal fires each step's reward independently; CompositeGoal waits for all sub-goals and fires reward 1.0 once.

<AdaptiveText>

Lightweight text-only variant (no wrapper element, no automatic goal wiring). Useful when you publish text variants from the dashboard WYSIWYG.

import { AdaptiveText } from '@sentientui/react';

<h1>
  <AdaptiveText id="hero_headline" default="Ship faster with SentientUI" />
</h1>

useAdaptiveTokens(id, dims, opts?)

function useAdaptiveTokens(
  id: string,
  dims: Record<string, readonly string[]>,   // 1–4 dims × 2–6 values; first value = baseline
  opts?: { goal?: string | GoalConfig },
): { tokens: Record<string, string>; props: Record<string, string> };  // props keys: `data-${dim}`

Spread props onto the element you style. Values serialize through SSR markup — no flicker, no hydration mismatch. Renaming a value is a cold start for that value's learning.

useAdaptive(id, config)

function useAdaptive<T>(
  id: string,
  config: { variants: Record<string, T>; goal: string | GoalConfig },  // first key = baseline
): {
  variant: string;
  value: T;
  bind: { ref: (el: HTMLElement | null) => void; 'data-sentient-id': string; 'data-sentient-variant': string };
  fireGoal: (goalType?: string, opts?: ComponentGoalOptions) => void;
};

Headless Swap-rung hook. goal is required and bind must be attached to a rendered element — learning needs both. Supersedes useAssignment.

<AdaptiveGroup>

| Prop | Type | Description | |------|------|-------------| | id | string | Unique group identifier. | | arrangements | Record<string, string[]> | Arrangement id → ordered child keys. First entry = baseline. | | baseline | string (optional) | Explicit baseline arrangement id (defaults to the first key). | | goal | string \| GoalConfig (optional) | Conversion goal credited to this group. | | children | keyed ReactNodes | Every key referenced by an arrangement must exist. |

Reorders via React keys (DOM moves, state preserved). Declared orders only.

Persona attributes (Rung 1a)

AdaptiveRoot renders an inline script (its first child) that sets data-sentient-persona and data-sentient-confidence on <html> before first paint; the client SDK adopts those values and never rewrites them mid-session. Requirements: suppressHydrationWarning on <html>, AdaptiveRoot at the top of the tree. Override in dev with ?sentient_persona=<persona>.

useAssignment(componentId, variantIds?)

Deprecated — use useAdaptive instead; it carries the goal and exposure wiring useAssignment leaves to you. Kept for backward compatibility.

Lower-level hook when you need the variant ID inside your own render logic (e.g. full-page layout tests where <Adaptive>'s wrapper <div> would break a flex/grid layout).

import { useAssignment, useSentient } from '@sentientui/react';

function Hero() {
  const client = useSentient();
  const { variantId, isLoading } = useAssignment('hero_cta', ['control', 'variant_a']);

  if (isLoading) return <Skeleton />;
  return variantId === 'variant_a' ? <AccentHero /> : <DefaultHero />;
}

When using useAssignment you are responsible for firing the goal — <Adaptive>'s automatic impression and goal tracking does not apply. Use useAdaptiveGoal (recommended) or client?.goal(name) from useSentient().

useAdaptiveGoal(componentId)

Returns a fireGoal(goalType, opts?) callback that records a conversion attributed to the variant currently served for componentId — so it shows up in the per-variant CVR funnel with no manual variantId/projectId plumbing. Use it for imperative handlers (click, form submit, custom events) when you're not using the declarative <Adaptive goal={…}> prop.

import { useAdaptiveGoal } from '@sentientui/react';

function HeroContact({ method }: { method: string }) {
  const fireContact = useAdaptiveGoal('hero_headline');
  return (
    <a href="tel:+1..." onClick={() => fireContact('hero_contact', { metadata: { method } })}>
      Call us
    </a>
  );
}

The served variant is resolved from the SDK's assignment cache (the same one <Adaptive id="hero_headline"> populates), so render that component before firing. opts accepts reward (0–1, default 1) and metadata. This replaces hand-rolled helpers that call client.track({ eventType: 'goal_achieved', componentId, variantId, … }) — you no longer need to pass or track the variant yourself.

useLayoutOrder()

Returns the resolved persona-specific section order, or null when not configured / below confidence threshold. Always fall back to your default order.

import { useLayoutOrder } from '@sentientui/react';

function Page() {
  const order = useLayoutOrder();
  const defaultOrder = ['hero', 'pricing', 'features', 'social_proof'];
  const sections: Record<string, React.ReactNode> = {
    hero: <Hero />, pricing: <Pricing />, features: <Features />, social_proof: <SocialProof />,
  };
  return <main>{(order ?? defaultOrder).map((id) => <Fragment key={id}>{sections[id]}</Fragment>)}</main>;
}

SSR for Pages Router / custom SSR

import { loadAdaptiveAssignments } from '@sentientui/react/server';

export async function getServerSideProps({ req }) {
  const assignments = await loadAdaptiveAssignments(
    [{ id: 'hero_cta', variantIds: ['control', 'variant_a'] }],
    {
      cookies: req.cookies,
      apiKey:  process.env.NEXT_PUBLIC_SENTIENT_API_KEY!,
      baseUrl: 'https://api.sentient-ui.com/v1',
      origin:  process.env.NEXT_PUBLIC_APP_URL,
    },
  );
  return { props: { initialAssignments: assignments } };
}

Pass the result as initialAssignments on <AdaptiveProvider> in _app.tsx. For pages with a sections layout, use loadAdaptiveDecision (same options shape, plus sections: string[]) — its return value carries both assignments and layoutOrder.

Forwarding to your own analytics

<AdaptiveProvider
  apiKey={process.env.NEXT_PUBLIC_SENTIENT_API_KEY!}
  context="saas"
  onAssignment={(componentId, variantId) => {
    posthog.capture('$feature_flag_called', { $feature_flag: componentId, $feature_flag_response: variantId });
    mixpanel.register({ [`variant_${componentId}`]: variantId });
  }}
>
  {children}
</AdaptiveProvider>

Fires at most once per component ID per page load. Full Segment / GA4 / Mixpanel recipes: see sentient-ui.com/docs/integrations.

Local overrides (development)

Force a variant without touching the bandit:

# URL parameter (stackable)
https://yourapp.com?sentient_variant=hero_cta:variant_a&sentient_variant=pricing:annual_first

# Or, before SDK init:
window.__sentient_overrides = { hero_cta: 'variant_a' };

Overrides bypass the bandit entirely — no events recorded, weights unchanged.

Graph mode

Pass enableGraph to AdaptiveProvider (or AdaptiveRoot) to turn on DOM graph scanning. The SDK captures your page structure and syncs it to power the dashboard Graph page. The graph code is loaded on demand, so apps that don't set enableGraph keep the lean bundle.

<AdaptiveProvider apiKey="pk_…" context="saas" enableGraph>
  <App />
</AdaptiveProvider>

Direct @sentientui/core users enable the same thing by importing init from @sentientui/core/graph and passing graph: true.

Consent management (GDPR)

By default the SDK initialises with consent: true, so tracking starts on first paint — opt-in consent is something you wire up, not the default. For GDPR-style opt-in, pass consent={false} until the visitor accepts your banner; no events are sent and no cookie is written while consent is false. To serve the best-performing variant while the cookie banner is pending (rather than freezing the UI), add preConsentBehavior: 'statistical_winner':

<AdaptiveProvider
  apiKey={process.env.NEXT_PUBLIC_SENTIENT_API_KEY!}
  context="saas"
  consent={hasConsent}
  preConsentBehavior="statistical_winner"
>
  {children}
</AdaptiveProvider>

Set hasConsent to false until the user accepts your cookie banner, then flip it to true. The provider re-initialises automatically and begins full tracking.

Without React

import { init, grantConsent } from '@sentientui/core';

const client = init({
  apiKey: 'pk_...',
  context: 'saas',
  consent: false,
  preConsentBehavior: 'statistical_winner',
});

// After the user accepts the cookie banner:
grantConsent(); // upgrades client in place — no need to reassign

OneTrust integration

window.addEventListener('OneTrustGroupsUpdated', () => {
  // C0002 = Analytics/Performance category in OneTrust
  if (window.OnetrustActiveGroups?.includes('C0002')) {
    grantConsent();
  }
});

Cookiebot integration

window.addEventListener('CookiebotOnAccept', () => {
  if (window.Cookiebot?.consent?.statistics) {
    grantConsent();
  }
});

When consent: false without preConsentBehavior, the SDK is a complete no-op — no API calls, no cookies, nothing. The preConsentBehavior: 'statistical_winner' mode calls only GET /v1/winner, a read-only endpoint that returns the best variant without storing any visitor data.

Docs

Full SDK reference: sentient-ui.com/docs.
Integrations: sentient-ui.com/docs/integrations.

License

MIT