affectUI › Components › Agents

Inline Diff

AI edits to prose shown inline – struck and added words you accept or reject one by one, with keys, a popover and undo.

Part of affectUI Pro. Install: npx shadcn@latest add https://www.affectui.com/r/inline-diff.json · Import from @/components/ui/inline-diff

When to use

  • An AI rewrote a piece of prose – an email, a paragraph, a product description – and people should approve each edit
  • "Make it shorter", "fix the tone", "fix grammar" actions where most edits are small and scattered through the text
  • Review steps in an agent flow where the result should only change with an explicit yes

When not to use

  • Code or line-based text – use a line diff with gutters instead
  • Full rewrites where almost every word changes – show the new version next to the old one
  • Live typing suggestions inside an editor – this reviews a finished suggestion, it isn't a text field

Usage

import { InlineDiff } from "@/components/ui/inline-diff";

export function ReviewRewrite({ draft, rewrite }: { draft: string; rewrite: string }) {
  return (
    <InlineDiff
      label="Suggested by AI · Make it shorter"
      original={draft}
      suggested={rewrite}
      onChange={(text) => saveDraft(text)}
      onDone={(text) => setFinal(text)}
    />
  );
}

Props

NameTypeDefaultDescription
originalstring""The text as it is now
suggestedstring""The text the AI suggests – diffed word by word against original
changesDiffSegment[]Pre-computed segments ({ type: "equal", text } or { type: "change", remove, insert }) instead of original and suggested
onChange(text: string) => voidCalled on every decision and undo with the text so far – pending changes keep the original words
onDone(text: string) => voidCalled when the last pending change is decided, with the final text
labelstringSmall line on the gray tray above the card, e.g. "Suggested by AI · Make it shorter"
refRef<InlineDiffHandle>highlight(index | null), accept(index), reject(index), acceptAll(), rejectAll(), undo(), reset()
classNamestringExtra class on the root

Keyboard

  • Tab / Shift+Tab: Move between pending changes
  • ‹ / ›: Toolbar arrows: highlight the previous / next pending change (wrapping around) and open its popover
  • Y / Enter: Accept the focused change and move to the next one
  • N / Backspace: Reject the focused change and move to the next one
  • Esc: Leave the change or clear the arrow highlight

Accessibility

  • Every pending change is a focusable button labelled like "Suggestion 2 of 4: replace “available” with “live”. Press Y to accept, N to reject"
  • Removed words are a <del>, new words an <ins>; the side that was dropped is hidden from screen readers once decided
  • Each decision, Accept all, Reject all and Undo are announced in a polite live region with the number left
  • After the last decision focus moves to Undo, so the keyboard is never stranded; the hidden toolbar is inert
  • Popover buttons are labelled and keep focus on the change; visible focus rings throughout
  • The prev/next arrows never steal focus: a focused change hands focus to the next one, otherwise the change is highlighted while focus stays on the arrow
  • Deleted words are red and struck, added words green – color is never the only cue (strike-through, <del> and <ins>)

Responsive

  • Fills its container; the paragraph wraps freely and changes wrap across lines with their tints
  • The popover stays inside the card horizontally, down to 320px wide
  • Below 400px the prev/next arrows step aside; on touch screens the popover buttons grow and drop the key hints
  • Dark mode built in: switches when an ancestor (usually <html>) has the class dark or data-theme="dark"

Requirements

  • React 19

Related

  • Writing Assistant – AI chat card with a soft gradient glow, suggestions, streaming replies and live voice dictation
  • Change Summary – What moved since last time – one metric per row with a colored trend
  • Approval Card – An agent asks before it acts – risk badge, preview, Approve and Deny with shortcuts, a pausing countdown, and a smooth morph into a resolved row

Guides