Accessibility guidelines for RawDrive. Use when building UI components, handling keyboard navigation, implementing ARIA attributes, or ensuring WCAG compliance.
All components and features must meet WCAG 2.1 Level AA standards.
htmlFor/id or aria-labelalt="") for decorative imagesrole="alert" for error messagesWhen building custom components that don't use native HTML elements:
role attributes (e.g., role="button", role="dialog")aria-label or aria-labelledby for accessible namesaria-describedby for additional descriptionsaria-expanded for expandable elementsaria-hidden="true" for decorative elementsaria-live for dynamic content announcementsaria-invalid and aria-describedby for form validation<button> elements, not divs with click handlerstype="button" to prevent form submissionaria-label for icon-only buttonsaria-busy="true"disabled attribute, not just visual stylingaria-invalid="true" for validation errorsaria-describedby to link error messagesaria-required="true" or required attributerole="alert" or aria-live="assertive"<fieldset> and <legend>role="dialog" or role="alertdialog"aria-modal="true" to indicate modal behaviorrole="presentation" and aria-hidden="true" on backdrop overlays// Complete modal/sidebar focus management pattern
const MyModal = ({ isOpen, onClose }) => {
const modalRef = useRef<HTMLDivElement>(null);
const triggerRef = useRef<HTMLButtonElement>(null);
// Focus trap
useEffect(() => {
if (!isOpen) return;
const modal = modalRef.current;
if (!modal) return;
const focusableElements = modal.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const firstElement = focusableElements[0] as HTMLElement;
const lastElement = focusableElements[focusableElements.length - 1] as HTMLElement;
const handleTab = (e: KeyboardEvent) => {
if (e.key !== 'Tab') return;
if (e.shiftKey && document.activeElement === firstElement) {
e.preventDefault();
lastElement?.focus();
} else if (!e.shiftKey && document.activeElement === lastElement) {
e.preventDefault();
firstElement?.focus();
}
};
modal.addEventListener('keydown', handleTab);
firstElement?.focus();
return () => modal.removeEventListener('keydown', handleTab);
}, [isOpen]);
// Return focus to trigger element when closed
const handleClose = useCallback(() => {
onClose();
setTimeout(() => triggerRef.current?.focus(), 100);
}, [onClose]);
return (
<>
{/* Overlay - NOT keyboard interactive */}
<div
className="fixed inset-0 bg-black/50"
onClick={handleClose}
role="presentation"
aria-hidden="true"
/>
{/* Modal content */}
<div
ref={modalRef}
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
>
<h2 id="modal-title">Modal Title</h2>
<button onClick={handleClose}>Close</button>
</div>
</>
);
};
// Announce to screen reader
const announce = (message: string, priority: 'polite' | 'assertive' = 'polite') => {
const announcement = document.createElement('div');
announcement.setAttribute('role', 'status');
announcement.setAttribute('aria-live', priority);
announcement.className = 'sr-only';
announcement.textContent = message;
document.body.appendChild(announcement);
setTimeout(() => announcement.remove(), 1000);
};
.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;
}
Before marking a component complete: