# Rich text editors announced without their label

Source: https://easeweb.dev/learn/rich-text-editors
Topics: Custom inputs > Rich text editors are announced as text boxes with their visible label (WCAG 4.1.2, 1.3.1, 2.5.3)
The fix: Give the editor's editable element role="textbox" and aria-multiline="true", and name it with the label shown beside it. Either point aria-labelledby at that label, or set the editor library's label setting to the same words, which puts them in aria-label.
Test with: keyboard, screen reader, speech input
References: https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html https://www.w3.org/WAI/WCAG22/Understanding/label-in-name.html https://www.w3.org/WAI/WCAG22/Techniques/failures/F59 https://www.w3.org/WAI/WCAG22/Techniques/failures/F68 https://www.w3.org/WAI/WCAG22/Techniques/failures/F111 https://html.spec.whatwg.org/multipage/forms.html#category-label https://www.w3.org/WAI/ARIA/apg/practices/names-and-descriptions/ https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Roles/textbox_role https://ckeditor.com/docs/ckeditor5/latest/api/module_core_editor_editorconfig-EditorConfig.html https://prosemirror.net/docs/ref/#view.EditorProps.attributes https://github.com/ckeditor/ckeditor5/blob/master/packages/ckeditor5-ui/src/editableui/inline/inlineeditableuiview.ts https://github.com/facebook/lexical/blob/main/packages/lexical-react/src/shared/LexicalContentEditableElement.tsx

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.

## What happens

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.

## Why it fails

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.

## Who is affected

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.

## What automation and AI miss

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.

## Before: an editable div with no role

```html
<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>
```

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.

## Before: the editor library's own name

```html
<label for="message">Message</label>
<div class="toolbar" role="toolbar" aria-label="Formatting">
  …
</div>
<div id="message" class="editor" contenteditable="true" role="textbox" aria-label="Rich Text Editor. Editing area: main">
  <p>The parcel arrived with a cracked lid.</p>
</div>
```

The library has made the box a text box and named it, but with its own words. "Message" is still only on screen.

## After: a text box that points at its label

```html
<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>
```

`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.

## After: the label's words copied in the editor's settings

```html
<div>Message</div>
<div class="toolbar" role="toolbar" aria-label="Formatting">
  …
</div>
<div id="message" class="editor" contenteditable="true" role="textbox" aria-multiline="true" aria-label="Message">
  <p>The parcel arrived with a cracked lid.</p>
</div>
```

Many editor libraries take a name for the typing area in their settings: CKEditor 5 has a label setting, and ProseMirror, which Tiptap is built on, passes any attribute to the typing area through its `attributes` option. Set it to the label's words, add `role` and `aria-multiline` the same way where the library doesn't, and the HTML above is the result. The box is a text box named "Message", so a screen reader and voice control get the word on screen. It works only while the two copies say the same thing. The label and the setting usually live in different files, so when the label is reworded or translated and the setting isn't, the name keeps the old words and voice control stops responding to the new ones.

## Which option to use

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.

## Implementation decisions

**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

1. Inspect each editor's editable element in the browser's accessibility tree and check its role is a multi-line text box and its name is the label shown beside it, starting with the same words.
2. With a screen reader, Tab into each editor and check the visible label is read with the role, and that two editors on one page are read with different names.
3. With voice control, say "Click" followed by each editor's visible label and check that editor takes focus.
4. Reword one label in the code, or switch the page's language, then repeat step 1 and check the name has changed with it.

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](/learn/keyboard-traps). A Bold button that doesn't say whether it is on is covered in [toggle buttons that don't say whether they are pressed](/learn/toggle-buttons), and toolbar icons with no name in [icon-only buttons with no accessible name](/learn/icon-buttons). 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](/learn/alt-text-prompts). Labels on native fields are covered in [form fields with missing or mismatched labels](/learn/form-labels). Test the complete journey with your target assistive technology.

---

Get this guide in your coding agent: MCP https://easeweb.dev/mcp (setup at https://easeweb.dev/services/developers)
