React Accessibility Checker
Build a React component that automatically checks for common accessibility violations within its rendered DOM. This is crucial for ensuring web applications are usable by everyone, including people with disabilities.
Problem Description
You need to create a AccessibilityChecker React component. This component will wrap other React components and, during development, will analyze the DOM rendered by its children for predefined accessibility issues. It should visually highlight these issues on the screen and provide a way to log them.
Key Requirements:
- Component Wrapping: The
AccessibilityCheckercomponent should acceptchildrenas a prop, allowing it to wrap any React subtree. - Rule Implementation: Implement checks for at least the following common accessibility violations:
- Missing
alttext for<img>tags: Images should have descriptivealtattributes for screen readers. - Non-interactive elements with click handlers: Elements like
<div>or<span>that haveonClickprops attached should ideally be interactive elements (e.g.,<button>,<a>) or have ARIA roles and keyboard event listeners. For this challenge, focus on flagging non-interactive elements withonClick. - Insufficient color contrast: (Optional but highly encouraged) Implement a basic check for contrast ratios between text and background colors. You can use a simplified approach or an external library if allowed.
- Missing
aria-labeloraria-labelledbyfor interactive elements without visible text: For example, icons acting as buttons.
- Missing
- Visual Feedback: When an accessibility issue is detected, the component should visually highlight the problematic element (e.g., with a red border) and display a tooltip or a small annotation near it indicating the type of violation.
- Logging: Maintain a list of detected issues. This list should be accessible (e.g., via a console log or a dedicated UI element, though a console log is sufficient for this challenge).
- Development Mode Only: The accessibility checking and visual feedback should only be active in a development environment (e.g.,
process.env.NODE_ENV !== 'production'). In production, the component should simply render its children without any overhead. - Performance: The checks should be as efficient as possible to avoid significant performance degradation in development.
Expected Behavior:
When AccessibilityChecker is used to wrap a component that has accessibility issues, the issues should be visually marked on the page, and a report of these issues should be logged.
Edge Cases:
- Dynamically added content: How does the checker handle elements that are added to the DOM after the initial render?
- Nested
AccessibilityCheckercomponents: Ensure correct behavior when multiple checkers are nested. - Complex DOM structures: The checker should traverse the DOM effectively.
Examples
Example 1: Missing alt text
// App.tsx
import React from 'react';
import AccessibilityChecker from './AccessibilityChecker';
function App() {
return (
<AccessibilityChecker>
<div>
<h1>My Page</h1>
<img src="logo.png" alt="" /> {/* Intentionally empty alt */}
<p>Welcome!</p>
</div>
</AccessibilityChecker>
);
}
export default App;
Output:
The <img> tag would be highlighted with a red border, and a tooltip/annotation would appear, stating something like "Missing alt text for <img>". The console would log:
Accessibility Violation: Missing alt text for <img> tag. Element: <img src="logo.png" alt="">
Explanation: The AccessibilityChecker identifies the <img> element has an empty alt attribute, flags it visually, and logs the issue.
Example 2: Non-interactive element with onClick
// App.tsx
import React from 'react';
import AccessibilityChecker from './AccessibilityChecker';
function App() {
const handleClick = () => {
alert('Clicked!');
};
return (
<AccessibilityChecker>
<div>
<p>Click the area below:</p>
<div onClick={handleClick} style={{ width: '100px', height: '50px', backgroundColor: 'lightblue' }}>
Click Me
</div>
</div>
</AccessibilityChecker>
);
}
export default App;
Output:
The <div> with the onClick handler would be highlighted, and a tooltip would indicate a non-interactive element with a click handler. The console would log:
Accessibility Violation: Non-interactive element received a click event handler. Consider using a <button> or <a>. Element: <div onClick={handleClick}>Click Me</div>
Explanation: The AccessibilityChecker detects a <div> element with an onClick prop and flags it.
Example 3: Missing aria-label on icon button
// App.tsx
import React from 'react';
import AccessibilityChecker from './AccessibilityChecker';
import { FaCog } from 'react-icons/fa'; // Assuming react-icons for illustration
function App() {
const handleSettingsClick = () => {
console.log('Settings clicked');
};
return (
<AccessibilityChecker>
<div>
<button onClick={handleSettingsClick}>
<FaCog /> {/* Icon without text or aria-label */}
</button>
</div>
</AccessibilityChecker>
);
}
export default App;
Output:
The <button> containing the FaCog icon would be highlighted, and the annotation would indicate a missing aria-label or aria-labelledby. The console would log:
Accessibility Violation: Interactive element (<button>) has no accessible name. Add aria-label or aria-labelledby. Element: <button><FaCog /></button>
Explanation: The AccessibilityChecker identifies the button, sees it contains an element (the icon) that provides its visual representation but no accessible name, and flags it.
Constraints
- The solution must be written in TypeScript.
- The
AccessibilityCheckercomponent should not introduce significant performance overhead in production builds. - The checker should ideally run after the component has rendered to analyze the DOM.
useEffectis likely a good candidate for this. - Avoid relying on external, production-ready accessibility libraries unless specifically permitted for advanced features (like color contrast). Focus on implementing the core logic yourself.
- Assume a standard React development environment (e.g., Create React App or Vite with React template).
Notes
- You'll need to use DOM manipulation techniques within React's lifecycle methods or hooks to inspect the rendered elements.
- Consider how to associate the visual indicator (border/tooltip) with the correct DOM element.
- For the non-interactive element check, you might need to inspect the element's tag name and check for event handler props.
- For the
alttext check, you'll need to iterate through<img>elements. - Think about how to prevent infinite loops if your checker itself triggers re-renders that cause checks to run again.
- A good approach is to run the checks once after the initial render and potentially again if the DOM structure changes significantly, though for this challenge, a single run after initial render in development should suffice.
- Consider using
MutationObserverfor a more robust solution that can detect dynamically added elements, but this might be beyond the scope of a basic implementation. Focus on static analysis first.