Search topics
Rich text editors announced without their label
The typing area is a contenteditable div that a label element can't name, so it has no name, or the generic one its editor library gives every editor.
aria-label="Rich Text Editor. Editing area: main" The problem
Screen reader users aren't told which box they are writing in, and voice control users can't reach it by saying the label everyone else sees.
A shop's contact form has a box headed Message, with Bold, Italic and list buttons above it, so people can format what they write. On screen it is as clearly labelled as any other field on the form. A screen reader user who presses Tab into it never hears "Message". In the first version, the box has no name, and nothing says it is a text box: Chrome's accessibility tree lists it as a generic element. In the second, the editor library has named it: the name is "Rich Text Editor. Editing area: main", which the library gives every editor it creates, so a form with two editors has two boxes called the same thing. A voice control user says "Click Message", the word on screen, and nothing happens in either version.
A rich text editor can't be a textarea, which holds plain text only. To show bold words and lists, editors make a div editable with contenteditable, and that div is not a form field. A label element names only what HTML calls labelable elements, such as button, input, meter, output, progress, select and textarea, so for="message" on a label that points at the div names nothing: the box gets no name from it, and clicking the label does nothing. The link people see between the label and the box isn't in the code, which fails 1.3.1. contenteditable makes the div editable but gives it no role, so a screen reader isn't told it is a text box or what it is for, which fails 4.1.2 for both role and name (W3C failures F59 and F68). W3C failure F111 names the whole case, a control with a visible label and no name, and counts it against 1.3.1, 2.5.3 and 4.1.2 at once. Adding role="textbox" alone fixes the role and leaves the name empty.
Some editor libraries, such as CKEditor 5 and Lexical, add the role themselves. CKEditor 5 also adds an aria-label when it is given none: "Rich Text Editor. Editing area: main", where "main" is the name of the editing area, not anything on the page. The box then has a role and a name, but the name isn't the label shown above it. 2.5.3 asks for the visible words to be in the name, so that people who say them reach the control, and "Message" appears nowhere in it. 1.3.1 still fails too, because the label on screen is still not tied to the box.
Every version looks the same, so a sighted mouse user sees nothing wrong. The library's name gets worse with each editor on a page: a product form with a Description editor and a Care instructions editor has two boxes with the same name, and only their order tells them apart.
2 of 10 groups affected
- No vision (affected)
- Low vision (not affected)
- Colour vision (not affected)
- No hearing (not affected)
- Hard of hearing (not affected)
- No speech (not affected)
- Motor (affected)
- Reach, strength (not affected)
- Cognitive (not affected)
- Seizures (not affected)
- Without vision Screen reader users reach a box with no name, or with the library's name, so they don't hear the Message label printed above it and can't tell what to write or which editor they are in.
- Limited manipulation Voice control users say the label they can see, and the editor doesn't respond, because those words are not its name.
axe's aria-input-field-name rule checks elements with a text box role, so it finds a box given role="textbox" and no name. To axe, a div with no role isn't a field at all, and no axe rule reports it; a library's name is a name, so that version isn't reported as a violation either: while a label's for still points at the box, as in the second Before, axe only lists it for review, because the label's words are not in the name. EaseWeb's audit reports an editable div with no role as a 4.1.2 finding, and sends an editor named "Rich Text Editor…" or "Editor editing area…" to a person, since whether the label shown should be its name is for someone to look at. It doesn't reach an editor inside an iframe. A quick fix, by hand or by an AI assistant, may add an id to the div so the label's for matches: it looks right in review and still names nothing.
Try it
Tab into the message box and see what it is called, then say “Click Message” with voice control.
Broken 1: the label above the box is a label element, but its for can’t reach a div, so the box has no name, and nothing gives it a role.
Broken 2: the editor library names the box itself, with words that appear nowhere on screen.
Apply a fix. Then turn on Reword the label and try both fixes again: which name follows the new words?
Broken Can you fix this?
(Tab into the message box)
The fix
Make the box a text box named by the label beside it, with aria-labelledby or the library's label setting.
- 1The editable element has role="textbox" and aria-multiline="true".
- 2Its name holds the words of the label shown beside it, best by pointing aria-labelledby at that label.
- 3Never leave the name the editor library gives every editor.
An editable div with no role
<label for="message">Message</label><div class="toolbar" role="toolbar" aria-label="Formatting"> …</div><div id="message" class="editor" contenteditable="true"> <p>The parcel arrived with a cracked lid.</p></div>A text box that points at its label
<div id="message-label">Message</div><div class="toolbar" role="toolbar" aria-label="Formatting"> …</div><div id="message" class="editor" contenteditable="true" role="textbox" aria-multiline="true" aria-labelledby="message-label"> <p>The parcel arrived with a cracked lid.</p></div>Why this fix: Name things by pointing at the words on screen
The label's for matches the div's id, but a div is not a labelable element, so the label names nothing, and the div has no role.
role="textbox" and aria-multiline="true" say what the box is, and aria-labelledby makes the label's own text its name: a screen reader reads "Message" with the role, and voice control responds to "Click Message". The label is plain text now, because a label element can't name a div. The library's aria-label is gone; if it stays, aria-labelledby wins over it, but the next person to read the code won't know which name counts.
Both give the box a role and the words of its label as its name. Point aria-labelledby at the label: there is one copy of the words, so the name follows the label whenever it changes, which the copied aria-label doesn't. Copy the words only when the library lets you set nothing on the typing area but its own label setting, and then pass that setting the same string the label shows, from one variable, so the two can't drift.
Set it in the library, then check the page. The attributes belong on the element where editing starts, which the library creates, so set them through the library's options: an attribute on the element you pass in reaches the typing area only when the library keeps it, as CKEditor 5 keeps an aria-label. Check the result in the browser after each upgrade: some libraries add their own role or aria-label, and a default that changes between versions changes your editor's name with it.
Multi-line. aria-multiline="true" says that Enter starts a new line rather than sending the form. Chrome already reports an editable text box as multi-line by itself; the attribute states it in the markup rather than leaving it to each browser's guess.
Clicking the label. A label element focuses its field when clicked; a div that names an editor doesn't. If the design needs the click, a handler that calls focus() on the editor adds it. It isn't required to pass.
Hints, required and placeholders. Put a hint such as "Bold and lists are kept" in an element of its own and point aria-describedby at it. A div can't take required, so use aria-required="true" on the text box, beside the visible marking. A grey placeholder that the editor draws with CSS, often from a data-placeholder attribute, is not a name and disappears once people type.
No formatting needed. If the text needs no bold, lists or links, such as a short note to a courier, a textarea with a label element does the whole job without any of this.
Editors in an iframe. An editor drawn inside an iframe has two things to name: the frame, with its title, and the editable page inside it. Check both in the accessibility tree; the audit can't reach the second.
Verify the fix
4 checks, no mouse.
Test with: Keyboard, Screen reader, Speech input
Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the editor library is upgraded.
Limits
This is a starting pattern, not a guarantee for every editor. It covers the typing area's role and name, not the rest of the editor. An editor that keeps the Tab key to indent, so that keyboard users can't leave it, is covered in keyboard traps in editors and embedded widgets. A Bold button that doesn't say whether it is on is covered in toggle buttons that don't say whether they are pressed, and toolbar icons with no name in icon-only buttons with no accessible name. A toolbar that takes one Tab stop and moves with the arrow keys belongs to the topic "Toolbars work with arrow keys and take one Tab stop", which has no guide yet. Asking for alt text when people add an image to the editor is covered in image uploads that never ask for alt text. Labels on native fields are covered in form fields with missing or mismatched labels. Test the complete journey with your target assistive technology.
This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.
References
- WCAG Understanding: name-role-value (opens in a new tab)
- WCAG Understanding: info-and-relationships (opens in a new tab)
- WCAG Understanding: label-in-name (opens in a new tab)
- WCAG Technique F59 (opens in a new tab)
- WCAG Technique F68 (opens in a new tab)
- WCAG Technique F111 (opens in a new tab)
- html.spec.whatwg.org: forms (opens in a new tab)
- ARIA APG: names-and-descriptions (opens in a new tab)
- developer.mozilla.org: textbox_role (opens in a new tab)
- ckeditor.com: module_core_editor_editorconfig-EditorConfig (opens in a new tab)
- prosemirror.net: ref (opens in a new tab)
- github.com: inlineeditableuiview.ts (opens in a new tab)
- github.com: LexicalContentEditableElement.tsx (opens in a new tab)
EaseWeb service
Fixed it? Keep it from coming back.
I turn guides like this one into rules your coding agents follow and checks your CI runs. Then I retest by hand with a keyboard and screen readers, periodically.