I needed a way to show text differences in my Vue 3 apps, so I built this wrapper around the fantastic jsdiff library by @kpdecker. It gives you five different ways to highlight changes between text blocks, from character-level precision to sentence-level overview.
Looking for PHP? Check out the sister package php-diff-text — a 1:1 PHP port with the same diffing strategies.
- Works with Vue 3 Composition API
- Six different diff strategies: characters, words, words with spaces, lines, sentences, and HTML
- Perfect for text, documents, and prose comparisons
- Easy to customize with CSS variables
- Full TypeScript support
- Lightweight (just a thin wrapper around jsdiff)
- Pass any options that jsdiff supports
npm install vue-diff-textTo run this demo locally: git clone this repo, cd demo, npm install, then npm run dev
Each component uses a different diffing strategy depending on what level of detail you need:
DiffChars - Shows every single character change. Great for catching typos or small edits.
DiffWords - Highlights word-level changes but ignores whitespace. Perfect for most text editing scenarios.
DiffWordsWithSpace - Like DiffWords but also shows whitespace changes. Useful when formatting matters.
DiffLines - Shows entire line changes. Good for comparing plain text files or when you want a high-level overview.
DiffSentences - Highlights sentence-level changes. Nice for prose and document editing.
DiffHtml - Compares HTML content and highlights markup differences. Perfect for rich text editing and HTML content changes. Supports a similarity-threshold prop to gracefully handle complete content replacements by showing old text as fully deleted and new text as fully added when texts are too dissimilar.
⚠️ Important: You should import the CSS file for styling to work. It's not mandatory, you CAN implement the classes yourself.
import 'vue-diff-text/dist/style.css'
<template>
<div>
<!-- Pick whichever diff type makes sense for your use case -->
<DiffChars :old-text="oldText" :new-text="newText" />
<DiffWords :old-text="oldText" :new-text="newText" />
<DiffWordsWithSpace :old-text="oldText" :new-text="newText" />
<DiffLines :old-text="oldText" :new-text="newText" />
<DiffSentences :old-text="oldText" :new-text="newText" />
<DiffHtml :old-text="oldHtml" :new-text="newHtml" />
</div>
</template>
<script setup>
import { DiffChars, DiffWords, DiffWordsWithSpace, DiffLines, DiffSentences, DiffHtml } from 'vue-diff-text'
import 'vue-diff-text/dist/style.css'
const oldText = "Hello world"
const newText = "Hello Vue world"
const oldHtml = '<p>Welcome to our <strong>website</strong>!</p>'
const newHtml = '<p>Welcome to our <strong>amazing website</strong>!</p>'
</script>Since this is just a wrapper around jsdiff, you can pass any options that jsdiff supports:
<template>
<div>
<!-- Ignore case differences -->
<DiffWords
:old-text="oldText"
:new-text="newText"
:options="{ ignoreCase: true }"
/>
<!-- Ignore whitespace when comparing lines -->
<DiffLines
:old-text="oldText"
:new-text="newText"
:options="{ ignoreWhitespace: true }"
/>
</div>
</template>
<script setup>
import { DiffWords, DiffLines } from 'vue-diff-text'
import 'vue-diff-text/dist/style.css'
const oldText = "Hello WORLD"
const newText = "hello world"
</script>All components take the same props:
old-text(required) - The original textnew-text(required) - The new text to compareoptions(optional) - Any options to pass to jsdiffsimilarity-threshold(optional, DiffHtml only) - Number between 0 and 1. When set, if the text similarity falls below this threshold, the diff renders as a full replacement (all old text deleted, all new text added) instead of word-level diffing. Recommended value:0.3. Default:null(disabled).
The options you can pass depend on which diff type you're using. Here are the most common ones:
For most components:
ignoreCase: true- Ignore case differencesignoreWhitespace: true- Ignore whitespace differences
For DiffLines:
newlineIsToken: true- Treat newlines as separate tokens
Check the jsdiff docs for the complete list of what each diff type supports.
When text is completely rewritten, word-level diffs can produce unreadable results by finding
incidental word matches. The similarity-threshold prop fixes this:
<DiffHtml
:old-text="oldHtml"
:new-text="newHtml"
:similarity-threshold="0.3"
/>When set, DiffHtml computes text similarity. If below the threshold, it shows the old text as fully deleted and the new text as fully added — just like Google Docs or GitHub handles complete rewrites. The threshold is a number between 0-1 (0.3 = 30% similarity).
You must import the CSS file for styling to work. Add this import to your component or main.js:
import 'vue-diff-text/dist/style.css'You can then customize the look in two ways:
Just override the CSS variables to change colors:
:root {
--text-diff-added-bg: #e6ffed;
--text-diff-added-color: #1b7332;
--text-diff-removed-bg: #ffe6e6;
--text-diff-removed-color: #d73a49;
--text-diff-removed-decoration: line-through;
}If you need more control, target these classes. Note that all styles are scoped under .text-diff:
.text-diff {
white-space: pre-wrap;
word-wrap: break-word;
/* Add your custom container styles here */
}
.text-diff .diff-added {
background-color: #e6ffed;
color: #1b7332;
font-weight: bold;
border-radius: 3px;
padding: 2px 4px;
}
.text-diff .diff-removed {
background-color: #ffe6e6;
color: #d73a49;
text-decoration: line-through;
border-radius: 3px;
padding: 2px 4px;
}The available classes are:
.text-diff- Main container (each component has this).text-diff .diff-added- Added text spans.text-diff .diff-removed- Removed text spans
Note: The DiffHtml component uses the same CSS classes (.diff-added and .diff-removed) as the other components for consistent styling.
Want to contribute or just mess around with the code? Here's how to get started.
You'll need Node.js 18+ and npm (or yarn, whatever you prefer).
git clone https://github.com/sitefinitysteve/vue-diff-text.git
cd vue-diff-text
npm installThe easiest way to develop is to use the demo app with hot reload:
cd demo
npm run devThis spins up a dev server (usually at http://localhost:5173) where you can see all the components in action. The demo is set up with Vite aliases so it points directly to the source files - no build step needed.
Just edit the files in src/components/ and your changes will show up instantly.
If you want to test the built version instead:
npm run build
cd demo
npm run devWhen you're ready to build:
npm run buildThis creates the dist files (ES module, UMD bundle, TypeScript declarations, and CSS).
The demo folder has a complete Vue 3 app for testing. It lets you:
- Edit text in real-time and see the diffs
- Compare all five diff types side by side
- Test different options
- Try it with longer text blocks
cd demo
npm install # First time only
npm run devvue-diff-text/
├── src/
│ ├── components/ # The five diff components
│ └── index.ts # Main entry point
├── demo/ # Test app
├── dist/ # Built files
└── package.json
Available scripts:
npm run dev- Start dev servernpm run build- Build for productionnpm run preview- Preview built version
For the demo (run from demo/):
npm run dev- Start demo servernpm run build- Build demonpm run preview- Preview built demo
- vue ^3.4.21 - Vue 3 framework
- diff ^5.2.0 - The core diffing library by @kpdecker that does all the heavy lifting
- diffblazer ^1.0.0 - Fast HTML diffing library used by the DiffHtml component
Found a bug or want to add a feature? Pull requests are welcome!
- Fork it
- Create your feature branch
- Make your changes
- Test it in the demo app
- Commit and push
- Open a pull request
MIT © Steve McNiven-Scott
Huge thanks to @kpdecker for creating and maintaining jsdiff. This library wouldn't exist without his excellent work on the underlying diffing algorithms.
- GitHub repo
- Issues
- Vue 3 docs
- jsdiff docs
- php-diff-text - PHP sister package (1:1 port)
- v-code-diff - For code diffing with syntax highlighting
