Get Started for FREE

Build an Accessible Rich Text Comment System in React

Illustration showing an accessible rich text comment system with formatting tools, link insertion, user interaction, and security validation in a React application

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
Accessible rich text comment system in React with formatted user input and secure rendering of comments.
Accessible rich text comment system in React with formatted user input and secure rendering of comments.

Comment Flow (Simple Mental Model)

 

User writes → Editor → Submit form
       ↓
Validation → Sanitization
       ↓
Store comment → Render safely

WCAG Compliance Summary

WCAG CriterionWhat We DidWhere
1.3.1 Info & RelationshipsLabel tied to editor
2.1.1 KeyboardFull keyboard navigationFroala default
3.3.1 Error IdentificationClear validation messagesrole="alert"
4.1.2 Name, Role, ValueARIA labels on editorconfig

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.

graphical user interface, text

Posted on March 30, 2026

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!


Add a Comment

Your email address will not be published. Required fields are marked *