@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-reactPeer 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 classes — className 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.Checkboxrenders a native checkbox inside a<label>.Clickwrap.SubmitButtonrenders a native<button type="button">.- Provide clear button text and checkbox labels that describe the agreement being accepted.
- Use
loadingFallbackinClickwrap.Gatewhen 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';