Branch graph
ProA git commit graph where branches draw in as colored lanes, merge commits join lines, a rebase lifts commits and replays them onto main, a squash folds them into one, and hovering a commit shows its message, author and diff stats.
- A merge strategy picker in a code review or pull request screen, showing exactly what main will look like afterwards.
- A commit history panel in a developer tool, with hover details and keyboard browsing.
- Docs and landing pages for version control or CI products that explain merge, rebase, and squash.
- Use git-branches for a looping, non-interactive illustration of a branch, pull request, and release.
- Use a timeline or changelog for releases people need to read rather than a commit graph.
- Use a virtualized table for long real histories; this graph is built for a focused slice of about five to fifteen commits.
Installation
Pro source and install commands unlock with a Pro plan.
import { BranchGraph } from "@/components/arc/branch-graph/branch-graph"; const ada = { name: "Ada Park", avatar: "/people/ada.jpg" }; const commits = [ { id: "c3", hash: "b81f2e0", message: "Rank results by recency", branch: "feat/search", parents: ["c2"], author: ada, date: "1 hour ago", additions: 64, deletions: 9, files: 3 }, { id: "m2", hash: "4a0c9d1", message: "Upgrade to Node 22", branch: "main", parents: ["m1"], author: ada, date: "3 hours ago", additions: 8, deletions: 8, files: 2 }, { id: "c2", hash: "e27d4b3", message: "Index docs on publish", branch: "feat/search", parents: ["m1"], author: ada, date: "Yesterday", additions: 210, deletions: 12, files: 7 }, { id: "m1", hash: "93c5a07", message: "Launch docs site", branch: "main", parents: [], author: ada, date: "2 days ago", additions: 540, deletions: 0, files: 31 },]; export function MergePreview() { return ( <BranchGraph commits={commits} branches={[{ name: "main" }, { name: "feat/search" }]} pullRequest={{ number: 1207, title: "Instant search", branch: "feat/search" }} autoplay={false} /> );}BranchGraph
An interactive commit graph with a merge preview. Branches draw in as colored lanes from the oldest commit up, and merge commits join their lines with clean curves. A strategy control replays the pull request as a merge commit that joins both lines, a rebase that lifts the branch's commits like cards and replays them on top of the base branch with new hashes, or a squash that folds them into one commit. Hovering or arrowing to a commit shows its message, author, and diff stats, and dims every other branch.
commits{ id: string; hash: string; message: string; body?: string; branch: string; parents?: string[]; author: { name: string; avatar?: string }; date: string; additions?: number; deletions?: number; files?: number }[]a checkout pull request with a merged fixCommits, newest first. parents lists parent ids; a second parent makes a merge commit. date is display text such as "2 hours ago", so server and client agree.branches{ name: string; color?: string }[]main, feat/checkout, fix/roundingBranches in lane order. The first is the base branch the pull request merges into. Colors default to the chart series palette; the first lane follows the accent.pullRequest{ number: number; title: string; branch: string }{ number: 482, title: "Checkout redesign", branch: "feat/checkout" }The pull request being previewed. Its branch's commits are the ones that merge, rebase, or squash; the title names the squashed commit.strategy"open" | "merge" | "rebase" | "squash"–Controlled strategy. open is the history before merging. Pair with onStrategyChange.defaultStrategy"open" | "merge" | "rebase" | "squash""open"Starting strategy when uncontrolled.onStrategyChange(strategy) => void–Called when a strategy is picked.strategies("open" | "merge" | "rebase" | "squash")[]all fourStrategies offered in the control, in order. Pass one to hide the control.titlestringthe pull request titleVisible title with the pull request number. Pass an empty string to hide it.autoplaybooleantrueCycles before, rebase, merge, and squash while nobody interacts. Hover holds it; the first pick, tap, or key press stops it for good. Only when uncontrolled.speednumber1Multiplies the pace of every animation. 0 jumps straight to each state.pausedbooleanfalseHolds the autoplay where it is. Picks by the user still animate.accentstring–Any CSS color for the base lane and hash rewrites. Defaults to the first chart series, which follows the accent.labelstring–Accessible name of the component. Built from the title and branch names when left out.classNamestring–Class on the root element.styleCSSProperties–Inline styles on the root element.- The root is a labelled section. The strategy control is a radiogroup of real radio buttons with a roving tab stop; arrow keys, Home, and End pick a strategy.
- Commits form a listbox in visual order, newest first. Up and Down, Home, and End move through commits with aria-activedescendant; Escape closes the commit card. Each option is named with its message, hash, author, date, diff stats, and the branches that point to it.
- A polite live region announces the result of every strategy picked by the user, such as how many commits were replayed and that they got new hashes. Autoplay changes are not announced.
- The graph and the commit card are aria-hidden because the listbox carries the same information. Branch color is always paired with a named tag, merge commits carry an icon and a hollow dot, and additions and deletions carry + and − signs, so nothing relies on color alone.
- Pointer focus draws no ring; keyboard focus shows Arc's shared focus ring. The keyboard commit takes the hover fill and shows its card.
- One rAF loop writes SVG path data, transforms, and opacity straight to the DOM and skips every value that did not change. React renders only when the strategy or the inspected commit changes.
- Edges are computed each frame from the live positions of their two commits, so lines stretch and bend with them. A branch bends near its parent and a merge bends near its child, like a real git client.
- The entrance draws each line up from the oldest commit at a constant pace, and every commit lands as its line reaches it.
- A rebase lifts the branch's rows into floating cards, carries them one after another along an eased arc into the base lane, and sets them down with a soft ring while their hashes rewrite in place. The old branch point retracts first and the new link to the base tip draws in after the commits land.
- A merge makes room at the top, then the merge commit lands and both lines draw into it. A squash flies the commits into one point where the squashed commit appears; undoing it replays them back out. Every transition starts from wherever things are, so picking a new strategy mid-flight never jumps.
- The loop runs only while something moves or autoplay is waiting, and stops off screen and in hidden tabs. With reduced motion the graph shows each state immediately with no entrance, flights, or autoplay.
- The graph is drawn in real pixels in an SVG beside the rows, so hairlines and dots stay crisp at any width.
- Container queries drop the date below 720px, the author below 560px, and the hash below 400px; the full details stay in the hover card and each option's accessible name. The strategy control takes a full row on phones.
- On touch, a tap on a commit pins its card and a tap elsewhere closes it.
- About sixty SVG and HTML elements are updated from one loop with a write cache, so a quiet frame touches nothing.
- Every layout is computed once per data set. Nothing is measured inside the loop.
Notes for AI
Give your coding assistant the Markdown reference instead of screenshots.
- Use it where people choose how a pull request lands: a merge button, a code review tool, or docs that teach merge, rebase, and squash.
- Pass commits newest first with real parent ids. Keep the pull request branch to two to five commits and use two or three lanes; more lanes read as noise.
- Keep branch names short, like feat/search. The hover card holds the longer story in body.
- The merge, rebase, and squash states are previews computed from the data. Say so if the page could be read as the live state of a repository.
The full library index for assistants is at /llms.txt.