# AI rewrite buttons that replace text with no preview or undo

Source: https://easeweb.dev/learn/ai-rewrites
Topics: Forms > AI rewrites of a person's text can be checked and undone (WCAG 3.3.4)
The fix: Either show the rewrite under the person's text with "Use this version" and "Keep mine", and change nothing until they choose, or keep the version it replaces and leave a "Restore my text" button on the page. The Restore button passes too, but the rewrite is saved and shown to others until someone notices and restores it.
Test with: keyboard, screen reader, zoom
References: https://www.w3.org/WAI/WCAG22/Understanding/error-prevention-legal-financial-data.html https://www.w3.org/WAI/WCAG22/Techniques/general/G98 https://www.w3.org/WAI/WCAG22/Techniques/general/G99 https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand https://www.nngroup.com/articles/user-mistakes/

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 site for freelancers gives each person a public profile, and clients read its About text before they get in touch. The page saves as you go. A bookkeeper has written their own: "I do the books for small shops and cafés, part-time, in the evenings. I reply to messages within two days." Under the box is a button, "Improve with AI".

They press it, hoping for tidier sentences. The box now says: "Experienced bookkeeper helping small businesses thrive. Available any time, I reply to every message within the hour." It reads well, and it has already been saved. It also says two things they never said: that they are available any time, and that they reply within the hour. Their own text is gone. There is no Undo, and in Chrome, Ctrl+Z leaves the new text where it is. Clients now read promises the bookkeeper can't keep, and to get their words back they have to remember them and type them again.

## Why it fails

Success criterion 3.3.4, Error Prevention (Legal, Financial, Data), is level AA. It covers pages that change or delete user-controllable data: data people can see and change themselves, such as the name and address on their account, or the About text on their profile. When a page changes that data, at least one of three things must be true. The change can be reversed; the entries are checked for input errors and people can correct them; or there is a way to review, confirm and correct the information before it is final. The button does none of them.

W3C's Understanding page for 3.3.4 says it doesn't ask for a confirmation every time someone saves. When people type and save, the change is the one they made, and they have seen it. Here the change is written by software, and the person meets it only after it has replaced their text in storage. Two of W3C's sufficient techniques for 3.3.4 fit this change: a step to review and correct the answers before submitting (G98), which W3C lists for legal transactions and tests, and a way to recover deleted information (G99), which it lists for actions that delete information. Checking for input errors, the criterion's second way, can't help: the rewrite is valid text, and only the person can tell it says something else.

The browser can't help either. Ctrl+Z undoes what someone typed into a field. When a script sets the field's value, there is nothing for it to take back: in Chrome, the rewrite stays. The old text has also left the server, so a reload brings back only the new one.

## Who is affected

On a public profile, the rewrite is what clients read and what they hold the person to. A promise to reply within the hour has to be kept, or explained to each client who expected it.

## What automation and AI miss

The button has a name, the text box has a label, and the page passes every automated check. Whether a rewrite can be compared or undone shows only once the button is pressed, and the result depends on a model and a server. EaseWeb's audit flags AI writing buttons beside a text box (`ew:ai-rewrite-manual-check`) for a person to try in a test account; it never presses them, since on a live site that would change someone's text. Nothing in the Before's code is wrong to a linter or a type checker. Whether people can get their words back is a question about the product, and a code assistant asked to add the button isn't asked it.

## Before: Improve with AI saves over the text at once

```html
<label for="about">About you</label>
<textarea id="about" name="about">I do the books for small shops and cafés, part-time, in the evenings. I reply to messages within two days.</textarea>
<button type="button" id="improve">Improve with AI</button>
```

```js
const about = document.getElementById('about');
document.getElementById('improve').addEventListener('click', async () => {
  const res = await fetch('/ai/improve', { method: 'POST', body: about.value });
  const text = await res.text();
  // The rewrite replaces the person's text in the box, and is saved over it.
  about.value = text;
  await fetch('/profile/about', { method: 'PUT', body: text });
});
```

One press changes the saved profile. Nothing shows the new text beside the old one before it counts, nothing asks the person to choose, and the old text is kept nowhere, so there is nothing to undo.

## After: show the rewrite, and change nothing until it is chosen

```html
<label for="about">About you</label>
<textarea id="about" name="about">I do the books for small shops and cafés, part-time, in the evenings. I reply to messages within two days.</textarea>
<button type="button" id="improve">Improve with AI</button>
<div id="suggestion" hidden>
  <h2>Suggested by AI</h2>
  <p id="suggested-text"></p>
  <p>Your text above stays as it is until you choose. Check that this still says what you mean.</p>
  <button type="button" id="use">Use this version</button>
  <button type="button" id="keep">Keep mine</button>
</div>
```

```js
const about = document.getElementById('about');
const suggestion = document.getElementById('suggestion');
const suggested = document.getElementById('suggested-text');
document.getElementById('improve').addEventListener('click', async () => {
  const res = await fetch('/ai/improve', { method: 'POST', body: about.value });
  // Show the rewrite under the person's text, and change nothing yet.
  suggested.textContent = await res.text();
  suggestion.hidden = false;
});
document.getElementById('use').addEventListener('click', async () => {
  about.value = suggested.textContent;
  await fetch('/profile/about', { method: 'PUT', body: about.value });
  suggestion.hidden = true;
  about.focus();
});
document.getElementById('keep').addEventListener('click', () => {
  suggestion.hidden = true;
  about.focus();
});
```

