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

@chamelioai/clickwrap-sdk-react

v0.1.0-beta.7

Published

React components for clickwrap consent by Chamelio

Readme

@chamelioai/clickwrap-sdk-react

React components for embedding clickwrap consent flows. Built on top of @chamelioai/clickwrap-sdk.

Installation

npm install @chamelioai/clickwrap-sdk-react

Peer dependencies: React 18+

This package depends on @chamelioai/clickwrap-sdk for API access and keeps React and React DOM as peer dependencies so your app owns the React runtime.

Quick Start

import { Clickwrap, ClickwrapProvider } from '@chamelioai/clickwrap-sdk-react';

function App() {
  return (
    <ClickwrapProvider publicKey="your_public_api_key" userIdentifier={userEmail}>
      <Clickwrap.Agreement slug="terms-of-service">
        <Clickwrap.Content />
        <Clickwrap.Checkbox label="I have read and agree to the terms" />
        <Clickwrap.SubmitButton>I Agree</Clickwrap.SubmitButton>
      </Clickwrap.Agreement>
    </ClickwrapProvider>
  );
}

Provider Setup

Wrap your app (or the relevant subtree) with ClickwrapProvider:

<ClickwrapProvider
  publicKey="your_public_api_key" // required — your Chamelio public API key
  userIdentifier={email} // required — current user's email or unique ID
>
  {children}
</ClickwrapProvider>

The provider creates a ClickwrapClient instance and makes it available to all descendant components and hooks.

| Prop | Type | Required | Description | | ---------------- | ----------- | -------- | ------------------------------------------------ | | publicKey | string | Yes | Chamelio public API key | | userIdentifier | string | Yes | Current user's email or stable unique identifier | | children | ReactNode | Yes | React subtree that can use Clickwrap components |

Components

Clickwrap.Agreement

Scopes a single agreement by slug. Fetches data and status on mount and provides context to child components.

<Clickwrap.Agreement slug="terms-of-service">
  {/* Child components read from this agreement's context */}
</Clickwrap.Agreement>

Supports render-prop children for full control:

<Clickwrap.Agreement slug="terms-of-service">
  {({ data, isAccepted, accept, isLoading }) => <div>{/* Custom UI */}</div>}
</Clickwrap.Agreement>

| Prop | Type | Description | | ---------- | --------------------------------- | ------------------------------- | | slug | string | Agreement slug (required) | | children | ReactNode \| (ctx) => ReactNode | Regular children or render prop |

Clickwrap.Content

Renders the agreement's HTML content. Extends HTMLAttributes<HTMLDivElement>.

<Clickwrap.Content />
<Clickwrap.Content requireScroll style={{ maxHeight: '200px' }} />

Clickwrap.Content records a viewed event automatically when the content is visible. For scroll_and_accept agreements, it also requires and records scrolled_to_bottom before acceptance.

| Prop | Type | Default | Description | | --------------- | --------- | ------- | --------------------------------------------------------------- | | requireScroll | boolean | false | Require scrolling to bottom even for click-to-accept agreements |

Clickwrap.Checkbox

Renders a labeled checkbox. When a checkbox mounts, the submit button is automatically disabled until checked. Extends InputHTMLAttributes<HTMLInputElement>.

<Clickwrap.Checkbox label="I have read and agree to the Terms of Service" />

| Prop | Type | Description | | ----------- | ------------ | --------------------- | | label | string | Label text (required) | | onChecked | () => void | Callback when checked |

className and style apply to the outer <label>. Use inputClassName and inputStyle for the underlying checkbox input.

Clickwrap.SubmitButton

Renders an accept button. Automatically disabled when loading, submitting, already accepted, checkbox unchecked, or scroll incomplete. Extends ButtonHTMLAttributes<HTMLButtonElement>.

<Clickwrap.SubmitButton onAccepted={() => router.push('/dashboard')}>I Agree</Clickwrap.SubmitButton>

| Prop | Type | Description | | ------------ | ------------------------- | ------------------------------------ | | onAccepted | () => void | Callback after successful acceptance | | onError | (error: Error) => void | Callback on error | | metadata | Record<string, unknown> | Extra metadata to include |

Consumer onClick handlers run before the SDK acceptance call. If your handler calls event.preventDefault(), the SDK will not capture acceptance.

If acceptance fails, onError is called when provided and the button remains retryable.

Clickwrap.Gate

Conditionally renders children based on acceptance status. No styles — pure logic.

<Clickwrap.Gate fallback={<div>Please accept the terms first</div>} loadingFallback={<Spinner />}>
  <ProtectedContent />
</Clickwrap.Gate>

| Prop | Type | Description | | ----------------- | ----------- | ---------------------------------------- | | children | ReactNode | Shown when accepted | | fallback | ReactNode | Shown when not accepted | | loadingFallback | ReactNode | Shown while loading (defaults to null) |

Common Patterns

1. Submit Button Only

The simplest pattern — user clicks to accept immediately.

<Clickwrap.Agreement slug="terms">
  <Clickwrap.Content />
  <Clickwrap.SubmitButton>I Agree</Clickwrap.SubmitButton>
</Clickwrap.Agreement>

2. Checkbox + Submit Button

Button is disabled until the checkbox is checked.

<Clickwrap.Agreement slug="terms">
  <Clickwrap.Content />
  <Clickwrap.Checkbox label="I agree to the Terms of Service" />
  <Clickwrap.SubmitButton>Continue</Clickwrap.SubmitButton>
</Clickwrap.Agreement>

3. Checkbox Only (Form Integration)

Use the render prop to integrate acceptance into your own form:

