Overview

Diffs is a library for rendering code and diffs on the web. This includes both high-level, easy-to-use components, as well as exposing many of the internals if you want to selectively use specific pieces. We've built syntax highlighting on top of Shiki which provides a lot of great theme and language support.

We have an opinionated stance in our architecture: browsers are rather efficient at rendering raw HTML. We lean into this by having all the lower level APIs purely rendering strings (the raw HTML) that are then consumed by higher-order components and utilities. This gives us great performance and flexibility to support popular libraries like React as well as provide great tools if you want to stick to vanilla JavaScript and HTML. The higher-order components render all this out into Shadow DOM and CSS grid layout.

Generally speaking, you're probably going to want to use the higher level components since they provide an easy-to-use API that you can get started with rather quickly. We currently only have components for vanilla JavaScript and React, but will add more if there's demand.

For this overview, we'll talk about the vanilla JavaScript components for now but there are React equivalents for all of these.

Rendering Diffs

Our goal with visualizing diffs was to provide some flexible and approachable APIs for how you may want to render diffs. For this, we provide a component called FileDiff.

There are two ways to render diffs with FileDiff:

  1. Provide two versions of a file or code snippet to compare
  2. Consume a patch file

You can see examples of these approaches below, in both JavaScript and React.

Merge conflict resolution UI

Render conflicts through a dedicated diff primitive that treats current and incoming sections as structured additions/deletions without running text diffing. Resolve by choosing current, incoming, or both changes and preview the updated file instantly.

Installation

Diffs is published as an npm package. Install Diffs with the package manager of your choice:

Package Exports

The package provides several entry points for different use cases:

PackageDescription
@pierre/diffsVanilla JS components, plus utility functions
@pierre/diffs/reactReact components for rendering diffs and files
@pierre/diffs/editLow-level edit mode Editor for attaching editing to rendered file and diff components
@pierre/diffs/ssrServer-side rendering utilities for pre-rendering diffs with syntax highlighting
@pierre/diffs/workerWorker pool utilities for offloading syntax highlighting to background threads

Build with agents

Agent skill

Install the diffs agent skill with the Skills CLI for access to the entire API and common recipes, regardless of your integration method.

Prompt an agent

Alternatively, you can copy-paste this prompt to your agent to install the skill and point it to these docs as plain-text.

The "For Agents" button on the home page copies the same prompt.

Plain-text docs

Our documentation is also available in condensed, Markdown-formatted plain text, available in two versions:

  • llms.txt is a short index of the sections
  • llms-full.txt is the everything in the docs — prose, API tables, and code examples

Copy and paste as needed, or provide the URLs to your agent.

Core Types

Before diving into the components, it's helpful to understand the core file, diff, and annotation data structures used throughout the library.

FileContents

FileContents represents one existing file version. Use it when rendering a file with the <File> component, or pass it as oldFile and/or newFile to diff components. For added or deleted files, pass null for the intentionally missing side.

An omitted side is not the same as null. If you provide either oldFile or newFile, provide the other side too, using null only when that file side does not exist. Empty file contents are still a real file; represent them with contents: '', not null.

For rendering and Worker Pool caching, cacheKey is optional; when provided, treat it as a revision identity and change it with the contents, filename, language, or revision.

FileDiffMetadata

FileDiffMetadata represents the differences between file versions. It contains the hunks (changed regions), line counts, and optionally the full file contents for expansion (if possible).

When a component uses loadDiffFiles, treat FileDiffMetadata as mutable render metadata. A partial metadata object parsed from a patch can be upgraded in place: isPartial flips to false, hunks and line arrays are replaced with hydrated values, and the object identity is preserved.

If you reparse a patch or create a new partial FileDiffMetadata, the renderer treats it as a fresh partial model. Keep the same metadata object stable when you want hydration to persist.