The rewrite appears under the box as a suggestion, and the box keeps the person's own words, so both can be read, one after the other, before anything changes. Nothing is saved until "Use this version" is pressed; "Keep mine" closes the suggestion and leaves the profile as it was. Either way, focus goes back to the text box, so people carry on from where their text is.

## After: apply the rewrite, with a Restore button that stays

```html
<label for="about">About you</label>
<textarea id="about" name="about">I do the books for small shops and cafés, part-time, in the evenings. I reply to messages within two days.</textarea>
<button type="button" id="improve">Improve with AI</button>
<p id="rewritten" hidden>
  Rewritten with AI. Your own text is kept.
  <button type="button" id="restore">Restore my text</button>
</p>
```

```js
const about = document.getElementById('about');
const rewritten = document.getElementById('rewritten');
document.getElementById('improve').addEventListener('click', async () => {
  const res = await fetch('/ai/improve', { method: 'POST', body: about.value });
  const text = await res.text();
  about.value = text;
  // The server keeps the version this replaces, so Restore works after a reload too.
  await fetch('/profile/about', { method: 'PUT', body: text });
  rewritten.hidden = false;
});
document.getElementById('restore').addEventListener('click', async () => {
  const res = await fetch('/profile/about/restore', { method: 'POST' });
  about.value = await res.text();
  rewritten.hidden = true;
  about.focus();
});
// Once the person edits the text themselves, the rewrite is theirs.
about.addEventListener('input', () => {
  rewritten.hidden = true;
});
```

The rewrite is saved at once, as before, but the change can be taken back: the server keeps the text it replaced, and "Restore my text" brings it back word for word. The line under the box says what happened and stays until the person restores the text or edits it themselves; when the page is loaded again with the rewrite still in place, the server renders the line without `hidden`. This holds only while the old version is really kept, and while Restore stays where the change was made, not in a toast that goes. Even then, the rewrite is live on the profile until someone restores it, and people who can't tell what changed may never press the button.

## Which option to use

Both pass 3.3.4. Use the suggestion: nothing the person wrote changes, or reaches clients, until they choose, and they choose with both versions in front of them. Restore is not enough on its own, because the rewrite is public until someone notices it. Keep a Restore button as well when a rewrite can be applied in other ways, such as from a menu or a shortcut, so every way in has a way back. A dialog that asks "Replace your text?" before the rewrite is written isn't enough here: W3C accepts a confirmation for a delete (G168), but this one asks people to approve text they haven't seen.

## Implementation decisions

Put the suggestion straight after the button in the page, so the next Tab or arrow press reaches it. The wait while the model writes, and saying that the suggestion is ready, are their own failures: see [loading spinners that screen readers never announce](/learn/loading-states) and [status messages that are shown but never announced](/learn/status-messages).

Marking what changed helps people compare, especially when only a few words differ. Mark it with more than colour, such as bold or an underline, and keep the plain text of the suggestion as the thing that is used.

A summary or other generated text that should sit beside the person's text, such as a summary of meeting notes, goes in its own field or above the notes. Never write it into the notes themselves.

Some editors show a suggestion as grey text in the box and accept it with a key. If yours does, insert the accepted text with `document.execCommand('insertText', false, text)` rather than setting the value: in our test in Chromium, Ctrl+Z then brought back what was there before. Setting the value, or calling `setRangeText()`, left the new text in place after Ctrl+Z in the same test. MDN marks `execCommand` as deprecated, so keep the call in one place you can replace. Keep a Restore or a suggestion step as well, since Ctrl+Z is gone once the page is reloaded.

Don't put Restore in a toast. A toast that removes itself takes the only way back with it; see [toasts that vanish before you can read or undo them](/learn/toast-timing). The same care applies to payments and other data a slip would cost people: see [checkouts that take payment with no review or undo](/learn/error-prevention).

## Verify the fix

1. In a test account, save a few sentences with facts in them, such as your hours or how fast you reply, and press the AI button. Confirm your saved text and the public profile are unchanged until you choose the new version, or that the change is shown with a way to restore your text.
2. Choose "Keep mine", or "Restore my text", and reload the page. Confirm your own text is back word for word. Where there is a Restore line, also reload once before restoring, and confirm the line is still there.
3. With a keyboard alone, press the AI button, reach the suggestion or the Restore button, use it, and confirm focus goes back to the text box.
4. With a screen reader, confirm you can read the suggestion, and still reach your own text, before the button that uses it, and that each button's name says what it does.
5. At 400% zoom or on a phone, confirm you can read your text and the suggestion one straight after the other, and reach every button that decides.

Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the AI feature, its prompt or the editor changes.

## Limits

This is a starting pattern, not a guarantee for every product. The example is a profile's About text; drafts, listings, messages, notes and documents need the same care wherever an AI button writes into text that is kept. A suggestion step only helps people who read it: the model can still be wrong in ways the person doesn't notice. Whether AI-written text must be labelled for its readers is outside WCAG. Test the complete feature, including the shortcuts and menus that apply a rewrite, with the people and tools your users rely on.

---

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