The library is designed with accessibility in mind to ensure that all users, including those using assistive technologies, can understand and interact with consent controls. This documentation covers the accessibility features built into the components and provides guidance for maintaining compliance.
The consent components follow the Web Content Accessibility Guidelines (WCAG) 2.2 Level AA standards. This ensures that your consent management system is usable by people with diverse abilities and those who rely on assistive technologies like screen readers, keyboard navigation, and voice control.
Key accessibility features include:
- Full keyboard navigation with focus trap
- Comprehensive ARIA attributes for screen readers
- Automatic focus management with focus restoration
- Sufficient color contrast ratios
- High contrast mode support
- Reduced motion support for vestibular disorders
- Clear visual focus indicators
- Semantic HTML structure
- Unique ARIA IDs for multiple components
All interactive elements in the consent banner and modal are fully accessible via keyboard. Users who cannot or prefer not to use a mouse can navigate and operate all controls using standard keyboard shortcuts.
Tab - Move focus forward through interactive elements (buttons, toggles, links)
Shift + Tab - Move focus backward through interactive elements
Enter or Space - Activate the currently focused button or toggle
Escape - Close the consent modal and return focus to the trigger element
When the consent banner appears:
- Focus can be moved to the banner's action buttons using Tab
- Users can navigate between "Accept All", "Reject All", and "Customize" buttons
- Pressing Enter or Space on any button executes the corresponding action
When the consent modal opens:
- Focus is automatically moved to the first interactive element in the modal
- Users can Tab through category toggles and service toggles (if enabled)
- Tab wraps within the modal - focus is trapped and cannot leave the modal while it's open
- Pressing Escape closes the modal and returns focus to the element that opened it
- Clicking "Save Preferences" closes the modal and saves the selections
This focus trap prevents users from accidentally interacting with content behind the modal, which is especially important for keyboard and screen reader users.
The components include comprehensive ARIA (Accessible Rich Internet Applications) attributes to provide context and state information to assistive technologies.
The banner and modal use appropriate semantic roles:
<!-- Banner uses dialog role to indicate modal-like behavior -->
<div role="dialog" aria-modal="false" aria-labelledby="consent-title">
<!-- Banner content -->
</div>
<!-- Modal uses dialog role with modal flag -->
<div role="dialog" aria-modal="true" aria-labelledby="modal-title">
<!-- Modal content -->
</div>The role="dialog" attribute tells screen readers that this is a dialog box containing important information that requires user interaction. The aria-modal attribute indicates whether the dialog blocks interaction with the rest of the page.
All interactive elements include descriptive labels:
<!-- Buttons with clear labels -->
<button aria-label="Accept all cookies">Accept All</button>
<button aria-label="Reject all non-essential cookies">Reject All</button>
<button aria-label="Customize cookie preferences">Customize</button>
<button aria-label="Close preferences modal">Close</button>
<!-- Toggles with state information -->
<button
role="switch"
aria-checked="true"
aria-label="Analytics cookies"
>
<!-- Toggle UI -->
</button>The aria-label attribute provides a text description of the button's purpose. For toggle switches, role="switch" and aria-checked communicate both the control type and its current state.
When consent preferences are saved, screen readers announce the change using ARIA live regions:
<div role="status" aria-live="polite" aria-atomic="true">
Your cookie preferences have been saved.
</div>The aria-live="polite" attribute tells screen readers to announce the message when the user finishes their current task, without interrupting them. The aria-atomic="true" attribute ensures the entire message is read together.
The banner and modal include clear descriptions of what cookies are and why they're used. Screen readers can access all text content, including:
- Banner title and description
- Category names and purposes
- Service names and descriptions
- Privacy policy links
Proper focus management ensures keyboard and screen reader users always know where they are and can navigate efficiently.
When the modal opens, focus is trapped inside it. This prevents users from tabbing out of the modal and getting lost in background content:
// Example of focus trap behavior
const modal = document.querySelector('[role="dialog"][aria-modal="true"]');
const focusableElements = modal.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
// When Tab is pressed on the last element, loop to first
// When Shift+Tab is pressed on the first element, loop to lastThis circular tab order ensures users can always navigate the modal efficiently without losing their place.
When the modal closes, focus is automatically returned to the element that opened it:
// Store reference to trigger element
const triggerElement = document.activeElement;
// Open modal
openModal();
// When modal closes, restore focus
closeModal();
triggerElement.focus();This prevents users from losing their position on the page when closing the modal.
All interactive elements display visible focus indicators when navigated via keyboard. The default focus style uses a clear outline:
.rck-btn:focus-visible {
outline: 2px solid var(--rck-primary);
outline-offset: 2px;
}
.rck-toggle:focus-visible {
outline: 2px solid var(--rck-primary);
outline-offset: 2px;
}The :focus-visible selector ensures focus indicators only appear during keyboard navigation, not when clicking with a mouse.
When the modal opens, focus is placed on the first logical element:
- If the modal has a close button, focus moves there
- Otherwise, focus moves to the first toggle or interactive element
This ensures screen reader users immediately know the modal has opened and can begin interacting with it.
The default themes meet WCAG 2.1 Level AA color contrast requirements, ensuring text is readable for users with low vision or color blindness.
Light theme:
- Text on background: 14.5:1 (exceeds 4.5:1 requirement)
- Button text on primary color: 14.5:1 (exceeds 4.5:1 requirement)
- Border colors: Sufficient contrast for visibility
Dark theme:
- Text on background: 14.5:1 (exceeds 4.5:1 requirement)
- Button text on primary color: 14.5:1 (exceeds 4.5:1 requirement)
- Border colors: Sufficient contrast for visibility
If you customize the theme colors using CSS variables, verify your color combinations meet contrast requirements:
/* Ensure sufficient contrast */
:root {
--rck-bg: #ffffff;
--rck-text: #1a1a1a; /* Must have 4.5:1 ratio with bg */
--rck-primary: #000000;
--rck-primary-text: #ffffff; /* Must have 4.5:1 ratio with primary */
}Use online contrast checkers to verify your custom colors:
- WebAIM Contrast Checker: https://webaim.org/resources/contrastchecker/
- Contrast Ratio: https://contrast-ratio.com/
The components don't rely solely on color to convey information:
- Toggle states use position and shape in addition to color
- Buttons have clear text labels
- Disabled states use opacity and cursor changes
- Focus states use outlines, not just color changes
For users with vestibular disorders or motion sensitivity, the library respects the prefers-reduced-motion media query:
@media (prefers-reduced-motion: reduce) {
.rck-banner,
.rck-modal,
.rck-toggle,
.rck-btn {
animation: none !important;
transition: none !important;
}
}When users have reduced motion enabled in their system preferences, all animations and transitions are disabled. This includes:
- Banner slide-in animations
- Modal fade effects
- Toggle switch transitions
- Button hover animations
- Overlay fade effects
No configuration is needed - this works automatically based on system preferences.
The library includes a dedicated high contrast theme for users with low vision:
<ConsentProvider
config={{
theme: 'high-contrast',
}}
>The high contrast theme uses:
| Element | Color |
|---|---|
| Background | #000000 (pure black) |
| Text | #FFFFFF (pure white) |
| Primary buttons | #FFFF00 (yellow) |
| Toggle on state | #00FF00 (green) |
| Focus indicators | #00FFFF (cyan) |
| Borders | #FFFFFF (white) |
The library also respects the prefers-contrast: more media query. When enabled in system settings, high contrast styles are applied automatically:
@media (prefers-contrast: more) {
.rck-banner,
.rck-modal {
--rck-bg: #000000;
--rck-text: #ffffff;
--rck-primary: #ffff00;
/* ... */
}
}For advanced use cases, the library exports a useFocusTrap hook that you can use in your own components:
import { useFocusTrap } from 'react-consent-shield';
import { useRef } from 'react';
function MyModal({ isOpen, onClose }) {
const modalRef = useRef<HTMLDivElement>(null);
useFocusTrap(modalRef, {
isActive: isOpen,
onEscape: onClose,
restoreFocus: true,
autoFocus: true,
});
return (
<div ref={modalRef} role="dialog" aria-modal="true">
{/* Modal content */}
</div>
);
}| Option | Type | Default | Description |
|---|---|---|---|
| isActive | boolean | required | Whether the focus trap is active |
| onEscape | () => void | undefined | Callback when Escape key is pressed |
| restoreFocus | boolean | true | Restore focus to previously focused element on deactivation |
| autoFocus | boolean | true | Auto-focus first focusable element on activation |
The focus trap implements WCAG 2.2 AA requirements:
- Tab cycling: Tab on last element wraps to first, Shift+Tab on first wraps to last
- Escape handling: Pressing Escape calls the
onEscapecallback - Focus restoration: When deactivated, focus returns to the element that was focused before activation
- Auto-focus: When activated, focus moves to the first focusable element
- Focus containment: If focus somehow leaves the container, it's brought back automatically
Here's how to ensure accessibility when integrating the library into your application:
import { ConsentProvider, ConsentBanner, ConsentModal } from 'react-consent-shield';
function App() {
return (
<ConsentProvider
config={{
services: [googleAnalytics, metaPixel],
}}
>
{/* Your main application content */}
<main id="main-content">
<h1>Welcome to Our Site</h1>
{/* Page content */}
</main>
{/* Consent components */}
<ConsentBanner />
<ConsentModal />
</ConsentProvider>
);
}Add a skip link to help keyboard users bypass the consent banner if needed:
function App() {
return (
<>
<a href="#main-content" className="skip-link">
Skip to main content
</a>
<ConsentProvider config={{ /* ... */ }}>
<ConsentBanner />
<ConsentModal />
<main id="main-content" tabIndex={-1}>
{/* Page content */}
</main>
</ConsentProvider>
</>
);
}.skip-link {
position: absolute;
top: -40px;
left: 0;
background: #000;
color: #fff;
padding: 8px;
text-decoration: none;
z-index: 100000;
}
.skip-link:focus {
top: 0;
}For users with accessibility needs, provide an always-visible link to manage preferences:
import { useConsent } from 'react-consent-shield';
function Footer() {
const { openPreferences } = useConsent();
return (
<footer>
<button
onClick={openPreferences}
aria-label="Manage cookie preferences"
>
Cookie Settings
</button>
</footer>
);
}If you need to announce consent changes to screen readers:
import { useConsent } from 'react-consent-shield';
import { useEffect, useState } from 'react';
function ConsentAnnouncer() {
const { consent } = useConsent();
const [announcement, setAnnouncement] = useState('');
useEffect(() => {
if (consent) {
setAnnouncement('Your cookie preferences have been saved.');
// Clear announcement after it's been read
setTimeout(() => setAnnouncement(''), 1000);
}
}, [consent]);
return (
<div
role="status"
aria-live="polite"
aria-atomic="true"
className="sr-only"
>
{announcement}
</div>
);
}.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}Regular accessibility testing ensures your consent implementation remains accessible to all users.
Use automated tools to catch common accessibility issues:
axe DevTools - Browser extension that scans for WCAG violations:
# Install as Chrome/Firefox extension
# Run scan on page with consent banner
# Check for any errors or warningsjest-axe - Automated accessibility testing in Jest:
import { axe } from 'jest-axe';
import { render } from '@testing-library/react';
import { ConsentBanner } from 'react-consent-shield';
test('ConsentBanner should not have accessibility violations', async () => {
const { container } = render(<ConsentBanner />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});Lighthouse - Chrome DevTools accessibility audit:
# Open Chrome DevTools
# Navigate to Lighthouse tab
# Run accessibility audit
# Review and fix any issuesAutomated tools can't catch everything. Perform manual testing for complete coverage:
Keyboard Navigation:
- Disconnect your mouse or don't use it
- Use Tab to navigate through all interactive elements
- Verify all buttons and toggles are reachable
- Test that Enter/Space activates controls
- Verify Escape closes the modal
- Confirm focus indicators are visible
- Check that focus doesn't get trapped unexpectedly
Screen Reader Testing:
Test with popular screen readers:
- NVDA (Windows, free): https://www.nvaccess.org/
- JAWS (Windows, commercial): https://www.freedomscientific.com/
- VoiceOver (macOS/iOS, built-in): Enable in System Preferences
- TalkBack (Android, built-in): Enable in Settings
Steps to test:
- Enable screen reader
- Navigate to the consent banner
- Verify all text is announced clearly
- Check that button purposes are clear
- Test toggle switches announce their state
- Confirm modal opening/closing is announced
- Verify saved preferences trigger announcements
Visual Testing:
Check visual accessibility features:
- Verify sufficient color contrast in both themes
- Test with browser zoom at 200% - content should remain usable
- Test with Windows High Contrast mode
- Verify focus indicators are visible
- Check that UI doesn't rely solely on color
Cognitive Testing:
Ensure the consent UI is clear and understandable:
- Instructions should be simple and direct
- Button labels clearly indicate their action
- Categories and services have clear descriptions
- The purpose of each cookie type is explained
- No confusing or misleading patterns
Use this checklist to verify accessibility compliance:
Keyboard Navigation:
- All interactive elements are keyboard accessible
- Tab order is logical and predictable
- Focus indicators are visible on all focusable elements
- Modal traps focus and returns it on close
- Escape key closes the modal
- Focus restoration works when modal closes
Screen Reader Support:
- All buttons have clear labels or aria-label
- Toggle states are announced to screen readers
- Screen reader announces all important information
- Modal announces when it opens
- Consent changes are announced to screen readers
- Unique ARIA IDs prevent conflicts with multiple instances
Visual Accessibility:
- Color contrast meets WCAG AA standards (4.5:1 for normal text)
- UI works at 200% browser zoom
- Privacy policy links are keyboard accessible
- Close buttons are clearly labeled
- High contrast mode displays correctly
- Reduced motion preference is respected
Automated Testing:
- No accessibility violations in automated tools
- axe-core reports no critical issues
- Lighthouse accessibility score > 90
If you discover accessibility issues in the library, please report them:
- Check the GitHub issues to see if it's already reported
- Create a new issue with:
- Description of the accessibility problem
- Which WCAG criterion is violated
- Steps to reproduce
- Your testing environment (browser, screen reader, etc.)
- Screenshots or screen recordings if applicable
Accessibility is an ongoing commitment. Regular testing and user feedback help ensure the library remains usable for everyone.
Back to main documentation | Previous: Styling | Next: Framework Integration