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;
}
unstyled + override→ Uses only the override class
unstyled, no override→ Returns an empty string (no styling at all)
override only→ Replaces the default with the override
neither→ Uses the built-in default class

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.

ComponentKeysclassNames Keys
Consent Management
ConsentBanner14root, container, title, description, optionsList, optionItem, optionCheckbox, optionLabel, optionDescription, buttonGroup, acceptButton, rejectButton, customizeButton, saveButton
ConsentManager9root, header, title, description, optionsList, optionItem, toggle, saveButton, resetButton
ConsentStorage1root
Data Subject Rights (DSR)
DSRRequestForm11root, title, description, form, fieldGroup, label, input, select, textarea, submitButton, successMessage
DSRDashboard8root, header, title, filters, requestList, requestItem, statusBadge, detailPanel
DSRTracker9root, header, title, stats, statCard, table, tableHeader, tableRow, statusBadge
Data Protection Impact Assessment (DPIA)
DPIAQuestionnaire15root, header, title, section, sectionTitle, question, questionText, guidance, input, radioGroup, radioOption, navigation, nextButton, prevButton, progressBar
DPIAReport10root, header, title, summary, riskBadge, riskTable, riskRow, recommendation, conclusion, printButton
StepIndicator7root, step, stepActive, stepCompleted, stepPending, connector, label
Breach Notification
BreachReportForm10root, title, form, fieldGroup, label, input, select, textarea, submitButton, notice
BreachRiskAssessment8root, header, title, slider, riskBadge, riskScore, notificationStatus, submitButton
BreachNotificationManager9root, header, title, breachList, breachItem, statusBadge, timeline, timelineStep, detailPanel
RegulatoryReportGenerator9root, header, title, reportPreview, field, fieldLabel, fieldValue, generateButton, downloadButton
Privacy Policy Generator
PolicyGenerator10root, header, title, description, sectionList, sectionItem, form, input, generateButton, complianceNotice
PolicyPreview9root, header, title, description, content, section, sectionTitle, sectionContent, complianceNotice
PolicyExporter9root, header, title, description, formatSelector, formatOption, exportButton, complianceNotice, preview
Lawful Basis Tracker
LawfulBasisTracker15root, header, title, summary, summaryCard, table, tableHeader, tableRow, form, input, select, submitButton, statusBadge, complianceScore, gapAlert
Cross-Border Transfer
CrossBorderTransferManager15root, header, title, summary, summaryCard, transferList, transferItem, form, input, select, submitButton, riskBadge, statusBadge, detailPanel, approvalStatus
Record of Processing Activities (ROPA)
ROPAManager16root, header, title, orgInfo, summary, summaryCard, table, tableHeader, tableRow, form, input, select, submitButton, statusBadge, exportButton, complianceGap
Total (19 components)169All 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.

Advanced: Shared Theme Object

For larger applications, you may want to centralise your style overrides so every toolkit component shares a consistent look:

// theme/ndpr-theme.ts
export const ndprTheme = {
  consentBanner: {
    root: 'fixed inset-x-0 bottom-0 bg-brand-900 text-white p-6 shadow-2xl z-50',
    acceptButton: 'bg-brand-500 hover:bg-brand-600 text-white font-semibold py-2 px-6 rounded-lg',
    rejectButton: 'border border-white/30 text-white py-2 px-6 rounded-lg hover:bg-white/10',
  },
  dsrRequestForm: {
    root: 'bg-white dark:bg-gray-800 rounded-xl shadow p-6',
    submitButton: 'bg-brand-500 hover:bg-brand-600 text-white font-semibold py-2 px-6 rounded-lg',
  },
  breachReportForm: {
    root: 'bg-white dark:bg-gray-800 rounded-xl shadow p-6',
    submitButton: 'bg-red-600 hover:bg-red-700 text-white font-semibold py-2 px-6 rounded-lg',
  },
};

// Then use across your app:
import { ndprTheme } from '@/theme/ndpr-theme';

<ConsentBanner classNames={ndprTheme.consentBanner} ... />
<DSRRequestForm classNames={ndprTheme.dsrRequestForm} ... />
<BreachReportForm classNames={ndprTheme.breachReportForm} ... />

Additional Resources

Component Documentation

Full API reference for every component, including all classNames interfaces and props.

Browse Components

GitHub Source

View the resolveClass utility and component source code for the exact default class values.

View on GitHub