Sections / Gems / AI interaction primitives
Ink Diff
Rewrite-with-AI for a paragraph, shown as an edit rather than a replacement. Your model's new text plays back as an inline diff: struck words get a pen line through them and fold away, new words unfold and ink in. Then the reader reviews it change by change and accepts or rejects each one, with the mouse or the keyboard. Your model plugs in through one callback; without it a labelled demo applies a fixed plain-English word list and says so.
- Kind
- AI interaction primitives
- Interaction
- inline diff · accept or reject · streaming · keyboard review · your model via callback · editable
- Dependencies
- None
- Licence
- Licensed with Full Access, for the sites you build; do not resell the gem itself
Formats
- Webflow (native)
- Figma (layers)
- Framer component
- HTML
- Prompts for Claude Code, Cursor, Lovable, v0, Bolt
Copy to Webflow pastes a native section with the gem in an Embed inside it. Copy to Figma pastes a frame of its best moment, text and controls as layers. Copy to Framer pastes a component with its options as controls. HTML is one self-contained snippet. Each prompt (Claude Code, Cursor, Lovable, v0, Bolt) carries the exact files, so your agent installs this gem, not a lookalike. With the MCP server, ask your agent for get_section("ink-diff").
Behaviour and accessibility
- The text is an editable, labelled text box until a rewrite starts; Ctrl or Cmd+Enter rewrites, Escape or Stop cancels (the signal you got is aborted) and leaves the text untouched.
- Review is a labelled group: arrow keys move between changes, Accept and Reject resolve the current one, Accept all finishes; each change is announced as what it removes and adds, and the status line is a status role.
- Without onRewrite the gem shows a 'Demo mode · fixed word list, not a model' badge and only swaps a fixed list of wordy phrases for plain ones. It never pretends to be a model.
- Nothing runs between rewrites: frames are drawn only while text streams in or a change is moving, and never off screen.
- With prefers-reduced-motion: no pen line, folding or ink; changes appear at once, ready to review.
Wire it to your model
onRewrite(text, instruction, { signal }) returns the full rewritten text: a fetch Response (its streamed body is read as text), a ReadableStream, an async iterable of strings, or a plain string, sync or promised. Pass signal to fetch so Stop cancels the request. The gem diffs it against the original word by word; onDone(text) gets the result once every change is reviewed.
Options
| Option | Type | Default | What |
|---|---|---|---|
text | string | "Our platform is designed in order to help teams of all sizes to really streamline the way that they actually manage their projects. With a very wide range of features, it basically makes it possible for you to utilize your time in a more efficient manner, so that you are able to focus on the things that really matter." | The paragraph to rewrite. Blank lines separate paragraphs. The reader can edit it before rewriting. |
instructions | string[] | 4 built in | One-tap instructions offered above the text. |
onRewrite | (text: string, instruction: string, ctx: InkContext) => InkStream | Promise<InkStream> | Your model: return the full rewritten text (streamed or not). Unset = labelled demo mode. | |
onDone | (text: string) => void | Called with the text once every change has been accepted or rejected. | |
theme | 'light' | 'dark' | "light" | Colour scheme. |
ink | string | "#2440d8" | The ink colour: new words, the pen line and the active change. |
stagger | number | 240 | Milliseconds between one change and the next during playback. |
label | string | "Text to rewrite" | Accessible name of the text box. |
demoLabel | string | "Demo mode · fixed word list, not a model" | The badge shown in demo mode. |

Free
