Build an Accessible Rich Text Comment System in React
Posted on By Shamal Jayawardhana | In Editor, Tutorials
Table of contents
- Key Takeaways
- Step 1: Project Setup & Component Structure
- Core Components
- Basic Structure
- Sample State (App Level)
- Step 2: Integrating the Rich Text Editor
- Install
- Basic Editor Setup
- Step 3: Accessibility Configuration for the Editor
- 1. ARIA Labels (WCAG 4.1.2)
- 2. Keyboard Navigation (WCAG 2.1.1)
- 3. Focus Management (Critical UX + Accessibility)
- 4. Contrast & Visibility
- Step 4: Build the Accessible Comment Form
- Accessibility Wins Here
- Step 5: Sanitizing User-Generated Content (Critical)
- Example Using DOMPurify
- Step 6: Displaying Comments Safely
- CommentList
- CommentItem
- Important Note
- Comment Flow (Simple Mental Model)
- WCAG Compliance Summary
- Advanced Considerations
- 1. Limit Toolbar for Simplicity
- 2. Image Upload (Accessibility Warning)
- 3. Accessible UGC Strategy
- Ready to Build This?
- Final Thoughts
Modern apps don’t just collect comments, they rely on them.
Whether you’re building a SaaS dashboard, a blog platform, or a community feature, your comment system is where real user interaction happens. And today, plain text just isn’t enough.
Users expect formatting, links, maybe even images.
But here’s the challenge.
As soon as you introduce a rich text editor, accessibility becomes much harder to get right.
This guide walks you through exactly how to:
- Build a React comment system with a rich text editor
- Apply key WCAG accessibility patterns
- Handle validation, focus, and keyboard navigation
- Safely render user-generated HTML content
No fluff. Just practical implementation.
Key Takeaways
- Building an accessible rich text comment system in React requires combining proper component structure with a WCAG-compliant editor setup and form handling.
- Accessibility must be intentionally implemented through ARIA labels, keyboard navigation support, and focus management to ensure all users can interact with the editor effectively.
- A well-structured comment form should include clear labels, validation feedback, and accessible controls to improve usability and meet WCAG standards.
- Sanitizing user-generated HTML content is essential to prevent XSS vulnerabilities before rendering comments in the UI.
- Limiting editor features and maintaining a clean UI helps create a more accessible, readable, and user-friendly comment experience.
Step 1: Project Setup & Component Structure
We’ll keep things simple and structured.
Core Components
Your comment system will have three main parts:
- CommentForm → Input + editor + submission
- CommentList → List of comments
- CommentItem → Single comment display
Basic Structure
<App>
<CommentForm onSubmit={addComment} />
<CommentList comments={comments} />
</App>
Sample State (App Level)
import { useState } from "react";
import CommentForm from "./components/CommentForm";
import CommentList from "./components/CommentList";
export default function App() {
const [comments, setComments] = useState([]);
const addComment = (newComment) => {
setComments((prev) => [...prev, newComment]);
};
return (
<div>
<h1>Comments</h1>
<CommentForm onSubmit={addComment} />
<CommentList comments={comments} />
</div>
);
}
Step 2: Integrating the Rich Text Editor
We’ll use the Froala React SDK for the editor.
For full setup, follow: Integrate Froala with React
Install
npm install react-froala-wysiwyg froala-editor dompurify
Basic Editor Setup
import FroalaEditorComponent from "react-froala-wysiwyg";
import "froala-editor/js/plugins.pkgd.min.js";
// Handle Vite import edge case
const Editor = FroalaEditorComponent?.default || FroalaEditorComponent;
<Editor
tag="textarea"
model={content}
onModelChange={setContent}
/>
But this alone is not accessible enough.
Let’s fix that properly
Step 3: Accessibility Configuration for the Editor
This is where most implementations fail.
We’ll focus on:
- ARIA labels
- Keyboard navigation
- Focus management
- Contrast
1. ARIA Labels (WCAG 4.1.2)
Your editor must have a programmatic name.
import FroalaEditorComponent from "react-froala-wysiwyg";
import "froala-editor/js/plugins.pkgd.min.js";
// Fix for Vite environments
const Editor = FroalaEditorComponent?.default || FroalaEditorComponent;
<label id="comment-label">Write a comment</label>
<Editor
tag="textarea"
model={content}
onModelChange={setContent}
config={{
ariaLabel: "Comment input editor",
// Better accessibility (ties label properly)
attributes: {
"aria-labelledby": "comment-label"
}
}}
/>
Or link it explicitly:
config={{
attributes: {
'aria-labelledby': 'comment-label'
}
}}
2. Keyboard Navigation (WCAG 2.1.1)
Good news:
Froala already supports keyboard navigation by default.
Users can:
- Tab through toolbar buttons
- Use keyboard shortcuts
- Navigate inside the editor
You don’t need to rebuild this. Just don’t break it with custom overrides.
3. Focus Management (Critical UX + Accessibility)
After submitting a comment, where does focus go?
If you ignore this → screen reader users get lost.
Use useRef
import { useRef } from "react";
const editorRef = useRef(null);
<Editor
tag="textarea"
model={content}
onModelChange={setContent}
config={{
events: {
initialized: function () {
editorRef.current = this;
}
}
}}
/>
// After submit
const handleSubmit = () => {
// submit logic...
// Move focus back to editor
editorRef.current?.events.focus();
};
4. Contrast & Visibility
Your editor UI must meet contrast requirements.
Froala is built with accessibility in mind
(See: Accessible. Faster. More Customizable.)
Still:
- Avoid low-contrast themes
- Ensure buttons are visible in all states
Step 4: Build the Accessible Comment Form
Now let’s bring everything together.
Full CommentForm Component
import { useState, useRef } from "react";
import FroalaEditorComponent from "react-froala-wysiwyg";
import "froala-editor/js/plugins.pkgd.min.js";
// Fix for Vite environments
const Editor = FroalaEditorComponent?.default || FroalaEditorComponent;
export default function CommentForm({ onSubmit }) {
const [content, setContent] = useState("");
const [error, setError] = useState("");
const editorRef = useRef(null);
const handleSubmit = (e) => {
e.preventDefault();
// Strip HTML for validation
const plainText = content.replace(/<[^>]*>/g, "").trim();
if (!plainText) {
setError("Comment cannot be empty.");
return;
}
setError("");
onSubmit(content);
setContent("");
// Focus editor correctly
editorRef.current?.events.focus();
};
return (
<form onSubmit={handleSubmit}>
<label id="comment-label">Write your comment</label>
<Editor
tag="textarea"
model={content}
onModelChange={setContent}
config={{
placeholderText: "Type your comment here...",
ariaLabel: "Comment editor",
// Proper label association
attributes: {
"aria-labelledby": "comment-label"
},
toolbarButtons: ["bold", "italic", "underline", "insertLink"],
// Capture editor instance for focus
events: {
initialized: function () {
editorRef.current = this;
}
}
}}
/>
{error && (
<p role="alert" style={{ color: "red" }}>
{error}
</p>
)}
<button type="submit">Post Comment</button>
<button type="button" onClick={() => setContent("")}>
Cancel
</button>
</form>
);
}
Accessibility Wins Here
- Proper <form> structure
- Associated <label>
- Error messages with role=”alert”
- Keyboard-friendly buttons
- Focus restored after submission
Step 5: Sanitizing User-Generated Content (Critical)
This is non-negotiable.
Even if Froala outputs clean HTML:
You must sanitize before rendering.
Otherwise → XSS vulnerabilities
Example Using DOMPurify
npm install react-froala-wysiwyg froala-editor dompurify
import DOMPurify from "dompurify"; // Example usage before rendering const safeHTML = DOMPurify.sanitize(userInput);
Step 6: Displaying Comments Safely
CommentList
import CommentItem from "./CommentItem";
export default function CommentList({ comments }) {
return (
<div className="comment-list">
{comments.map((comment, index) => (
<CommentItem key={index} content={comment} />
))}
</div>
);
}
CommentItem
import DOMPurify from "dompurify";
export default function CommentItem({ content }) {
return (
<div
className="comment"
dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(content)
}}
/>
);
}
Important Note
- dangerouslySetInnerHTML is safe only after sanitization
- Never render raw user HTML
Comment Flow (Simple Mental Model)
User writes → Editor → Submit form
↓
Validation → Sanitization
↓
Store comment → Render safely
WCAG Compliance Summary
| WCAG Criterion | What We Did | Where |
|---|---|---|
| 1.3.1 Info & Relationships | Label tied to editor | |
| 2.1.1 Keyboard | Full keyboard navigation | Froala default |
| 3.3.1 Error Identification | Clear validation messages | role="alert" |
| 4.1.2 Name, Role, Value | ARIA labels on editor | config |
Advanced Considerations
1. Limit Toolbar for Simplicity
For comments, you don’t need full editing power.
toolbarButtons: ['bold', 'italic', 'insertLink']
Keeps UI clean and more accessible
For advanced control:
Customizing Froala’s Behavior with Command-Driven Events
2. Image Upload (Accessibility Warning)
If you allow images:
- Require alt text
- Ensure uploads are keyboard accessible
See: Three Ways For Uploading Images in Froala: Which One Should You Use?
3. Accessible UGC Strategy
Think beyond code:
- Avoid overwhelming formatting options
- Keep comment UI simple
- Ensure readability of output
Ready to Build This?
You now have everything you need to:
- Build a React comment system with rich text editor
- Make it accessible and compliant
- Safely handle user-generated content
Ready to implement this in your app?
Start a free trial of Froala and get your API key to follow along with this tutorial.
Final Thoughts
Accessibility isn’t a “nice-to-have” feature.
In a comment system, it directly impacts:
- Who can participate
- How easily they can interact
- Whether your app is inclusive
The good news?
You don’t need to build everything from scratch.
With the right structure + a capable editor + a few intentional decisions, you can ship a fully accessible rich text comment experience without slowing down your development.
Shamal Jayawardhana
Shamal Jayawardhana is a seasoned web development expert and technical content strategist with a proven track record of helping developers and digital creators thrive. With over five years of hands-on experience, he has worked with leading SaaS brands to produce high-impact tutorials, WordPress guides, and developer-focused resources.
No comment yet, add your voice below!