@pierre/diffs is an open source diff and code rendering library. It's built on Shiki for syntax highlighting and theming, is super customizable, and comes packed with features. Made with love by The Pierre Computer Company.
Currently v1.1.6
Choose from stacked (unified) or split (side-by-side). Both use CSS Grid and Shadow DOM under the hood, meaning fewer DOM nodes and faster rendering.
12 unmodified lines13141516171819202122232425262728293031323334353631 unmodified lines12 unmodified lines} from '../types';export function createSpanFromToken(token: ThemedToken) {const element = document.createElement('div');const style = getTokenStyleObject(token);element.style = stringifyTokenStyle(style);return element;}export function createRow(line: number) {const row = document.createElement('div');row.dataset.line = `${line}`;const lineColumn = document.createElement('div');lineColumn.dataset.columnNumber = '';lineColumn.textContent = `${line}`;const content = document.createElement('div');content.dataset.columnContent = '';row.appendChild(lineColumn);row.appendChild(content);return { row, content };}31 unmodified lines12 unmodified lines13141516171819202122232425262728293031323331 unmodified lines12 unmodified lines} from '../types';export function createSpanFromToken(token: ThemedToken) {const element = document.createElement('span');const style = token.htmlStyle ?? getTokenStyleObject(token);element.style = stringifyTokenStyle(style);element.textContent = token.content;element.dataset.span = ''return element;}export function createRow(line: number) {const row = document.createElement('div');row.dataset.line = `${line}`;const content = document.createElement('div');content.dataset.columnContent = '';row.appendChild(content);return { row, content };}31 unmodified lines
We built @pierre/diffs on top of Shiki for syntax highlighting and general theming. Our components automatically adapt to blend in with your theme selection, including across color modes.
123456789101112use std::io;fn main() {println!("What is your name?");let mut name = String::new();io::stdin().read_line(&mut name).unwrap();println!("Hello, {}", name.trim());}fn add(x: i32, y: i32) -> i32 {return x + y;}123456789101112use std::io;fn main() {println!("Enter your name:");let mut name = String::new();io::stdin().read_line(&mut name).expect("read error");println!("Hello, {}!", name.trim());}fn add(a: i32, b: i32) -> i32 {a + b}
Love the Pierre themes? Install our Pierre Theme pack with light and dark flavors, or learn how to build your own Shiki themes.
Your diffs, your choice. Render changed lines with classic diff indicators (+/–), full-width background colors, or vertical bars. You can even highlight inline changes—character or word based—and toggle line wrapping, hide numbers, and more.
1234567891011121314const std = @import("std");const Allocator = std.heap.page_allocator;const ArrayList = std.ArrayList;pub fn main() !void {const stdout_writer_instance = std.io.getStdOut().writer();try stdout_writer_instance.print("Hi You, {s}! Welcome to our application.\n", .{"World"});var list = ArrayList(i32).init(allocator);defer list.deinit();const configuration_options = .{ .enable_logging = true, .max_buffer_size = 1024, .timeout_milliseconds = 5000 };_ = configuration_options;}12345678910111213141516171819const std = @import("std");const GeneralPurposeAllocator = std.heap.GeneralPurposeAllocator;const ArrayList = std.ArrayList;pub fn main() !void {var gpa = GeneralPurposeAllocator(.{}){};defer _ = gpa.deinit();const allocator = gpa.allocator();const stdout_writer_instance = std.io.getStdOut().writer();try stdout_writer_instance.print("Hello There, {s}! Welcome to the updated Zig application.\n", .{"Zig"});var list = ArrayList(i32).init(allocator);defer list.deinit();try list.append(42);const configuration_options = .{ .enable_logging = true, .max_buffer_size = 2048, .timeout_milliseconds = 10000, .retry_count = 3 };_ = configuration_options;}
@pierre/diffs adapts to any font, font-size, line-height, and even font-feature-settings you may have set. Configure font options with your preferred CSS method globally or on a per-component basis.
39 unmodified lines40414243444543444745464711 unmodified lines39 unmodified lines return nil, false }
now := time.Now() expired := now.After(item.ExpiresAt) if expired { if time.Now().After(item.ExpiresAt) { delete(c.items, key) c.onEviction(key, item.Value) return nil, false }
11 unmodified linesSwap between the prebuilt hunk separator styles to preview how collapsed chunks are displayed.
7 unmodified lines89101112131421 unmodified lines3637383940414217 unmodified lines6061626364656614 unmodified lines7 unmodified linessummary.push('checkpoint-03');summary.push('checkpoint-04');summary.push('checkpoint-05');summary.push('phase:boot');summary.push('checkpoint-07');summary.push('checkpoint-08');summary.push('checkpoint-09');21 unmodified linessummary.push('checkpoint-31');summary.push('checkpoint-32');summary.push('checkpoint-33');summary.push('phase:mid');summary.push('checkpoint-35');summary.push('checkpoint-36');summary.push('checkpoint-37');17 unmodified linessummary.push('checkpoint-55');summary.push('checkpoint-56');summary.push('checkpoint-57');summary.push('phase:tail');summary.push('checkpoint-59');summary.push('checkpoint-60');summary.push('checkpoint-61');14 unmodified lines7 unmodified lines89101112131421 unmodified lines3637383940414217 unmodified lines60616263646566676814 unmodified lines7 unmodified linessummary.push('checkpoint-03');summary.push('checkpoint-04');summary.push('checkpoint-05');summary.push('phase:boot-ready');summary.push('checkpoint-07');summary.push('checkpoint-08');summary.push('checkpoint-09');21 unmodified linessummary.push('checkpoint-31');summary.push('checkpoint-32');summary.push('checkpoint-33');summary.push(`phase:mid-${tasks.length}`);summary.push('checkpoint-35');summary.push('checkpoint-36');summary.push('checkpoint-37');17 unmodified linessummary.push('checkpoint-55');summary.push('checkpoint-56');summary.push('checkpoint-57');if (tasks.length > 0) {summary.push(`phase:tail-${tasks[0].id}`);}summary.push('checkpoint-59');summary.push('checkpoint-60');summary.push('checkpoint-61');14 unmodified lines
Switch between lightweight header metadata and a fully custom header rendered inside the built-in data-diffs-header shell.
5 unmodified lines6789101112131415161718192021225 unmodified lineslet apiBaseURL: URLlet timeout: TimeIntervallet maxRetries: Intprivate init() {self.apiBaseURL = URL(string: "https://api.example.com")!self.timeout = 30.0self.maxRetries = 3}func headers() -> [String: String] {return ["Content-Type": "application/json","Accept": "application/json"]}}5 unmodified lines678910111213141516171819202122232425262728295 unmodified lineslet apiBaseURL: URLlet timeout: TimeIntervallet maxRetries: Intlet enableLogging: Boolprivate init() {self.apiBaseURL = URL(string: "https://api.example.com/v2")!self.timeout = 60.0self.maxRetries = 5self.enableLogging = true}func headers(token: String? = nil) -> [String: String] {var headers = ["Content-Type": "application/json","Accept": "application/json","X-API-Version": "2.0"]if let token = token {headers["Authorization"] = "Bearer \(token)"}return headers}}
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.
19 unmodified lines20212223232425262714 unmodified lines42434444454647454647484950515253545521 unmodified lines19 unmodified linesexport async function createSession(userId: string) { await cleanupExpiredSessions(userId);
|| <<<<<<< HEAD const data = {======= const sessionData = { source: 'web',>>>>>>> feature/oauth-session-source provider: 'password', userId, expiresAt: Date.now() + SESSION_TTL,14 unmodified lines if (oldest) await invalidateSession(oldest.id); }
|| <<<<<<< HEAD await db.auditLog.create({ event: 'session.created', userId, });======= await db.sessionEvent.create({ type: 'audit-log', data: { sessionId: session.id, type: 'created', source: sessionData.source ?? 'credentials', }, });>>>>>>> feature/oauth-session-source
return { session, token };}21 unmodified lines@pierre/diffs provide a flexible annotation framework for injecting additional content and context. Use it to render your own line comments, annotations from CI jobs, and other third-party content.
3 unmodified lines45677891011121314141516171819192021223 unmodified lines
SECRET_KEY = "your-secret-key"
def create_token(user_id: str, expires_in: int = 3600) -> str:def create_token(user_id: str, role: str = "user", expires_in: int = 3600) -> str: payload = { "sub": user_id, "role": role, "exp": time.time() + expires_in } return jwt.encode(payload, SECRET_KEY, algorithm="HS256")
def verify_token(token: str) -> Optional[str]:def verify_token(token: str) -> Optional[dict]: try: payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"]) if payload["exp"] < time.time(): return None return payload["sub"] return {"user_id": payload["sub"], "role": payload["role"]} except jwt.InvalidTokenError: return NoneShould we validate the role parameter? We could restrict it to a set of allowed values.
Good idea, maybe use a Literal type or an enum.
Agreed, we should also update verify_token to return the role.
Annotations can also be used to build interactive code review interfaces similar to AI-assisted coding tools like Cursor. Use it to track the state of each change, inject custom UI like accept/reject buttons, and provide immediate visual feedback.
5 unmodified lines678910910111213143 unmodified lines5 unmodified lines</head><body> <header> <h1>Welcome</h1> <p>Thanks for visiting</p> <h1>Welcome to Our Site</h1> <p>We're glad you're here</p> <a href="/about" class="btn">Learn More</a> </header> <footer> <p>© Acme Inc.</p>3 unmodified linesTurn on line selection with enableLineSelection: true. When enabled, clicking a line number will select that line. Click and drag to select multiple lines, or hold Shift and click to extend your selection. You can also control the selection programmatically. Also selections will elegantly manage the differences between split and unified views.
17 unmodified lines181920212223242526272829303132333435363738394011 unmodified lines17 unmodified lines~Vector() {delete[] data;data = nullptr;}void push_back(const T& value) {if (length >= capacity) {reserve(capacity * 2);}data[length++] = value;}T& operator[](size_t index) {if (index >= length) {throw std::out_of_range("Index out of bounds");}return data[index];}size_t size() const { return length; }bool empty() const { return length == 0; }11 unmodified lines17 unmodified lines18192021222324252627282930313233343536373839404142434411 unmodified lines17 unmodified lines~Vector() {delete[] data;}void push_back(const T& value) {if (length >= capacity) {size_t newCap = capacity == 0 ? 1 : capacity * 2;reserve(newCap);}data[length++] = value;}T& operator[](size_t index) {return data[index];}void clear() {length = 0;}T& front() { return data[0]; }T& back() { return data[length - 1]; }size_t size() const { return length; }bool empty() const { return length == 0; }11 unmodified lines
In addition to rendering standard Git diffs and patches, you can pass any two files in @pierre/diffs and get a diff between them. This is especially useful when comparing across generative snapshots where linear history isn't always available. Edit the css below to see the diff.
1233.pizza { display: flex; justify-content: center;}Collectively, our team brings over 150 years of expertise designing, building, and scaling the world's largest distributed systems at Cloudflare, Coinbase, Discord, GitHub, Reddit, Stripe, X, and others.