Visual primitive
Text diff
<DomTextDiff>A standalone text comparison for AI rewrites, code patches, editorial review, and before-and-after copy changes.
Playground
Compare text live
Text diff playground
Switch between the complete diff, current text, and finished replacement. Inline, unified, and split still control how the diff itself is arranged.
<script setup>
import { reactive } from 'vue';
import { DomTextDiff } from '@getdom/studio/vue';
const data = reactive({
"original": "Write better pages with a flexible visual editor.",
"proposed": "Create clearer pages with an editor built for focused teams.",
"mode": "diff",
"view": "inline",
"format": "text",
"originalLabel": "Current",
"proposedLabel": "Suggested",
"showHeader": true,
"showModeSwitcher": true,
"framed": true
});
</script>
<template>
<DomTextDiff
v-bind="data"
/>
</template>Code
Cursor-style file edit
Use the unified code view for a compact patch with line numbers and word-level emphasis inside changed lines.
app/metadata.js
export async function generateMetadata(page) { const description = page.seoDescription || page.description; return { title: page.title, description: page.description, title: page.seoTitle || page.title, description, robots: page.noIndex ? 'noindex' : 'index', };}<script setup>
import { DomBadge, DomTextDiff } from '../../../lib/vue';
const original = `export async function generateMetadata(page) {
return {
title: page.title,
description: page.description,
};
}`;
const proposed = `export async function generateMetadata(page) {
const description = page.seoDescription || page.description;
return {
title: page.seoTitle || page.title,
description,
robots: page.noIndex ? 'noindex' : 'index',
};
}`;
</script>
<template>
<div class="w-full max-w-3xl overflow-hidden rounded-xl border border-border bg-canvas shadow-lg">
<header class="flex items-center justify-between gap-3 border-b border-border bg-secondary/35 px-4 py-2.5">
<div class="flex min-w-0 items-center gap-2">
<svg class="size-4 shrink-0 text-muted-fg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true">
<path d="M6 3h9l4 4v14H6V3Zm9 0v5h4M9 12h6M9 16h6" />
</svg>
<p class="truncate font-mono text-xs font-medium text-canvas-fg">app/metadata.js</p>
</div>
<DomBadge label="AI edit" tone="primary" size="sm" />
</header>
<DomTextDiff
:original="original"
:proposed="proposed"
view="unified"
format="code"
original-label="Current file"
proposed-label="Proposed edit"
:framed="false"
/>
</div>
</template>
Writing
Word processor suggestion
Keep review actions app-owned while DomTextDiff renders the semantic insertion and deletion markup.
Launch announcement
Draft · Product update
A clearer way to publish
The opening paragraph has a suggestion ready for review.
<script setup>
import { ref } from 'vue';
import { DomBadge, DomButton, DomTextDiff } from '../../../lib/vue';
const current = 'Our platform gives teams many tools that can be used to create, review, and publish content in a number of different ways.';
const suggested = 'Give every team one focused place to create, review, and publish content with confidence.';
const decision = ref('');
/**
* Records the reviewer's decision for the suggestion demo.
*
* @param {'accepted'|'rejected'} value Review decision.
* @returns {void}
*/
function decide(value) {
decision.value = value;
}
</script>
<template>
<article class="w-full max-w-2xl rounded-xl border border-border bg-canvas shadow-lg">
<header class="flex flex-wrap items-center justify-between gap-3 border-b border-border px-5 py-3">
<div>
<p class="text-sm font-semibold text-canvas-fg">Launch announcement</p>
<p class="mt-0.5 text-xs text-muted-fg">Draft · Product update</p>
</div>
<DomBadge label="AI suggestion" tone="primary" size="sm" />
</header>
<div class="space-y-4 p-5">
<div class="border-b border-border pb-4 text-[15px] leading-7 text-canvas-fg">
<p class="font-serif text-2xl font-semibold tracking-tight">A clearer way to publish</p>
<p class="mt-3 text-muted-fg">The opening paragraph has a suggestion ready for review.</p>
</div>
<DomTextDiff
:original="current"
:proposed="suggested"
view="inline"
original-label="Current paragraph"
proposed-label="Suggested rewrite"
/>
<footer class="flex items-center justify-between gap-3">
<p class="text-xs text-muted-fg">
{{ decision ? `Suggestion ${decision}` : 'Review the wording before changing the document.' }}
</p>
<div class="flex items-center gap-2">
<DomButton size="sm" variant="secondary" @click="decide('rejected')">Reject</DomButton>
<DomButton size="sm" variant="primary" @click="decide('accepted')">Accept</DomButton>
</div>
</footer>
</div>
</article>
</template>
SEO
Meta description rewrite
Compare the current metadata value with an AI replacement without embedding acceptance or persistence rules in the diff primitive.
Meta description
AI found a clearer description for the search result.
115 / 160 characters
<script setup>
import { DomButton, DomTextDiff } from '../../../lib/vue';
const currentDescription = 'DOM Studio is a collection of Vue components that helps developers build websites and apps.';
const suggestedDescription = 'Build polished Vue application interfaces with editable components, practical blocks, and source your team can own.';
</script>
<template>
<div class="w-full max-w-2xl rounded-xl border border-border bg-canvas p-5 shadow-sm">
<div class="flex flex-wrap items-start justify-between gap-3">
<div>
<p class="text-sm font-semibold text-canvas-fg">Meta description</p>
<p class="mt-1 text-xs leading-5 text-muted-fg">AI found a clearer description for the search result.</p>
</div>
<span class="rounded-full bg-primary/10 px-2.5 py-1 text-xs font-medium text-primary">SEO suggestion</span>
</div>
<DomTextDiff
class="mt-4"
:original="currentDescription"
:proposed="suggestedDescription"
view="inline"
original-label="Current description"
proposed-label="Suggested replacement"
/>
<div class="mt-4 flex flex-wrap items-center justify-between gap-3">
<p class="text-xs text-muted-fg">{{ suggestedDescription.length }} / 160 characters</p>
<div class="flex items-center gap-2">
<DomButton size="sm" variant="secondary">Keep current</DomButton>
<DomButton size="sm" variant="primary">Use suggestion</DomButton>
</div>
</div>
</div>
</template>
Reference
Props
Control props
| Name | Type | TS | Default | Description |
|---|---|---|---|---|
original | string | string | '' | Current or original text. |
proposed | string | string | '' | Suggested replacement text. |
mode | 'diff' | 'original' | 'replacement' | string | 'diff' | Show the complete diff, current text, or finished replacement. |
view | 'inline' | 'unified' | 'split' | string | 'inline' | Inline prose, unified patch, or side-by-side comparison. |
format | 'text' | 'code' | string | 'text' | Use proportional text or code-oriented monospace styling. |
originalLabel | string | string | 'Original' | Label for the original content. |
proposedLabel | string | string | 'Suggested' | Label for the proposed content. |
showHeader | boolean | boolean | true | Show comparison labels and change counts. |
showModeSwitcher | boolean | boolean | true | Show the Diff, Current, and Replacement controls inside the standard header. |
framed | boolean | boolean | true | Render the standard border, radius, and canvas background. |
Auto-generated from Text diff.props and inline _edit hints.
Events
| Name | Payload | Description |
|---|---|---|
| @update:mode | 'diff' | 'original' | 'replacement' | Fired when the active content mode changes. |
| @mode-change | 'diff' | 'original' | 'replacement' | Fired after the viewer switches between the diff, current text, and replacement text. |
Names auto-detected from defineEmits and source emit() calls; payload and description from __doc.events when present.
Slots
| Name | Scope | Description |
|---|---|---|
| #header | { additions, removals, mode, setMode, originalLabel, proposedLabel } | Replace the comparison header and optionally control the active content mode. |