Code

Diff view

A file's changes, before and after. @pierre/diffs under the hood, themed with the kit's syntax palette and diff tokens; +/− glyphs so changes never rely on color alone. Unchanged regions collapse (click to expand, or show all from the header); soft wrap toggles per file; split falls back to unified in narrow panes; optional per-hunk actions and inline line comments.

Live example

Import

Each family is its own entry point, so a page loads only what it uses.

tsx
import { IconButton } from "@nibapp/ui/components/button";import { DiffLines } from "@nibapp/ui/components/diff-lines";import { DiffHighlighterProvider, DiffView } from "@nibapp/ui/components/diff-view";import { MessageBubble } from "@nibapp/ui/components/message-bubble";import { Tooltip } from "@nibapp/ui/components/tooltip";

Props

Each component's own props and variants, read from its source. What it also takes from an element or a Base UI part is named below its table.

IconButton

Square, icon-only button. Pair with <Tooltip> to surface the label visually (a `disabledReason` brings its own).

PropTypeDefaultDescription
size"xs" | "sm" | "md" | "lg""md"
aria-label (required)string—

Required: icon-only controls must be named for assistive tech.

children (required)ReactNode—
loadingbooleanfalse
disabledReasonstring | undefined—

Disables the button and says why, as Button's `disabledReason`.

Also takes the props of Base UI's Button, except aria-label and children.

DiffLines

A few lines of a change on the diff tokens, each with its sign: a preview or a demo, where the full `DiffView` (files, hunks, highlighting) is more than the place needs.

PropTypeDefaultDescription
lines (required)readonly DiffLine[]—
classNamestring—

DiffHighlighterProvider

Moves every descendant <DiffView>'s syntax highlighting into @pierre/diffs' worker pool: a diff first paints as plain text (same rows, same height), then swaps in its highlighted lines when the worker answers, and a content-keyed LRU makes remounts (a virtualized row scrolling back into view) highlighted on their first paint. Without this provider diffs highlight on the main thread while mounting. Mount it once, near the root; it owns the pool's lifetime. The workers start when the first diff mounts, so pages without diffs never download the highlighter.

PropTypeDefaultDescription
workerFactory (required)() => Worker—

Creates one @pierre/diffs highlight worker. The bundler has to see the worker entry, so the host supplies it (Vite, webpack, Rspack): `() => new Worker(new URL( "@pierre/diffs/worker/worker-portable.js", import.meta.url), { type: "module" })`. Read once, on mount. The portable entry, not `worker.js`: in @pierre/diffs 1.6.0 `worker.js` is also the chunk its own lazily loaded highlighter chunks import back (`./worker.js`). A dev server that loads a worker entry under another URL (Vite's `?worker_file`) then runs it twice in each worker. The second copy never gets `initialize`, keeps the library's default themes, and fails every request with `Theme "pierre-dark" has not been resolved`. The portable entry is one self-contained file.

children (required)ReactNode—

DiffView

PropTypeDefaultDescription
modeDiffMode—

Controlled layout.

defaultModeDiffMode—
onModeChange(mode: DiffMode) => void—
splitMinWidthnumber—

Below this container width (px) an uncontrolled split view shows unified instead, until the user picks a layout with the toggle. Default 900; 0 disables the fallback.

collapsedboolean—

Controlled collapse.

defaultCollapsedboolean—
onCollapsedChange(collapsed: boolean) => void—
filenamestring—

Header path; defaults to the new (or old) file name, or the patch's.

wrapboolean—

Soft-wrap long lines (controlled).

defaultWrapboolean—
onWrapChange(wrap: boolean) => void—
showWrapToggleboolean—

Show the soft-wrap toggle (default true).

maxHeightnumber | string—

Scroll the body beyond this height.

showModeToggleboolean—

Show the split/unified toggle (default true).

actionsReactNode—

Extra header controls (e.g. accept / reject), rendered before the built-ins.

onAcceptHunk(hunkIndex: number) => void—

Per-hunk toolbar actions; each receives the hunk's index in the parsed diff.

onRevertHunk(hunkIndex: number) => void—
onAskAboutHunk(hunkIndex: number, excerpt: string) => void—

"Ask about this hunk"; also receives the hunk as unified-diff text to quote.

hunkStatesReadonly<Partial<Record<number, HunkState>>>—

Settled hunks by index: labelled in their toolbar, with the matching action disabled.

commentsreadonly DiffComment[]—

Comments rendered inline under their line.

onAddComment(comment: NewDiffComment) => void—

Enables commenting: click a line number (or the gutter "+") to open an inline comment field.

onRemoveComment(id: string) => void—
commentPlaceholderstring | undefined—

The inline comment field's placeholder, when the host says where comments go; "Leave a comment…" by default.

lineNotesreadonly DiffLineNote[]—

Host content pinned under specific lines (review finding markers, lint notes…).

Also takes | { oldFile: DiffFile | null; newFile: DiffFile | null; patch?: never } | { patch: string; oldFile?: never; newFile?: never }, and the props of a <div> element, except children.

MessageBubble

A message the person sent, as a conversation draws it: a rounded bubble at the end of the line with its tail at the bottom end. The surface alone: the host adds attachments, the time and actions around it.

PropTypeDefaultDescription
from"person" | "agent""person"

Who wrote it: the person (a quiet fill), or an automated sender (a hairline).

size"sm" | "md""md"
sm
A demo or a preview of a conversation.
pendingboolean—

Not sent yet (queued): faded until it goes.

Also takes .

Tooltip

Convenience wrapper: `<Tooltip content="Copy" shortcut={["mod","c"]}><IconButton …/></Tooltip>`. A `shortcut` also sets `aria-keyshortcuts` on the trigger; the keycaps in the popup are hidden.

PropTypeDefaultDescription
content (required)ReactNode—

Tooltip text.

actionstring—

The action this control triggers: its keys, from the host's keymap (`ShortcutProvider`), show after the text, and nothing shows when it has no binding. Prefer it to `shortcut`.

shortcutreadonly string[]—

Keys shown after the text when there is no `action` (keys the caller already read from the keymap).

children (required)ReactElement—

The trigger element; must accept a ref and DOM props (e.g. <IconButton>).

sideTooltipContentProps["side"]—
alignTooltipContentProps["align"]—
disabledboolean—