@sudeep7353/grantly-react-consent
v0.1.7
Published
React implementation of the Grantly consent widget
Maintainers
Readme
@gfox/grantly-react-consent
A React component library that simplifies user consent management, offering components for both consent collection and revocation. Built with TypeScript support and fully customizable styling.
Table of Contents
- Installation
- Quick Start
- Requirements
- TypeScript Support
- ConsentWidget
- WithdrawConsentWidget
- Examples
- Troubleshooting
- License
Installation
Install the package using your preferred package manager:
npm install @gfox/grantly-react-consentyarn add @gfox/grantly-react-consentpnpm add @gfox/grantly-react-consentQuick Start
Get started with the Consent Widget in just a few steps:
- Install the package (see installation above)
- Import the component in your React application
- Configure the required props
Basic Example
import React from 'react';
import { ConsentWidget } from '@gfox/grantly-react-consent';
function App() {
const handleStatusChange = (data) => {
console.log('Consent status changed:', data.status, data);
if (data.status === 'accepted') {
console.log('User accepted purposes:', data.acceptedPurposes);
} else if (data.status === 'already-consented') {
console.log('User already consented');
}
};
return (
<div className="App">
<ConsentWidget
clientId="your-client-id"
xUserId="user-123"
onStatusChange={handleStatusChange}
/>
</div>
);
}
export default App;Requirements
- React >= 17.0.0
- React-DOM >= 17.0.0
These are peer dependencies and must be installed in your project.
TypeScript Support
This package includes full TypeScript definitions. No additional @types package is required.
import { ConsentWidget, WithdrawConsentWidget, ConsentStatusDetail, WithdrawConsentStatusDetail } from '@gfox/grantly-react-consent';
function App() {
const handleStatusChange = (data: ConsentStatusDetail) => {
// TypeScript will provide full type checking and autocomplete
if (data.status === 'accepted') {
console.log(data.acceptedPurposes); // TypeScript knows this exists
}
};
return (
<ConsentWidget
clientId="your-client-id"
xUserId="user-123"
onStatusChange={handleStatusChange}
/>
);
}ConsentWidget
A React component for managing user consent and data agreements with multiple display modes and customizable styling.
Description
The Consent Widget is a flexible React component that handles user consent management for data processing purposes. It supports three display modes: popup (default), inline, and plain, making it suitable for various integration scenarios. The widget automatically fetches consent agreements from an API and provides an intuitive interface for users to select their consent preferences.
Key Features:
- Automatic scroll locking in popup mode
- Consent modification mode with smart button enabling
- Programmatic consent submission via ref
- Comprehensive status callbacks
- Multi-language support via
acceptLanguageprop - Markdown support for agreement text
- XSS protection via DOMPurify
- Authentication token support (via prop or URL query parameter) - when provided, creates active consent directly
ConsentWidget Props
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| clientId | string | Required | - | Client identifier for the consent request |
| xUserId | string | Required | - | User identifier |
| authToken | string | Optional | - | Authentication token for consent submission. Can also be provided via URL query parameter ?token=.... When provided, an active consent is created directly. Otherwise, a draft consent is created |
| acceptLanguage | string | Optional | "en" | Language code for content localization (e.g., "en", "en-US", "hi") |
| displayMode | string | Optional | "" | Display mode flags (see Display Modes below) |
| modifyConsent | boolean | Optional | false | Enable consent modification mode. When enabled, the submit button is only enabled when consent has been modified |
| onStatusChange | function | Optional | - | Callback for consent status updates (see Status Callbacks below) |
Display Modes
The displayMode prop accepts space-separated flags:
| Flag | Description |
|------|-------------|
| Default (popup) | Modal overlay with full consent interface. Automatically locks background scroll when open |
| plain | Inline display without modal styling. No scroll locking |
| inline | Compact inline display (hides title, agreement text, and action buttons) |
| no-agreement-title | Hide the agreement title |
| no-agreement-text | Hide the agreement description text |
| no-change-summary | Hide purpose change summaries (shown by default in all modes except inline) |
Note: In default (popup) mode, the widget automatically locks background scrolling when the modal is open and restores it when closed. This ensures a clean user experience where users can only interact with the consent modal when it's open.
Examples:
// Modal popup (default)
<ConsentWidget displayMode="" />
// Plain inline mode
<ConsentWidget displayMode="plain" />
// Compact inline with hidden title
<ConsentWidget displayMode="inline no-agreement-title" />
// Plain mode with hidden change summary
<ConsentWidget displayMode="plain no-change-summary" />Ref Methods
The component supports ref forwarding and exposes the following method:
| Method | Description |
|--------|-------------|
| submitConsent() | Programmatically submit the consent. Returns a Promise that resolves with the consent result |
Example:
import { useRef } from 'react';
import { ConsentWidget } from '@gfox/grantly-react-consent';
const consentRef = useRef();
const handleSubmit = async () => {
try {
const result = await consentRef.current.submitConsent();
console.log('Consent submitted:', result);
} catch (error) {
console.error('Failed to submit:', error);
}
};
<ConsentWidget
ref={consentRef}
clientId="your-client-id"
xUserId="user-123"
// ... other props
/>Status Callbacks
The onStatusChange callback receives status updates with the following structure:
onStatusChange({
status: 'in-progress' | 'already-consented' | 'accepted' | 'declined' | 'error',
clientId: string,
userId: string, // Note: This is 'userId' in the callback, not 'xUserId'
// Conditional fields based on status:
acceptedPurposes?: string[], // present when status is 'accepted' or 'declined'
agreementVersionId?: string, // present when status is 'accepted' or 'declined'
actionId?: string, // present when status is 'accepted' (ID to use for completing the consent)
consentState?: string, // present when status is 'accepted' (backend consent state: 'draft' or 'active')
expiresAt?: string | null, // present when status is 'accepted' (consent expiration date if applicable)
error?: string // ONLY present when status is 'error' (contains error message)
})| Status | Description | Additional Fields |
|--------|-------------|-------------------|
| "in-progress" | Widget is fetching consent agreement data | None |
| "already-consented" | User has already consented (204 response). Widget won't open in popup mode | None |
| "accepted" | User has successfully submitted consent | acceptedPurposes, agreementVersionId, actionId, consentState, expiresAt |
| "declined" | User closed the modal without submitting consent | acceptedPurposes: [], agreementVersionId |
| "error" | An error occurred during fetch or submission | error (contains error message string) |
ConsentWidget Styling
The widget exposes CSS custom properties for complete customization:
Base Properties
--consent-widget-width: 100%;
--consent-widget-font-family: inherit;
--consent-widget-font-size: inherit;
--consent-widget-text-color: #f9fafb;Modal Properties
--consent-widget-modal-bg: rgba(0, 0, 0, 0.8);
--consent-widget-modal-content-bg: #1f2937;
--consent-widget-modal-z-index: 9999;Container Properties
--consent-widget-container-bg: #1f2937;
--consent-widget-container-padding: 2rem;
--consent-widget-container-border-radius: 16px;
--consent-widget-container-max-width: 800px;Button Properties
--consent-widget-primary-bg: #8b5cf6;
--consent-widget-secondary-bg: transparent;
--consent-widget-button-padding: 0.75rem 1.5rem;
--consent-widget-button-border-radius: 6px;Spacing Properties
--consent-widget-spacing-xs: 0.25rem;
--consent-widget-spacing-sm: 0.5rem;
--consent-widget-spacing-md: 1rem;
--consent-widget-spacing-lg: 1.5rem;
--consent-widget-spacing-xl: 2rem;Override these variables in your CSS to customize the widget appearance:
.consent-widget {
--consent-widget-primary-bg: #007bff;
--consent-widget-container-border-radius: 8px;
--consent-widget-text-color: #333;
}WithdrawConsentWidget
A React component that allows users to view and withdraw their existing consents. The widget displays consent information in a clean, customizable interface with support for both modal and inline display modes.
Key Features:
- Automatic modal opening when consents are loaded (default mode)
- ESC key support to close modal
- Automatic scroll locking in modal mode
- Status callback deduplication to prevent duplicate events
- Auto-detection of display mode from consent data
- Multi-language support via
acceptLanguageprop - Markdown support for agreement text
- XSS protection via DOMPurify
Note: The widget returns null (doesn't render) when loading or when there are no consents. Use the onStatusChange callback to handle these states.
WithdrawConsentWidget Props
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| clientId | string | Required | - | Client identifier for the application |
| userId | string | Required | - | User identifier |
| displayMode | string | Optional | "" | Display mode flags (see Display Modes) |
| acceptLanguage | string | Optional | "en" | Language code for content localization (e.g., "en", "en-US", "hi") |
| onStatusChange | function | Optional | - | Callback for widget status changes (see Status Callbacks below) |
| onModifyConsent | function | Optional | - | Callback when user clicks modify consent. Receives the consent object as parameter |
WithdrawConsentWidget Display Modes
The displayMode prop accepts space-separated flags:
| Flag | Description |
|------|-------------|
| Default (modal) | Modal overlay with full interface. Automatically opens when consents are loaded. Locks background scroll and supports ESC key to close |
| plain | Inline display without modal. No scroll locking |
| no-agreement-title | Hide agreement titles |
| no-agreement-text | Hide agreement descriptions |
| no-consent-timestamps | Hide consent timestamps (consented_at, expires_at, withdrawn_at) |
| no-modify-button | Hide modify consent buttons |
Note:
- In default (modal) mode, the widget automatically opens the modal when consents are successfully loaded
- The modal locks background scrolling when open and restores it when closed
- Press ESC key to close the modal in default mode
- The widget automatically determines the display mode from the first consent's
display_modeif not explicitly provided via props
Examples:
// Modal mode (default)
<WithdrawConsentWidget displayMode="" />
// Plain inline mode
<WithdrawConsentWidget displayMode="plain" />
// Plain mode with hidden titles and timestamps
<WithdrawConsentWidget displayMode="plain no-agreement-title no-consent-timestamps" />
// Modal mode without modify buttons
<WithdrawConsentWidget displayMode="no-modify-button" />WithdrawConsentWidget Styling
The widget exposes CSS variables for complete customization. All variables are prefixed with --wcw-:
Layout & Spacing
--wcw-max-width: 800px;
--wcw-padding: 20px;
--wcw-margin: 0 auto;
--wcw-border-radius: 8px;
--wcw-spacing-xs: 4px;
--wcw-spacing-sm: 8px;
--wcw-spacing-md: 12px;
--wcw-spacing-lg: 16px;
--wcw-spacing-xl: 20px;
--wcw-spacing-2xl: 24px;
--wcw-spacing-3xl: 40px;Colors
--wcw-bg-primary: #ffffff;
--wcw-bg-secondary: #f9fafb;
--wcw-text-primary: #1f2937;
--wcw-text-secondary: #4b5563;
--wcw-primary: #8b5cf6;
--wcw-danger: #8b5cf6;
--wcw-success: #059669;Typography
--wcw-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
--wcw-font-size-base: 14px;
--wcw-font-size-small: 12px;
--wcw-font-size-large: 18px;
--wcw-font-size-xl: 24px;
--wcw-font-weight-normal: 400;
--wcw-font-weight-medium: 500;
--wcw-font-weight-semibold: 600;Shadows & Transitions
--wcw-shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.1);
--wcw-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.1);
--wcw-shadow-lg: 0 8px 25px rgba(0, 0, 0, 0.15);
--wcw-transition-fast: 0.15s ease;
--wcw-transition-normal: 0.2s ease;
--wcw-transition-slow: 0.3s ease;Plain Mode Variables
--wcw-plain-bg: transparent;
--wcw-plain-shadow: none;
--wcw-plain-border: none;
--wcw-plain-padding: 0;
--wcw-plain-margin: 0;
--wcw-plain-max-width: none;
--wcw-plain-min-height: auto;WithdrawConsentWidget Status Callbacks
The onStatusChange callback receives status updates with the following structure:
onStatusChange({
status: 'in-progress' | 'consents-loaded' | 'no-consents' | 'withdrawing' | 'withdrawn' | 'error',
clientId: string,
// Additional fields based on status:
consents?: Consent[], // present when status is 'consents-loaded' or 'withdrawn'
totalCount?: number, // present when status is 'consents-loaded' or 'no-consents'
withdrawnConsent?: Consent, // present when status is 'withdrawing' or 'withdrawn'
actionId?: string, // present when status is 'withdrawn' (ID to use for completing the consent withdrawal)
error?: string // present when status is 'error'
})| Status | Description |
|--------|-------------|
| "in-progress" | Widget is fetching consents from the API |
| "consents-loaded" | Consents successfully loaded. Includes consents array and totalCount |
| "no-consents" | User has no active consents. Includes totalCount: 0 |
| "withdrawing" | Consent withdrawal in progress. Includes withdrawnConsent |
| "withdrawn" | Consent successfully withdrawn. Includes updated consents array, withdrawnConsent, and actionId |
| "error" | Error occurred during operation. Includes error message |
Note: The callback uses deduplication to avoid firing duplicate status updates for the same state.
Example:
<WithdrawConsentWidget
onStatusChange={(data) => {
switch (data.status) {
case 'consents-loaded':
console.log('Loaded consents:', data.consents);
console.log('Total count:', data.totalCount);
break;
case 'withdrawn':
console.log('Consent withdrawn:', data.withdrawnConsent);
console.log('Remaining consents:', data.consents);
break;
case 'no-consents':
console.log('User has no active consents');
break;
case 'error':
console.error('Error:', data.error);
break;
}
}}
/>Examples
Advanced ConsentWidget Usage
Inline Mode with Custom Styling
import React from 'react';
import { ConsentWidget } from '@gfox/grantly-react-consent';
import './ConsentWidget.css';
function InlineConsent() {
return (
<div className="consent-section">
<h2>Data Processing Consent</h2>
<ConsentWidget
clientId="your-client-id"
xUserId="user-123"
displayMode="inline no-agreement-title"
acceptLanguage="en"
onStatusChange={(data) => {
if (data.status === 'accepted') {
console.log('User has given consent');
}
}}
/>
</div>
);
}Plain Mode for Embedded Use
import React from 'react';
import { ConsentWidget } from '@gfox/grantly-react-consent';
function EmbeddedConsent() {
return (
<div className="embedded-consent">
<ConsentWidget
clientId="your-client-id"
xUserId="user-123"
displayMode="plain no-change-summary"
modifyConsent={true}
onStatusChange={(data) => {
// Handle consent modifications
if (data.status === 'accepted') {
console.log('Consent modified:', data.acceptedPurposes);
}
}}
/>
</div>
);
}Note: When modifyConsent={true}, the submit button is only enabled when the user has actually changed their consent selection from the initial state. This prevents unnecessary API calls when no changes are made.
Advanced WithdrawConsentWidget Usage
Modal Mode with Custom Callbacks
import React, { useState } from 'react';
import { WithdrawConsentWidget } from '@gfox/grantly-react-consent';
function ConsentManagement() {
const [consentData, setConsentData] = useState(null);
const handleStatusChange = (data) => {
switch (data.status) {
case 'consents-loaded':
setConsentData(data.consents);
break;
case 'withdrawn':
alert('Consent successfully withdrawn');
setConsentData(data.consents); // Update with remaining consents
break;
case 'error':
console.error('Error:', data.error);
break;
}
};
const handleModifyConsent = (consent) => {
// Redirect to consent modification page
window.location.href = `/modify-consent/${consent.id}`;
};
return (
<div>
<h1>Manage Your Consents</h1>
<WithdrawConsentWidget
clientId="your-client-id"
userId="user-123"
onStatusChange={handleStatusChange}
onModifyConsent={handleModifyConsent}
/>
</div>
);
}Plain Mode for Settings Page
import React from 'react';
import { WithdrawConsentWidget } from '@gfox/grantly-react-consent';
function SettingsPage() {
return (
<div className="settings-page">
<h2>Privacy Settings</h2>
<div className="consent-management">
<WithdrawConsentWidget
clientId="your-client-id"
userId="user-123"
displayMode="plain no-modify-button"
acceptLanguage="en"
onStatusChange={(data) => {
if (data.status === 'no-consents') {
console.log('User has no active consents');
}
}}
/>
</div>
</div>
);
}Custom Styling Examples
Dark Theme
.consent-widget {
--consent-widget-container-bg: #1a1a1a;
--consent-widget-text-color: #ffffff;
--consent-widget-primary-bg: #6366f1;
--consent-widget-modal-bg: rgba(0, 0, 0, 0.9);
--consent-widget-modal-content-bg: #2d2d2d;
}Brand Colors
.consent-widget {
--consent-widget-primary-bg: #007bff;
--consent-widget-secondary-bg: #6c757d;
--consent-widget-container-border-radius: 12px;
--consent-widget-button-padding: 1rem 2rem;
}Minimal Design
.consent-widget {
--consent-widget-container-bg: transparent;
--consent-widget-container-padding: 1rem;
--consent-widget-container-border-radius: 4px;
--consent-widget-button-border-radius: 4px;
--consent-widget-spacing-md: 0.5rem;
}License
This project is licensed under the Apache License 2.0.