Hydration appends :hydrated to the partial metadata's cacheKey. Without a partial key, changed diffs derive a key from both loaded file keys and parseDiffOptions.disableLineMatching. Pure renames use the loaded new file's key. Keep parseDiffOptions.disableLineMatching consistent with the options used to parse the supplied patch.

Tip: You can generate FileDiffMetadata using parseDiffFromFile (from file contents) or parsePatchFiles (from a patch string).

LineAnnotation and DiffLineAnnotation

LineAnnotation<T> places content on a file line and contains lineNumber plus typed metadata. Metadata is required when T is a concrete type and omitted when T is undefined. DiffLineAnnotation<T> adds side: 'additions' | 'deletions' to select a file side. Line coordinates are one-based on the selected file side, not row positions in the rendered diff. Use lineNumber: 0 for a file-level annotation above the first file line or, in a diff, above the first hunk or row on that side.

Callbacks shared by file and diff components use LineAnnotation[] | DiffLineAnnotation[]. Use isFileAnnotationCollection or isDiffAnnotationCollection to narrow the collection before reading shape-specific fields. For individual annotation unions, use isFileAnnotation or isDiffAnnotation.

Store a stable, position-independent application ID in metadata when an annotation owns drafts or other interactive state. For annotations that survive an edit, edit mode preserves metadata while remapping line coordinates. For the controlled update pattern and exact remapping rules, see Line annotations.

Creating Diffs

There are two ways to create a FileDiffMetadata.

From File Contents

Use parseDiffFromFile when you have the full file contents. Pass both sides for a changed file, oldFile: null for a new file, or newFile: null for a deleted file. This approach allows collapsed regions to be expanded.

From a Patch String

Use parsePatchFiles when you have a unified diff or patch file. This is useful when working with git output or patch files from APIs. Patch-derived metadata is partial until a renderer hydrates it with full files from loadDiffFiles.

Tip: If you need to change the language after creating a FileContents or FileDiffMetadata, use the setLanguageOverride utility function.

Highlighters

@pierre/diffs supports three syntax highlighting backends. Select one with preferredHighlighter; each backend loads on demand.

BackendDescription
shiki-jsThe default. Shiki with its JavaScript regex engine.
shiki-wasmShiki with its WebAssembly regex engine.
highlightsWebAssembly lexers from Highlights.

Set preferredHighlighter in the component's options:

highlights and shiki-wasm need WebAssembly, and highlights also needs SIMD. Some browsers lack these, and some pages block WebAssembly with a Content Security Policy. When you select highlights, we recommend checking canUseWASM() first and falling back to shiki-js when it returns false, as in the example above.

The same option works with React and vanilla components, edit mode, streaming, and SSR preload helpers. When using a worker pool, set preferredHighlighter in the pool's highlighterOptions; the pool controls the backend used by its components. Highlights usually performs best without a pool.

Only one backend can be active on a page, in each worker, and in each SSR server thread. preferredHighlighter selects the backend for the first initialization. Later requests use the active backend, even with a different preference. If no backend or preference exists, the default is shiki-js. Dispose every instance before you select another backend. getHighlighterType() returns the active backend.

Built-in theme names work across all three backends. Custom TextMate themes and grammars require Shiki; Highlights uses Zed themes and bundled lexers. See Themes for configuration and Highlights for its APIs and supported languages.

Try the backends in the diffs playground.

React API

Import React components from @pierre/diffs/react.

We offer a variety of components to render diffs and files. Many of them share similar types of props, which you can find documented in Shared Props.

Components

The React API exposes six main components:

  • CodeView renders a mixed, virtualized list of files and diffs inside one scroll container
  • MultiFileDiff compares file contents directly
  • PatchDiff renders from a patch string
  • FileDiff renders a pre-parsed FileDiffMetadata
  • File renders a single code file without a diff
  • UnresolvedFile renders merge conflict markers with built-in resolution UI
    • Currently in beta/experimental and may change in future releases.