Three-way diff engine for Figma design system libraries.
Compare constructor & fork. Detect upstream changes, local changes & conflicts. Pixel-level visual diff.
Features • How It Works • Quick Start • Variable Export • Structure • Tech Stack • Configuration
When a design system library is forked in Figma, tracking what changed upstream vs. what was customized locally becomes impossible manually. DiffLib solves this:
| Problem | Solution |
|---|---|
| "What changed upstream since we forked?" | Three-way diff with automatic baseline resolution |
| "Did our local edits conflict with upstream?" | Conflict detection with side-by-side comparison |
| "Are the visual renders actually different?" | Pixel-level diff with pixelmatch |
| "I need to share the diff with my team" | Self-contained HTML reports, no server needed |
| "Setting up a diff tool is painful" | Paste two Figma URLs and click Generate |
- Three-way structural diff — Components, styles & variables compared across base / upstream / local with automatic change attribution
- Two-way fallback — Works even when no common version history exists between files
- Pixel diff — Side-by-side renders + pixelmatch overlay to spot visual regressions
- Image lightbox — Hover any render to zoom into a full-screen frosted-glass viewer
- Rename detection — Fuzzy matching catches renamed components instead of flagging delete + add
- Variable diff — Upload Figma variable JSON exports for two-way variable comparison
- Self-contained reports — Each report is an HTML folder with
data.json+ images — works offline, shareable - Regenerate — One-click re-run from any report page with confirmation dialog and live progress
- Real-time progress — SSE-based live progress with step-by-step feedback during comparison
- API quota tracking — Live indicator in the navbar showing Figma API usage and rate limits
- Settings UI — Configure your Figma PAT from the browser, no server restart needed
- 100% local — Your designs never leave your machine
Constructor (upstream) Fork (local)
| |
v v
Figma API ─────────────────── Figma API
| |
┌────┴────┐ ┌─────┴────┐
│ Current │ │ Current │
│ version │ │ version │
└────┬────┘ └─────┬────┘
│ Base │
│ (constructor at │
│ fork time) │
│ │ │
v v v
┌─────────────────────────────────┐
│ Three-Way Diff Engine │
│ components + styles + variables │
└──────────────┬──────────────────┘
│
v
┌──────────────────────────────────┐
│ Pixel Diff (pixelmatch) │
│ upstream render vs local render │
└──────────────┬───────────────────┘
│
v
┌──────────────────────────────────┐
│ Self-Contained HTML Report │
│ report.html + data.json + imgs │
└──────────────────────────────────┘
- Baseline resolution — Walks version history to find the constructor version that matches the fork creation date
- Fetch & normalize — Pulls file data for base, upstream, and local; strips volatile fields
- Diff — Eight-case change attribution table:
upstream_changed,local_changed,conflict,new_upstream,new_local,deleted_upstream,deleted_local,renamed_* - Visual diff — Renders components via Figma Images API, composites with pixelmatch (top-left aligned)
- Report — Generates a portable HTML folder with all data and images embedded
- Node.js 20+
- pnpm 9+
- Figma Personal Access Token — Generate one here
git clone https://github.com/vincegx/Figma-DS-DiffLib.git
cd Figma-DS-DiffLib
pnpm installcp .env.example .env
# Edit .env → FIGMA_PAT=figd_your_token_hereOr configure via the UI: start the dev server and go to Settings.
pnpm --filter @figma-ds-diff/web dev
# Open http://localhost:3000- Go to
/new - Paste the constructor (upstream) Figma library URL
- Paste the fork (local) Figma library URL
- Optionally upload variable JSON exports
- Click Generate and watch the live progress
- View, zoom, and explore the report
The Figma REST API does not expose variables (design tokens). Unlike components and styles which DiffLib fetches automatically via the API, variables can only be accessed from within Figma itself using a plugin. This is a known limitation of Figma's public API.
To include variable diffs in your report, you need to manually export the variables from both files using a Figma plugin, then upload the two JSON files during step 2 of the comparison wizard.
Luckino — Variables Import/Export JSON & CSS
This plugin exports all variable collections in the W3C Design Tokens Community Group (DTCG) format, which is what DiffLib expects.
- Open your constructor (upstream) Figma library file
- Run the plugin: Plugins → Luckino Variables Import/Export
- Select Export and choose JSON format
- Export all collections — save the file (e.g.
variables_constructor.json) - Open your fork (local) Figma library file
- Repeat steps 2–4 — save as
variables_fork.json
During comparison setup (step 2 — "Variables"), drag & drop or select both JSON files:
- First file → constructor (upstream) variables
- Second file → fork (local) variables
This step is optional — you can skip it if you only need component and style diffs. Variable diffs are always two-way (constructor vs fork), since there is no version history for exported JSON.
The JSON follows the DTCG structure — collections at the top level, variables nested with $value and $type:
{
"Tokens": {
"Spacing": {
"base_1(4)": { "$value": 4, "$type": "number" },
"base_2(8)": { "$value": 8, "$type": "number" }
},
"Colors": {
"primary": { "$value": "#6366F1", "$type": "color" }
}
}
}Note: When regenerating a report from the report page, variable diffs are skipped because the JSON files are not stored in the report. Re-upload them via
/newif you need updated variable diffs.
pnpm monorepo with two packages:
packages/
├── core/ # @figma-ds-diff/core — pure TypeScript library
│ └── src/
│ ├── figma/ # API client, Zod schemas, URL parser, quota tracker
│ ├── baseline/ # Version history baseline resolver
│ ├── normalize/ # Components, styles, variables, filters
│ ├── diff/ # Three-way engine, visual diff, rename detection
│ ├── images/ # Batch PNG downloader (chunks of 50)
│ ├── report/ # Self-contained HTML report generator
│ └── index.ts # Barrel exports
│
└── web/ # @figma-ds-diff/web — Next.js 15 App Router UI
└── src/
├── app/
│ ├── api/compare/ # POST — SSE comparison orchestration
│ ├── api/reports/ # GET — list & serve reports + images
│ ├── api/quota/ # GET — API quota stats
│ ├── api/settings/ # POST — runtime config (FIGMA_PAT)
│ ├── new/ # Comparison wizard (3-step form)
│ ├── report/[slug]/ # Report viewer
│ └── settings/ # Settings page
├── components/
│ ├── home/ # Hero, report table, stat cards
│ ├── layout/ # NavBar with quota indicator
│ ├── new/ # Wizard steps, live processing view
│ ├── report/ # Sidebar, visual comparison, lightbox, pixel diff, regenerate
│ ├── settings/ # Settings form
│ └── shared/ # Reusable atoms (stat card, badges, dots)
├── hooks/ # useApiQuota, useKeyboardNav
└── lib/ # Utils, data mapper, runtime config
| Layer | Technology |
|---|---|
| Runtime | Node.js 20+, ESM everywhere |
| Language | TypeScript 5.7+ (strict, noUncheckedIndexedAccess, verbatimModuleSyntax) |
| Package manager | pnpm 9+ with workspaces |
| Validation | Zod v3 for all Figma API responses |
| Image processing | sharp (resize/composite) + pixelmatch (pixel diff) |
| Testing | Vitest 3 (170+ tests) |
| Web framework | Next.js 15 (App Router, Server Components) |
| UI | React 19, Tailwind CSS 4, Radix UI, shadcn/ui |
| Charts | Recharts (quota dashboard) |
| HTTP | Node.js built-in fetch (no axios) |
| Variable | Description | Required |
|---|---|---|
FIGMA_PAT |
Figma Personal Access Token | Yes |
Set in .env at the project root, or configure via the Settings page in the UI.
The token is read directly from the .env file (with a 5-second cache), so updates take effect without restarting the server.
# Install dependencies
pnpm install
# Type check both packages
pnpm --filter @figma-ds-diff/core typecheck
pnpm --filter @figma-ds-diff/web typecheck
# Run tests (170+ tests)
pnpm --filter @figma-ds-diff/core test
# Dev server (port 3000)
pnpm --filter @figma-ds-diff/web dev
# Production build
pnpm --filter @figma-ds-diff/web buildEach report is a self-contained folder:
reports/2026-02-15_011200_Library_vs_Fork/
├── report.html # Standalone HTML viewer
├── data.json # All diff data (components, styles, variables, summary)
└── images/
├── component_name_upstream.png
├── component_name_local.png
└── component_name_diff.png
- Portable — open
report.htmldirectly in any browser - Shareable — zip the folder and send it to your team
- Regeneratable —
data.jsonstores file keys for one-click re-run
| Document | Description |
|---|---|
| CLAUDE.md | Developer guide — architecture, conventions, pitfalls |
| docs/SPEC.md | Technical specification — diff algorithm, change attribution |
| docs/UI-SPEC.md | UI specification — design system, component patterns |
MIT
Built for design system teams managing Figma library forks.



