10 tips for building accessible rich text editors
April 12, 2021
If you’re building an app that features a rich text editor - you should make it as accessible as possible for screen-readers.
The following is a list of 10 recommendations for building accessible rich text editors. Although most points will be applicable to any kind of library you use, some of them are specific to react.js.
1. Test with an actual screen reader
If you have never used a screen reader, try one out on your app or any app with that has a rich text editor. I’d consider this blog-post a success even if you don’t come back to finish reading it.
- On OSX you already have one built in to your operating system: VoiceOver.
- For Windows users there’s nvda (free) which is also the most widely used.
- Another popular screen reader is JAWS (not free).
All of the below recommendations become more obvious if you put yourself into your users shoes.
2. Avoid keyboard traps
Overriding default keyboard behaviour to implement features such as hotkeys or indenting text via tab is a common pattern seen in web-based rich-text editors and IDEs.
However, screen-reader users rely entirely on the keyboard to navigate your app, their browser and operating system. It is frustrating and hard to overcome if default keyboard functionality is prevented by an application. If you are implementing such features, check they don’t collide with hotkeys that are already in use for the browsers or operating systems.
3. All interactive elements must be reachable via tab and/or arrow keys
Most rich text editing applications make use of menus and toolbars, dynamically positioned or fixed in place.
To ensure that elements are reachable via keyboard they need to be focusable. DOM elements such as button
, input
, select
or textarea
are focusable by default. If you’re using an element that isn’t considered interactive (such as a div
) you are required to add a tabindex
and a role
attribute so that the screen reader knows how to interact with the element.
Consider how much work it is for a keyboard-only user (the mouse is mostly useless for a user with sight-issues) to navigate to these UI elements. This will depend on the position of the element in the DOM and how many other interactive elements are in between.
If you’re building an editor with a floating toolbar (such as medium - which is completely inaccessible via tab keys btw) pay close attention to how a keyboard-only user can navigate to and from these buttons and toolbar components.
For complex usecases (if you have submenus or button groups) you should consider programmatically moving focus, more info on this here
4. Use alt text for your images and make them configurable
If you’re rendering images in your editor, it is recommended you use an alt attribute to represent what the image contains to a screen reader.
However, for user submitted content it is too difficult to generate meaningful alt text automatically, that’s why you should consider making the alt attribute configurable by the user. Here is what it looks like on medium.com:
5. Label your icon buttons
Most rich text editors make use of common icons for text formatting and layout.
These icons don’t have textual meaning on their own, hence we need to tell the screen reader what they are by assigning the aria-label
attribute like so:
<button aria-label="Bold" onClick={makeTextBold}>
<MyIcon>
</button>
If you’re not using the semantic button
element, you need to assign the tabindex
attribute (to make it tabbable) as well as the button
role (to tell the screen reader that this is a button), see below:
<span
aria-label="Bold"
role="button"
tabindex="0"
onClick={makeTextBold}
>
<MyIcon>
</span>
6. Use a linter with a11y support
To make it easier to stay on top of your apps accessibility, a linter can go a long way.
For react users, the a11y-jsx eslint plugin will make you aware of what attributes you’re missing to make your web-app accessible. I cannot overstate how valuable this is especially for teams.
I recommend running your linter automatically (either as part of your CI or with a git hook).
7. Use an accessible component library
Using established component libraries will give you a leg-up on reaching a good accessibility baseline. Reach.ui, material ui and chakra are all react libraries that have decent accessibility defaults.
8. Think twice about whether you need collaborative editing
If your rich text editor has collaborative editing the content of a users document can change at the same time as you’re editing it. The screen-reader needs to be notified that these updates occur, however these notifications must not be too noisy or distracting which can be a difficult balancing act.
Although there are no out of the box solutions for this, a good reference is googles “Live Edit’s” feature, providing a list of edits made by other users.
Features such as this can be notoriously expensive to implement, test and maintain. My recommendation: Think twice about whether you actually need collaboration as part of your product before adding it to your roadmap.
9. Use a title attribute for iframes
Embedding content via iframes is a staple for rich text editors these days. To make this accessible for screen readers you should use the title attribute to describe what the iframe contains.
10. Always use the textbox
role
Most frameworks (draft.js, slate.js and prosemirror as well as others) render this by default, but it’s always good to double check this!
Any rich text editor using the contentEditable
attribute (which is pretty much every rich text editor) needs to use the “textbox” role, to tell the screen-reader that it will respond to input and editing commands.