Styling & Customization
Control the look and feel of every NDPA Toolkit component using classNames overrides, unstyled mode, or your favourite CSS framework
Introduction
Every visible component in the NDPA Toolkit ships with sensible default Tailwind CSS classes, but gives you full control over the final appearance. You can work in one of three modes:
1. Default
Use the built-in Tailwind styles as-is. Zero configuration required.
2. classNames Overrides
Replace individual section classes while keeping the rest of the defaults intact.
3. Unstyled
Strip every default class so you start from a blank canvas and apply your own styles from scratch.
Mode 1 — Default Styles
If the default appearance works for your project, simply import and render the component. No extra props needed.
import { ConsentBanner } from '@tantainnovative/ndpr-toolkit';
function App() {
return (
<ConsentBanner
options={consentOptions}
onSave={handleSave}
position="bottom"
/>
);
}Tip: Start with the defaults and only customise what you need. This keeps your code minimal and ensures you benefit from future style improvements automatically.
Mode 2 — classNames Overrides
Every component exposes a classNamesprop — an object keyed by semantic section name. When you provide a value for a key, it replaces the default class for that section while leaving every other section untouched.
import { ConsentBanner } from '@tantainnovative/ndpr-toolkit';
<ConsentBanner
options={consentOptions}
onSave={handleSave}
classNames={{
root: 'fixed inset-x-0 bottom-0 bg-indigo-900 text-white p-6',
acceptButton: 'bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-6 rounded-full',
rejectButton: 'bg-transparent border border-white text-white py-2 px-6 rounded-full',
}}
/>In the example above, only root, acceptButton, and rejectButton are customised. Every other section (title, description, option items, etc.) retains its built-in Tailwind styling.
Mode 3 — Unstyled
Set unstyled={true} to remove all default Tailwind classes from the component. This gives you a blank slate so you can apply your own design system, whether that is Bootstrap, CSS Modules, vanilla CSS, or something else entirely.
<ConsentBanner
options={consentOptions}
onSave={handleSave}
unstyled={true}
classNames={{
root: 'my-banner',
container: 'my-banner__inner',
title: 'my-banner__title',
description: 'my-banner__desc',
acceptButton: 'my-banner__btn my-banner__btn--accept',
rejectButton: 'my-banner__btn my-banner__btn--reject',
}}
/>Note: When unstyled is true and no classNames are provided for a given section, that section receives an empty class string. Make sure you supply classes for every section you want visible.
How It Works — The resolveClass Utility
Under the hood, every component calls a small helper function called resolveClass for each rendered element. The logic is straightforward:
export function resolveClass(
defaultClass: string,
override?: string,
unstyled?: boolean
): string {
if (unstyled) return override || '';
if (override) return override;
return defaultClass;
}Example: Bootstrap
If your project uses Bootstrap, enable unstyled mode and map Bootstrap's utility and component classes through the classNames prop:
import { ConsentBanner } from '@tantainnovative/ndpr-toolkit';
<ConsentBanner
options={consentOptions}
onSave={handleSave}
unstyled={true}
classNames={{
root: 'fixed-bottom bg-dark text-white p-4 shadow-lg',
container: 'container',
title: 'h5 mb-2',
description: 'small text-light mb-3',
optionsList: 'list-unstyled mb-3',
optionItem: 'form-check mb-2',
optionCheckbox: 'form-check-input',
optionLabel: 'form-check-label',
optionDescription: 'form-text text-muted',
buttonGroup: 'd-flex gap-2',
acceptButton: 'btn btn-success',
rejectButton: 'btn btn-outline-light',
customizeButton: 'btn btn-link text-light',
saveButton: 'btn btn-primary',
}}
/>Example: CSS Modules
CSS Modules generate unique class names at build time, making them a great choice for scoped, collision-free styles. Pass the imported styles directly to classNames:
/* consent-banner.module.css */
.root {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #1a1a2e;
color: #eee;
padding: 1.5rem;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.3);
}
.title {
font-size: 1.25rem;
font-weight: 600;
margin-bottom: 0.5rem;
}
.acceptButton {
background: #4caf50;
color: white;
border: none;
padding: 0.5rem 1.5rem;
border-radius: 4px;
cursor: pointer;
}
.rejectButton {
background: transparent;
color: #ccc;
border: 1px solid #ccc;
padding: 0.5rem 1.5rem;
border-radius: 4px;
cursor: pointer;
}import { ConsentBanner } from '@tantainnovative/ndpr-toolkit';
import styles from './consent-banner.module.css';
<ConsentBanner
options={consentOptions}
onSave={handleSave}
unstyled={true}
classNames={{
root: styles.root,
title: styles.title,
acceptButton: styles.acceptButton,
rejectButton: styles.rejectButton,
}}
/>Example: Vanilla CSS
Using a plain stylesheet works the same way. Import the stylesheet in your app entry point and pass class name strings through the classNames prop:
/* styles/consent.css */
.consent-banner {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #fff;
border-top: 2px solid #e0e0e0;
padding: 1.5rem 2rem;
display: flex;
align-items: center;
justify-content: space-between;
z-index: 1000;
}
.consent-banner__title {
font-size: 1.1rem;
font-weight: 700;
}
.consent-banner__btn--accept {
background: #2563eb;
color: #fff;
border: none;
padding: 0.6rem 1.4rem;
border-radius: 6px;
font-weight: 500;
cursor: pointer;
}
.consent-banner__btn--reject {
background: transparent;
color: #555;
border: 1px solid #ccc;
padding: 0.6rem 1.4rem;
border-radius: 6px;
cursor: pointer;
}import { ConsentBanner } from '@tantainnovative/ndpr-toolkit';
import './styles/consent.css';
<ConsentBanner
options={consentOptions}
onSave={handleSave}
unstyled={true}
classNames={{
root: 'consent-banner',
title: 'consent-banner__title',
acceptButton: 'consent-banner__btn--accept',
rejectButton: 'consent-banner__btn--reject',
}}
/>classNames Reference
The table below lists every component that supports the classNames and unstyled props, along with every available key from its ClassNames interface. These keys map directly to the exported TypeScript interfaces in the component source files.
| Component | Keys | classNames Keys |
|---|---|---|
| Consent Management | ||
| ConsentBanner | 14 | root, container, title, description, optionsList, optionItem, optionCheckbox, optionLabel, optionDescription, buttonGroup, acceptButton, rejectButton, customizeButton, saveButton |
| ConsentManager | 9 | root, header, title, description, optionsList, optionItem, toggle, saveButton, resetButton |
| ConsentStorage | 1 | root |
| Data Subject Rights (DSR) | ||
| DSRRequestForm | 11 | root, title, description, form, fieldGroup, label, input, select, textarea, submitButton, successMessage |
| DSRDashboard | 8 | root, header, title, filters, requestList, requestItem, statusBadge, detailPanel |
| DSRTracker | 9 | root, header, title, stats, statCard, table, tableHeader, tableRow, statusBadge |
| Data Protection Impact Assessment (DPIA) | ||
| DPIAQuestionnaire | 15 | root, header, title, section, sectionTitle, question, questionText, guidance, input, radioGroup, radioOption, navigation, nextButton, prevButton, progressBar |
| DPIAReport | 10 | root, header, title, summary, riskBadge, riskTable, riskRow, recommendation, conclusion, printButton |
| StepIndicator | 7 | root, step, stepActive, stepCompleted, stepPending, connector, label |
| Breach Notification | ||
| BreachReportForm | 10 | root, title, form, fieldGroup, label, input, select, textarea, submitButton, notice |
| BreachRiskAssessment | 8 | root, header, title, slider, riskBadge, riskScore, notificationStatus, submitButton |
| BreachNotificationManager | 9 | root, header, title, breachList, breachItem, statusBadge, timeline, timelineStep, detailPanel |
| RegulatoryReportGenerator | 9 | root, header, title, reportPreview, field, fieldLabel, fieldValue, generateButton, downloadButton |
| Privacy Policy Generator | ||
| PolicyGenerator | 10 | root, header, title, description, sectionList, sectionItem, form, input, generateButton, complianceNotice |
| PolicyPreview | 9 | root, header, title, description, content, section, sectionTitle, sectionContent, complianceNotice |
| PolicyExporter | 9 | root, header, title, description, formatSelector, formatOption, exportButton, complianceNotice, preview |
| Lawful Basis Tracker | ||
| LawfulBasisTracker | 15 | root, header, title, summary, summaryCard, table, tableHeader, tableRow, form, input, select, submitButton, statusBadge, complianceScore, gapAlert |
| Cross-Border Transfer | ||
| CrossBorderTransferManager | 15 | root, header, title, summary, summaryCard, transferList, transferItem, form, input, select, submitButton, riskBadge, statusBadge, detailPanel, approvalStatus |
| Record of Processing Activities (ROPA) | ||
| ROPAManager | 16 | root, header, title, orgInfo, summary, summaryCard, table, tableHeader, tableRow, form, input, select, submitButton, statusBadge, exportButton, complianceGap |
| Total (19 components) | 169 | All keys listed above are the exact keys from each component's exported ClassNames TypeScript interface |
TypeScript integration: Each component exports its ClassNames interface (e.g. ConsentBannerClassNames, DSRRequestFormClassNames) so your editor provides autocomplete for every key. Import them from the same path as the component.
Best Practices
Start with Defaults
Begin with the built-in Tailwind styles and only override what does not match your brand. This minimises the amount of CSS you maintain and ensures future improvements carry over.
Override, Don't Fight
If only a handful of sections need changes, use classNames without unstyled. This keeps the rest of the component looking good with zero effort.
Use Unstyled for Full Control
If your project relies on a different CSS framework (Bootstrap, Bulma, Material, etc.), enable unstyled mode to prevent clashes between the toolkit's Tailwind classes and your own.
Keep Accessibility in Mind
When restyling components, preserve sufficient colour contrast, focus indicators, and font sizes. The default styles meet WCAG AA contrast requirements — make sure your overrides do too.
Centralise Overrides
If you use the same classNames across multiple instances, extract them into a shared constant or theme object so changes propagate everywhere at once.
Test in Both Modes
If your app supports dark mode, verify that your custom classNames look correct in both light and dark themes.
Additional Resources
Component Documentation
Full API reference for every component, including all classNames interfaces and props.
Browse ComponentsGitHub Source
View the resolveClass utility and component source code for the exact default class values.
View on GitHub