<Clickwrap.Agreement slug="terms">
  {({ accept, isChecked }) => (
    <form
      onSubmit={async (e) => {
        e.preventDefault();
        if (isChecked) await accept();
      }}
    >
      <Clickwrap.Content />
      <Clickwrap.Checkbox label="I agree" />
      <button type="submit" disabled={!isChecked}>
        Submit
      </button>
    </form>
  )}
</Clickwrap.Agreement>

4. Scroll-to-Bottom Requirement

Button stays disabled until the user scrolls to the bottom of the content.

<Clickwrap.Agreement slug="terms">
  <Clickwrap.Content requireScroll style={{ maxHeight: '200px' }} />
  <Clickwrap.SubmitButton>I Agree</Clickwrap.SubmitButton>
</Clickwrap.Agreement>

5. Gate (Protect Content)

Show a fallback until the user accepts, then reveal protected content.

<Clickwrap.Agreement slug="terms">
  <Clickwrap.Gate
    fallback={
      <div>
        <Clickwrap.Content />
        <Clickwrap.SubmitButton>Accept to Continue</Clickwrap.SubmitButton>
      </div>
    }
  >
    <Dashboard />
  </Clickwrap.Gate>
</Clickwrap.Agreement>

useClickwrap Hook

For full control, use the hook directly (must be inside ClickwrapProvider):

import { useClickwrap } from '@chamelioai/clickwrap-sdk-react';

function CustomAgreement() {
  const {
    data, // ClickwrapData | null
    status, // ClickwrapStatus | null
    isLoading, // initial fetch in progress
    isSubmitting, // accept/check/view call in flight
    isAccepted, // status?.accepted ?? false
    isChecked, // local checkbox UI state
    hasViewed, // viewed event captured for the active version
    hasScrolledToEnd, // content scroll tracking
    error, // Error | null
    accept, // (metadata?) => Promise<void>
    check, // (metadata?) => Promise<void>
    view, // (metadata?) => Promise<void>
    scrolledToBottom, // (metadata?) => Promise<void>
    refetch, // () => void
    setChecked, // (checked: boolean) => void
    setScrolledToEnd, // (scrolled: boolean) => void
    client, // ClickwrapClient (escape hatch)
  } = useClickwrap('terms-of-service');

  // Build your fully custom UI...
}

Error Handling

Components automatically disable Clickwrap.SubmitButton when the agreement cannot be loaded. For custom UI, read error from the render prop or useClickwrap:

<Clickwrap.Agreement slug="terms-of-service">
  {({ error, isLoading, data }) => {
    if (isLoading) return <p>Loading agreement...</p>;
    if (error) return <p>Unable to load agreement: {error.message}</p>;
    if (!data) return null;

    return (
      <>
        <Clickwrap.Content />
        <Clickwrap.SubmitButton onError={(err) => console.error(err)}>I Agree</Clickwrap.SubmitButton>
      </>
    );
  }}
</Clickwrap.Agreement>

accept, check, view, and scrolledToBottom reject if agreement data has not loaded yet. Capture failures are re-thrown and also stored in error.

Multiple Agreements

Render multiple agreements on the same page — each Clickwrap.Agreement manages its own state independently:

<ClickwrapProvider publicKey="your_public_api_key" userIdentifier={email}>
  <Clickwrap.Agreement slug="terms-of-service">
    <Clickwrap.Content />
    <Clickwrap.SubmitButton>Accept Terms</Clickwrap.SubmitButton>
  </Clickwrap.Agreement>

  <Clickwrap.Agreement slug="privacy-policy">
    <Clickwrap.Content />
    <Clickwrap.SubmitButton>Accept Privacy</Clickwrap.SubmitButton>
  </Clickwrap.Agreement>
</ClickwrapProvider>

Styling

All components ship with sensible inline default styles. Override them in two ways:

Inline styles — your style values merge on top (yours win):

<Clickwrap.Content style={{ maxHeight: '300px', border: '2px solid purple' }} />
<Clickwrap.SubmitButton style={{ backgroundColor: '#10b981' }}>Accept</Clickwrap.SubmitButton>

CSS classesclassName is passed through as-is:

<Clickwrap.Content className="my-content" />
<Clickwrap.SubmitButton className="btn btn-primary">Accept</Clickwrap.SubmitButton>

Clickwrap.Checkbox applies style to the outer <label> wrapper and passes input attributes such as name, value, required, and data-* to the underlying <input>.

Security

Clickwrap.Content renders contentHtml from the Clickwrap API with dangerouslySetInnerHTML. The SDK assumes this HTML is generated and sanitized.

Accessibility

  • Clickwrap.Checkbox renders a native checkbox inside a <label>.
  • Clickwrap.SubmitButton renders a native <button type="button">.
  • Provide clear button text and checkbox labels that describe the agreement being accepted.
  • Use loadingFallback in Clickwrap.Gate when hiding protected content during the initial status check.

Runtime Support

This package is intended for browser-rendered React applications. It can be imported in SSR frameworks, but components that fetch agreement data should render on the client because they rely on browser fetch and user-specific consent state.

Clickwrap.Content uses IntersectionObserver when available. If the browser does not support it, rendering still works and the viewed event is captured after render.

TypeScript

All types are exported:

import type {
  AgreementProps,
  CheckboxProps,
  ClickwrapConfig,
  ClickwrapData,
  ClickwrapEventType,
  ClickwrapProviderProps,
  ClickwrapStatus,
  ClickwrapType,
  ContentProps,
  GateProps,
  SubmitButtonProps,
  UseClickwrapReturn,
} from '@chamelioai/clickwrap-sdk-react';